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