tcp迷你聊天室
以http://www.cnblogs.com/Zzz-y/p/9107554.html里的 server 为雏形写了一个迷你型的多人聊天室。
client跟之前一样。主要对 server 做了一些改进:
1、聊天室要实现收到消息后,对所有的client广播,所以这边改成建立一个线程服务一个客户,于是clientfd共享就方便的多。
2、用两个数组tid和fd_array分别保存线程id和fd,两者用数组索引idx一一对应。一个客户进入聊天室,占用一个idx。当客户退出,释放idx。
3、聊天室同时可接纳人数上限为MAX_NUM。服务器在accept之后,对已经退出的tid作资源回收,并分配给新的客户端。如果没有tid,则提示房间已满并关闭连接。
4、服务器接受SIGINT信号作为结束,结束时回收所有线程资源,关闭fd。
运行结果:
服务器:
客户端:
如果房间满员:
代码:
1 #include <stdio.h> 2 #include <sys/socket.h> 3 #include <sys/types.h> 4 #include <arpa/inet.h> 5 #include <netinet/in.h> 6 #include <unistd.h> 7 #include <string.h> 8 #include <stdlib.h> 9 #include <pthread.h> 10 #include <time.h> 11 #include <signal.h> 12 13 #define MAX_NUM 3 14 15 struct tclient 16 { 17 int idx; 18 struct sockaddr_in addr; 19 }; 20 21 pthread_t tid[MAX_NUM]; 22 int fd_array[MAX_NUM]; 23 int serverfd; 24 25 void sig_act(int signo) { 26 for (int i = 0; i < MAX_NUM; ++i) 27 { 28 if (tid[i] != 0) { 29 pthread_join(tid[i], nullptr); 30 } 31 } 32 close(serverfd); 33 printf("server closed!\n"); 34 exit(0); 35 } 36 37 void* foo(void *arg) { 38 struct tclient tc = *static_cast<struct tclient*>(arg); 39 char buff[1024]; 40 char msg[1024]; 41 int fd = fd_array[tc.idx]; 42 int len; 43 time_t timep; 44 inet_ntop(AF_INET, &tc.addr.sin_addr, buff, sizeof(buff)); 45 printf("connction from %s, port %d\n", buff, ntohs(tc.addr.sin_port)); 46 while(1) { 47 len = read(fd, buff, 1024); 48 time (&timep); 49 if (len > 0) { 50 printf("receive from %d: %s\n", ntohs(tc.addr.sin_port), buff); 51 sprintf(msg, "%sreceive from %d: %s\n", ctime(&timep), ntohs(tc.addr.sin_port), buff); 52 for (int i = 0; i < MAX_NUM; ++i) 53 { 54 if (fd_array[i] != 0 && fd_array[i] != fd) 55 { 56 write(fd_array[i], msg, 1024); 57 } 58 } 59 } 60 else { 61 close(fd); 62 fd_array[tc.idx] = 0; 63 break; 64 } 65 usleep(1000); 66 } 67 return nullptr; 68 } 69 int main() { 70 int clientfd; 71 struct sockaddr_in serveraddr, clientaddr; 72 socklen_t clientaddr_len; 73 u_short port = 9999; 74 char buff[1024]; 75 int i; 76 serverfd = socket(AF_INET, SOCK_STREAM, 0); 77 if (serverfd == -1) { 78 printf("socket error\n"); 79 exit(0); 80 } 81 bzero(&serveraddr, sizeof(serveraddr)); 82 serveraddr.sin_family = AF_INET; 83 serveraddr.sin_port = htons(port); 84 serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); 85 if (bind(serverfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) { 86 printf("bind error\n"); 87 exit(0); 88 } 89 inet_ntop(AF_INET, &serveraddr.sin_addr, buff, sizeof(buff)); 90 printf("server ip is %s running on port %d\n", buff, ntohs(serveraddr.sin_port)); 91 92 if ( listen(serverfd, 1) == -1 ) { 93 printf("listen error\n"); 94 exit(0); 95 } 96 signal(SIGINT, sig_act); 97 while(1) { 98 clientaddr_len = sizeof(clientaddr); 99 clientfd = accept(serverfd, (struct sockaddr *)&clientaddr, &clientaddr_len); 100 for (i = 0; i < MAX_NUM; ++i) 101 { 102 if (tid[i] != 0 && fd_array[i] == 0) { 103 pthread_join(tid[i], nullptr); 104 tid[i] = 0; 105 } 106 if (tid[i] == 0) 107 { 108 struct tclient tc; 109 tc.idx = i; 110 tc.addr = clientaddr; 111 fd_array[i] = clientfd; 112 pthread_create(&tid[i], nullptr, foo, static_cast<void*>(&tc)); 113 break; 114 } 115 } 116 if (i == MAX_NUM) { 117 char msg[1024] = "The room is full!"; 118 write(clientfd, msg, 1024); 119 close(clientfd); 120 } 121 } 122 }
值得改进的地方:
1、这个server不是线程安全的:假如线程1在已经完成L54的验证,在L56发送msg之前,某一线程关闭了对应的fd,这样就造成了向未知fd发送数据。可以对fd加个锁。
2、融合https://www.cnblogs.com/Zzz-y/p/9281037.html里的数据库,加入账号密码。