基于TCP的网络聊天室实现(C语言)

  • 一、网络聊天室的功能
  • 二、网络聊天室的结果展示
  • 三、实现思路及流程
  • 四、代码及说明
    • 1.LinkList.h
    • 2.LinkList.c
    • 3.client.c
    • 4.server.c

一、网络聊天室的功能

有新用户登录,其他在线的用户可以收到登录信息
有用户发送群聊消息,其他在线的用户可以收到群聊信息
有用户退出,其他在线的用户可以收到退出信息
服务器可以发送系统信息


二、网络聊天室的结果展示

1.已经加入群聊的用户可以看到新加入群聊的用户
2.用户退出或者断线,其他用户也可以看到
3.server端可以发送系统消息给所有在聊天室的用户

三、实现思路及流程

客户端登录之后,为了实现一边发送数据一边接收数据,可以使用多进程或者多线程。
服务器既可以发送系统信息,又可以接收客户端信息并处理,可以使用多进程或者多线程。
服务器需要给多个用户发送数据,所以需要保存每一个用户的信息,使用链表来保存。
数据传输的时候要定义结构体,结构体中包含操作码、用户名以及数据。

四、代码及说明

对登录聊天室的用户,需要保存用户信息,本文用链式存储来存放用户信息,因此需要用到链队列,动态分配空间。链式队列的便利之处就在于往队列中插入用户信息的时候,不用想数组那样可能需要大量移动数据。

1.LinkList.h

代码如下:

#ifndef __LINKLIST_H__
#define __LINKLIST_H__#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>#define ERRLOG(msg)                                         \do {                                                    \printf("%s:%s:%d\n", __FILE__, __func__, __LINE__); \perror(msg);                                        \exit(-1);                                           \} while (0)#define N 128#define LEN 128
#define NUM_USR 64#define datatype int//自定义结构体,用来保存所有连接的客户的IP地址、端口号以及acceptfd文件描述符这三个参数
typedef struct info {struct sockaddr_in clientaddr;int acceptfd;char name[16];char named;struct info* next;char sayHi;
} usr_info_t;usr_info_t* info_head;usr_info_t* LinkListNodeCreate(void);
int LinkListInsertHead(usr_info_t* head, usr_info_t* node);
usr_info_t* LinkListSearchUsrByAcceptfd(usr_info_t* h, int acceptfd);#endif

2.LinkList.c

代码如下:

#include "LinkList.h"
#include <string.h>
/**功能:创建单链表*参数:*  @:无*返回值:成功返回单链表节点的首地址,失败返回NULL*注:创建链表节点,将指针域指向NULL,并将节点内容清零*/
usr_info_t* LinkListNodeCreate(void)
{usr_info_t* h;h = (usr_info_t*)malloc(sizeof(*h));if (h == NULL) {printf("alloc memory error\n");return NULL;}memset(h, 0, sizeof(*h));h->next = NULL;return h;
}/**功能:单链表按照头插法插入数据*参数:*  @head: 用户信息链表头节点的首地址*   @node: 要插入的节点的地址*返回值:成功返回0*/
int LinkListInsertHead(usr_info_t* head, usr_info_t* node)
{// 1.将node插入到head中即可node->next = head->next;head->next = node;// 2.成功返回0return 0;
}/**功能:通过Acceptfd查询用户数据*参数:*  @h:用户信息链表头指针*  @acceptfd:accept函数产生的文件描述符*返回值:成功返回0,失败返回-1*/
usr_info_t* LinkListSearchUsrByAcceptfd(usr_info_t* h, int acceptfd)
{while (h->next != NULL) {if (h->next->acceptfd == acceptfd) {return h->next;}h = h->next;}printf("用户不存在,查询失败\n");
}

3.client.c

代码如下:

#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>#define ERRLOG(msg)                                         \do {                                                    \printf("%s:%s:%d\n", __FILE__, __func__, __LINE__); \perror(msg);                                        \exit(-1);                                           \} while (0)void recycle()
{wait(NULL);
}void tuichu()
{exit(0);
}int main(int argc, const char* argv[])
{//参数合理性检查if (3 != argc) {printf("Usage : %s <IP> <port>\n", argv[0]);exit(-1);}//创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd) {ERRLOG("socket error");}//填充服务器网络信息结构体struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));serveraddr.sin_addr.s_addr = inet_addr(argv[1]);socklen_t serveraddr_len = sizeof(serveraddr);//与服务器建立连接if (-1 == connect(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {ERRLOG("connect error");}printf("与服务器连接成功..\n");char buff[128] = { 0 };int num = 0;//等待子进程退出信号SIGCHLD,回收子进程资源signal(SIGCHLD, recycle);//需要分出来一个子进程,专门用于接收消息,父进程用于发送消息pid_t pid = fork();int named = 0;while (1) {if (pid == 0) { //子进程,接收消息signal(SIGINT, tuichu); //子进程捕获到SIGINT信号就退出//接收应答消息memset(buff, 0, sizeof(buff));if (-1 == (num = recv(sockfd, buff, 128, 0))) {ERRLOG("recv ersockfdror");}printf("%s\n", buff);//如果收到自己的退出消息,就结束子进程} else { //父进程,发送消息if (named == 0) {printf("请输入群聊名称:\n");named = 1;} else if (named == 1) {printf("请再次输入群聊名称:\n");named = 2;}fgets(buff, 128, stdin);if (strlen(buff) != 0) {buff[strlen(buff) - 1] = '\0';}//如果用户输入quit就退出if (!strcmp(buff, "quit")) {//如果退出了,最后也要发送数据"quit"if (-1 == send(sockfd, buff, 128, 0)) {ERRLOG("send error");}char buf[22] = { 0 };sprintf(buf, "kill %d", pid);system(buf);wait(NULL);break;}//发送数据if (-1 == send(sockfd, buff, 128, 0)) {ERRLOG("send error");}}}//关闭套接字close(sockfd);return 0;
}

4.server.c

代码如下:

#include "LinkList.h"int max_fd = 0;
int acceptfd = 0;
int ret = 0;
int i = 0;
int nbytes = 0;
char buff[N] = { 0 };
int loop = 0;
char send_buf[128] = { 0 };
//创建要监视的文件描述集合
fd_set readfds; //母本
fd_set readfds_temp; //给select擦除用的void* sendThread(void* arg)
{char sys_send_buf[256] = { 0 };while (1) {memset(send_buf, 0, sizeof(send_buf));scanf("%s", send_buf);/*for (loop = 4; loop < max_fd + 1 && ret != 0; loop++)这样写是不对的,因为,和主线程共享ret,主线程中的ret每次结束都是会减到0的,所以如果这么写,一次循环也不会进入!!!!*/memset(sys_send_buf, 0, sizeof(sys_send_buf));sprintf(sys_send_buf, "[系统消息]:%s\n", send_buf);printf("%s\n", sys_send_buf);for (loop = 4; loop < max_fd + 1; loop++) {if (FD_ISSET(loop, &readfds)) {if (-1 == send(loop, sys_send_buf, N, 0)) {ERRLOG("send error");}}}}
}int main(int argc, const char* argv[])
{//参数合理性检查if (3 != argc) {printf("Usage : %s <IP> <port>\n", argv[0]);exit(-1);}//创建套接字 流式套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd) {ERRLOG("socket error");}//填充网络信息结构体struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(argv[1]);serveraddr.sin_port = htons(atoi(argv[2]));socklen_t serveraddr_len = sizeof(serveraddr);//绑定if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {ERRLOG("bind error");}//将套接字设置成被动监听状态if (-1 == listen(sockfd, 5)) {ERRLOG("listen error");}//清空集合FD_ZERO(&readfds);FD_ZERO(&readfds_temp);//将sockfd添加到集合中FD_SET(sockfd, &readfds);//更新最大文件描述符max_fd = max_fd > sockfd ? max_fd : sockfd;socklen_t clientaddr_len = sizeof(struct sockaddr_in);usr_info_t* usr_info_head = LinkListNodeCreate();usr_info_t* usr_info_node;usr_info_t* usr = NULL;pthread_t tid;int err_code;if (0 != (err_code = pthread_create(&tid, NULL, sendThread, NULL))) {printf("pthread_create error %s\n", strerror(err_code));exit(-1);}while (1) {readfds_temp = readfds;if (-1 == (ret = select(max_fd + 1, &readfds_temp, NULL, NULL, NULL))) {ERRLOG("select error");}//遍历集合 看哪个文件描述符就绪了for (i = 3; i < max_fd + 1 && ret != 0; i++) {if (FD_ISSET(i, &readfds_temp)) {if (sockfd == i) {//说明有新客户端连接了,创建一个新节点保存客户信息usr_info_node = LinkListNodeCreate();if (-1 == (acceptfd = accept(sockfd, (struct sockaddr*)&usr_info_node->clientaddr, &clientaddr_len))) {ERRLOG("accept error");}usr_info_node->acceptfd = acceptfd;LinkListInsertHead(usr_info_head, usr_info_node); //用户信息入队printf("客户端[%d]号连接到服务器..\n", usr_info_node->acceptfd);printf("客户端[%d]已连接\n", ntohs(usr_info_node->clientaddr.sin_port));memset(send_buf, 0, sizeof(send_buf));//将新客户端的acceptfd加入到集合FD_SET(acceptfd, &readfds);//更新最大文件描述符max_fd = max_fd > acceptfd ? max_fd : acceptfd;} else {//说明有客户端发来数据了usr = LinkListSearchUsrByAcceptfd(usr_info_head, i); //先根据acceptfd找一下用户信息if (-1 == (nbytes = recv(i, buff, N, 0))) {ERRLOG("recv error");} else if (0 == nbytes) {printf("客户端[%d]断开连接..\n", i);memset(send_buf, 0, sizeof(send_buf));sprintf(send_buf, "用户[%s]断开连接..", usr->name);for (loop = 4; loop < max_fd + 1 && ret != 0; loop++) {if (FD_ISSET(loop, &readfds) && loop != i) {if (-1 == send(loop, send_buf, N, 0)) {ERRLOG("send error");}}}//将该客户端的文件描述符在集合中删除FD_CLR(i, &readfds);close(i);continue;}if (!strncmp(buff, "quit", 4)) {printf("客户端[%d]退出了..\n", i);memset(send_buf, 0, sizeof(send_buf));sprintf(send_buf, "用户[%s]退出了..", usr->name);for (loop = 4; loop < max_fd + 1 && ret != 0; loop++) {if (FD_ISSET(loop, &readfds) && loop != i) {if (-1 == send(loop, send_buf, N, 0)) {ERRLOG("send error");}}}//将该客户端的文件描述符在集合中删除FD_CLR(i, &readfds);close(i);continue;}//第一次发过来的是用户的名字,所以,应该保存一下if (usr->named != 9) {strcpy(usr->name, buff);usr->named = 9;continue;}printf("客户端[%d]发来数据[%s]..\n", i, buff);//组装应答if (usr->sayHi == 7) {memset(send_buf, 0, sizeof(send_buf));sprintf(send_buf, "用户[%s]:%s\n", usr->name, buff);} else { //只有第一次才会对所有已经加入群聊的用户显示**加入群聊memset(send_buf, 0, sizeof(send_buf));sprintf(send_buf, "用户[%s]加入群聊..\n", usr->name);usr->sayHi = 7;}//如果用户的acceptfd还在readfds里,就接收本次用户发送的消息.注意不能从3开始,3是sockfd,给sockfd发消息,会出错!!!!for (loop = 4; loop < max_fd + 1 && ret != 0; loop++) {if (FD_ISSET(loop, &readfds) && loop != i) {if (-1 == send(loop, send_buf, N, 0)) {ERRLOG("send error");}}}ret--;}}}}close(sockfd);return 0;
}

基于TCP的网络聊天室实现(C语言)相关推荐

  1. QT学习:基于TCP的网络聊天室程序

    TCP与UDP的差别如图: 一.TCP工作原理 如下图所示,TCP能够为应用程序提供可靠的通信连接,使一台计算机发出的字节流无差错 地送达网络上的其他计算机.因此,对可靠性要求高的数据通信系统往往使用 ...

  2. 基于TCP的网络聊天室

    综合分析: 基于TCP的聊天室,支持多个用户同时登陆服务器进行聊天.(相当于群) socket编程,做一对多的通信,必然要用到多线程,保证多个客户端(并行)登陆服务器时同时进行聊天. 项目要求: 利用 ...

  3. 【基于UDP的网络聊天室】

    总结下近期写的小项目,在学习中同时积累解决问题的经验,以及真正的项目中解决问题的思路,如有不合理地方,请多指教! 一.项目名称 基于UDP的网络聊天室 二.功能 1.当有新用户登录时,其他在线用户可以 ...

  4. 基于UDP的网络聊天室网络编程0811作业-洪庆乐

    项目:基于UDP的网络聊天室 功能:1.服务器日志系统,且可以查看(实现) 2.有用户,其他用户收到这个人登入信息(实现) 3.群发消息(实现) 4.如果有用户下线,其他用户收到下线消息(实现) 5. ...

  5. 【毕业设计之PHP系列】基于PHP的网络聊天室系统

    基于PHP的网络聊天室系统 摘要:我们生活在一个通信变得非常重要的世界里,人们需要同他人快速容易的进行交流.E-mail.电话.邮件以及在线聊天是以书写文字的形式让人们进行思想交流的媒体.通信时一个重 ...

  6. Linux C/TCP多线程网络聊天室

    多线程TCP网络聊天室 我们都知道TCP是一种基于连接的传输协议,使用多线程来实现其实步骤很简单. 首先说明一下项目:服务端开启后,直接运行客户端可以加入聊天室,同时其他客户端可以接收到某某客户端进入 ...

  7. python基于udp的网络聊天室再用tkinter显示_Python实现网络聊天室的示例代码(支持多人聊天与私聊)...

    实验名称: 网络聊天室 功能: i. 掌握利用Socket进行编程的技术 ii. 掌握多线程技术,保证双方可以同时发送 iii. 建立聊天工具 iv. 可以和单人聊天 v. 可以和多个人同时进行聊天 ...

  8. 【完整代码及文档】基于Java的网络聊天室系统的设计与实现

    摘 要 计算机从出现到现在有了飞速的发展,现阶段的计算机已经不单单是用于进行运算的独立的个体了,跟随计算机一同发展的还有互联网技术,经过了长久的发展,互联网技术有了日新月异的发展,它的发展速度和计算机 ...

  9. javaweb课程设计:基于websocket的网络聊天室(所有的资源和代码还有详细步骤我都会提供)

    1 课程设计目的和任务 本项目的是实现在web应用上进行多人聊天,为以后在大型项目中实现客服在线服务做一个测试,提前了解HTML5新特性,熟练掌握websocket技术. 2 课程设计的主要内容 实现 ...

最新文章

  1. 不同Unix环境下date计算日期的用法
  2. Solr 配置文件之schema.xml
  3. python新建txt文件,并逐行写入数据
  4. Asp.Net大型项目实践系列导航 [以后要仔细看的,支持作者 转]
  5. NAT (PAT)地址转换技术(讲解+配置)
  6. 让UI设计显得魅力非凡,设计师少不了的渐变背景素材专辑,
  7. 在线JSON美化格式化工具
  8. mysql mangodb哪个简单,mysql和mongodb学哪个更加容易
  9. Netty in action—ChannelHandler和ChannelPipeline
  10. Objective-C 内存管理
  11. mro python_Python进阶-继承中的MRO与super
  12. 我的Python心路历程 第十期 (10.12 股票实战可视化之分位数)
  13. hdoj 1163 Eddy's digital Roots(数学问题讲究的是分析,找规律)
  14. 多伦多大学计算机专业硕士,多伦多大学计算机硕士专业 看你满足录取要求吗...
  15. 100层楼,2个鸡蛋,最少要几次才能测试出鸡蛋能承受的最大楼层?
  16. 玩转「Wi-Fi」系列之wpa_supplicant 介绍(七)
  17. 未识别的网络 无internet
  18. dos命令进入文件夹
  19. ARM芯片、内核、架构、指令集的联系与区别
  20. pip永久修改下载源(豆瓣源)

热门文章

  1. 2021年熔化焊接与热切割新版试题及熔化焊接与热切割模拟考试
  2. android studio自带的取色器(可脱离as界面取色)
  3. Java代码规范与质量检测插件SonarLint
  4. 如何用AI制作发光字?
  5. Datawhale组队学习-金融时序数据挖掘实践-Task01数据探索与分析
  6. 切图工具的附加功能:景区内导航的初步实现
  7. 我没有资格颓废--马云
  8. 用JAVA正则表达式轻松替换JSON中大字段
  9. 我还是个孩子,一个疯疯癫癫的孩子
  10. Access 窗体实现用户名及密码验证登录