【linux】多人聊天室实现

来源:互联网 发布:mac打不开微信链接 编辑:程序博客网 时间:2024/06/06 05:11

一、设计思路


服务器接收来自客户端的连接请求,当有客户端发送过来数据时,

服务器将数据保存到全局缓冲区,并将数据循环发送给已经连接的客户端


二、代码展示


1.  服务器端charroom_server_01.c

#include <stdio.h>#include <string.h>#include <sys/select.h>#include <sys/types.h> #include <sys/socket.h>#include <sys/time.h>#include <unistd.h>#include <errno.h>#include <netinet/in.h>  //for INADDR_ANY#define MAX_CLIENT_NUM 10/* 客户端最大连接数 */#define MAXLENGHT 1024/* 最大缓存长度 */#define SERVER_PORT 5012/* 服务器端的端口号 */#define LISTEN_MAX_NUM 5/* 监听队列的最大值 */int main(void){int clientSocket = -1, listenSocket = -1;int client[MAX_CLIENT_NUM];char buf[MAXLENGHT] = {0};fd_set allset, rset;socklen_t clilen;struct sockaddr_in cliaddr,  seraddr;int i = -1, maxi = -1, maxfd = -1, ret = -1, reuse = -1, iReady = -1;struct timeval tv;/* 创建TCP监听socket */listenSocket = socket(AF_INET, SOCK_STREAM, 0);if (-1 == listenSocket){printf("create socket error: %s\n",strerror(errno));return -1;}else{printf("listenSocket = %d\n", listenSocket);}bzero(&seraddr, sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_addr.s_addr = htonl(INADDR_ANY);seraddr.sin_port = htons(SERVER_PORT);/* 设置端口重用 */reuse = 1;ret = setsockopt(listenSocket,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)); if (0 > ret){printf("setsockopt error: %s\n",strerror(errno));return -1;}/* 绑定IP和端口 */ret = bind(listenSocket, (struct sockaddr *)&seraddr, sizeof(seraddr));if (0 > ret){printf("bind error: %s\n",strerror(errno));return -1;}/* 设置监听 */ret = listen(listenSocket, LISTEN_MAX_NUM);if (0 > ret){printf("listen error: %s\n",strerror(errno));return -1;}/* 初始化 */maxfd = listenSocket;memset(buf, 0 ,sizeof(buf));for (i = 0; i < MAX_CLIENT_NUM; i++)client[i] = -1;FD_ZERO(&allset);FD_SET(listenSocket, &allset);/* set two seconds to timeout *///tv.tv_sec = 2;//tv.tv_usec = 0;for (;;){//iReady = select(maxfd + 1, &allset, NULL, NULL, &tv);//将allset进行赋值的好处是,当监听到客户端的连接,if (FD_ISSET(fd, &rset))不会马上执行,因为新的客户端还没设置给rsetrset = allset;iReady = select(maxfd + 1, &rset, NULL, NULL, NULL);if (0 > iReady){printf("select error: %s, errno: %d\n",strerror(errno), errno);return -1;}else if (0 == iReady) //限定时间内没有数据到来{printf("no data within two seconds.\n");continue;}else{printf("iReady=%d\n", iReady);}/* 客户端发起连接 */if (FD_ISSET(listenSocket, &rset)){/* 接受来自客户端的连接 */clilen = sizeof(cliaddr);clientSocket = accept(listenSocket, (struct sockaddr *)&cliaddr, &clilen);printf("clientSocket : %d\n", clientSocket);if (-1 == clientSocket){printf("accept error: %s\n",strerror(errno));continue;}/* 保存已连接客户端的socket */for (i = 0; i < MAX_CLIENT_NUM; i++){if (client[i] < 0){client[i] = clientSocket;break;}}FD_SET(clientSocket, &allset);if (clientSocket > maxfd)maxfd = clientSocket;if (i > maxi)// maxi表示当前连接的客户端数量maxi = i;}/* 处理与客户端的通信 */for (i = 0; i <= maxi; i++){int fd = -1, n = -1;if ((fd = client[i]) < 0)continue;/* FD_ISSET为true,只是表明fd在rset集合中有设置,不能说明fd有数据到来*/if (FD_ISSET(fd, &rset)){for (;;){//接受来自客户端的数据,当前是阻塞的memset(buf, 0, sizeof(buf));n = recv(fd, buf, sizeof(buf), 0);if ( n < 0){printf("recv error: %s\n",strerror(errno));if(errno == EAGAIN)continue;elsebreak;;}else if (n == 0) //客户端关闭了socket{printf("client close: %d\n", fd);close(fd);FD_CLR(fd, &allset);client[i] = -1;break;}if (n != sizeof(buf)) //说明没有数据可读{printf("recv:%s\n", buf);//将接受的数据都发送给连接的每个客户端int j = 0, size = 0, fd2 = -1;for (j = 0; j <= maxi; j++){if ((fd2 = client[j]) < 0)continue;char tmp[MAXLENGHT] = {0};snprintf(tmp, sizeof(tmp), "%d say: %s", i, buf);size = send(fd2, tmp, strlen(tmp), 0);//printf("send: %s, size: %d, n: %d, fd2:%d\n", buf, size, n, fd2);if (size < 0){printf("send error: %s\n",strerror(errno));continue;}}break;}else{continue; //可能有数据,还要尝试再读取}}}}}}


2. 客户端charroom_client_01.c


#include <stdio.h>#include <string.h>#include <sys/select.h>#include <sys/types.h> #include <sys/socket.h>#include <sys/time.h>#include <unistd.h>#include <arpa/inet.h>#include <errno.h>#include <netinet/in.h>  //for INADDR_ANY#include <pthread.h>#define SERVER_PORT 5012/* 服务器端的端口号 */#define MAX_LENGTH 1024     /* 缓存最大长度 *//* 表示是否需要发送数据给服务器端  * 1表示接收到用户的输入信息,需要发送给服务器  * 0表示接收服务端的消息 */static int g_iIsSend = 0;/* 发送缓冲区 */char sendBuf[MAX_LENGTH] = {0};/* 表示是否正在等待用户的输入 * 1正在等待用户的输入 * 0用户已输入完成 */static int g_iWait = 1;/* 线程处理函数 */void *recevie_user_input(void *arg);int main(void){int sockfd = -1, ret = -1, i = -1;struct sockaddr_in servaddr;char buf[MAX_LENGTH] = {0};int n = -1;pthread_t tid;pthread_attr_t attr;/* 创建socket s*/sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd){printf("create socket error: %s\n",strerror(errno));return -1;}bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(SERVER_PORT);inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);ret = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));if (-1 == ret){printf("connect socket error: %s\n",strerror(errno));return -1;}pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); //设置分离属性if (pthread_create(&tid, &attr, recevie_user_input, NULL) != 0){printf("create thread error.\n");return -1;}while(1){/* 如果1 == g_iIsSend,表示用户输入的信息已经存到发送缓存sendBuf,需要发送给服务器 */if (1 == g_iIsSend){n = send(sockfd, sendBuf, strlen(sendBuf), 0);if (n < 0){printf("send error: %s\n",strerror(errno));break;}g_iIsSend = 0;continue;}/* 不断循环检服务器是否发送过来数据,如果有,接受数据,并显示到屏幕上,如果没有立即返回,并跳到while(1)处 */memset(buf, 0, sizeof(buf));//设置为非阻塞MSG_DONTWAIT,如果发生阻塞则返回EAGAINn = recv(sockfd, buf, sizeof(buf), MSG_DONTWAIT);if ( n < 0){if(errno == EAGAIN)continue;elsebreak;;}else if (n == 0) //对端关闭{close(sockfd);sockfd  = -1;}/* 1 == g_iWait表示此时客户端没有做任何操作,但是服务器有数据可能,应该是其他客户端发来的消息 */if (1 == g_iWait){printf("\n");printf("\033[1A"); //先回到上一行           printf("\033[K");  //清除该行“"please input:”  }/* 显示完服务器发来的数据,重新显示删除的行“"please input:”*/printf("%s", buf);if (1 == g_iWait){printf("please input:");fflush(stdout);}}if (sockfd < 0){close(sockfd);sockfd = -1;}return 0;}/* 处理用户输入的信息  * CTRL + Backspace : 可以一个一个字符删除 * CTRL + U: 重新从开始输入  */void *recevie_user_input(void *arg){int i = -1;while(1){/* 设置标准输出不带缓冲,这样不用加"\n"也会即时输出 *///setbuf(stdout, NULL);if (0 == g_iIsSend){/* 从标准输入读取字符串 */printf("please input:");//把输出缓冲区里的东西打印到标准输出设备,否则,不会立即输出fflush(stdout);g_iWait = 1;memset(sendBuf, 0, sizeof(sendBuf));read(STDIN_FILENO, sendBuf, sizeof(sendBuf));g_iWait = 0; //表示用户发送消息结束/* * \e[ 或 \033[ 是 CSI,用来操作屏幕的。             * \e[K 表示从光标当前位置起删除到 EOL (行尾)             * \e[NX 表示将光标往X方向移动N,X = A(上) / B(下) / C(左) / D(右),\e[1A 就是把光标向上移动1行 */printf("\033[1A"); //先回到上一行           printf("\033[K");  //清除该行  g_iIsSend = 1;}}}


三、 代码编译

[root@f8s charroom]# gcc charroom_server_01.c -o charroom_server_01[root@f8s charroom]# gcc charroom_client_01.c -o charroom_client_01 -lpthread


四、 实例演示


1.  开启服务器

[root@f8s charroom]# ./charroom_server_01listenSocket = 3

2. 第一个客户端

[root@f8s charroom]# ./charroom_client_010 say: hi0 say: i'm zhangshan1 say: hello1 say: my name is lisi.0 say: nice to meet you.1 say: nice to meet you.please input:

3. 第二个客户端

[root@f8s charroom]# ./charroom_client_011 say: hello1 say: my name is lisi.0 say: nice to meet you.1 say: nice to meet you.please input:



0 0
原创粉丝点击