【多线程】聊天室的实现
【目标实现】
模拟一个聊天室,任意一个客户端窗口可以发送消息,同时也可以接收聊天室内所有人的消息。
【服务器端】
1 #include <stdio.h> 2 #include <cstring> 3 #include <algorithm> 4 #include <arpa/inet.h> 5 #include <pthread.h> 6 #include <unistd.h> 7 using namespace std; 8 int cli_socks[100], num = 0, judge = 0; 9 pthread_mutex_t mutex; 10 void *t_main(void *arg) 11 { 12 char str[100]; 13 int sock = *(int *)arg; 14 while(1) 15 { 16 int len = read(sock, str, 100); 17 if(!len) break; 18 str[len] = 0; 19 pthread_mutex_lock(&mutex); 20 //向所有客户端发送消息 21 for(int i = 1; i <= num; i++) 22 { 23 write(cli_socks[i], str, sizeof(str)); 24 } 25 pthread_mutex_unlock(&mutex); 26 } 27 int i; 28 //线程同步 29 pthread_mutex_lock(&mutex); 30 for(i = 1; i <= num; i++) 31 { 32 if(cli_socks[i] == sock) 33 break; 34 } 35 while(i < num) 36 { 37 cli_socks[i] = cli_socks[i+1]; 38 i++; 39 } 40 num--; 41 pthread_mutex_unlock(&mutex); 42 printf("close connect is %d\n", sock); 43 close(sock); 44 if(!num && judge) exit(0); 45 } 46 int main(int argc, char ** argv) 47 { 48 int ser_sock, cli_sock; 49 sockaddr_in ser_addr, cli_addr; 50 ser_sock = socket(PF_INET, SOCK_STREAM, 0);//创建套接字 51 if(ser_sock == -1) 52 puts("socket error"); 53 54 //地址再分配 55 int opt = 1; 56 setsockopt(ser_sock, SOL_SOCKET, SO_REUSEADDR, &opt, 4); 57 58 memset(&ser_addr, 0, sizeof(ser_addr)); 59 ser_addr.sin_family = AF_INET; 60 ser_addr.sin_addr.s_addr = htonl(INADDR_ANY); 61 ser_addr.sin_port = htons(atoi("9190")); 62 //分配地址信息 63 bind(ser_sock, (sockaddr *)&ser_addr, sizeof(ser_addr)); 64 //转化可接受连接状态 65 listen(ser_sock, 5); 66 67 pthread_mutex_init(&mutex, NULL);//创建互斥量 68 pthread_t id; 69 puts("等待进入..."); 70 while(1) 71 { 72 socklen_t cli_len = sizeof(cli_addr); 73 //请求受理 74 cli_sock = accept(ser_sock, (sockaddr *)&cli_addr, &cli_len); 75 judge = 1; 76 //利用互斥量锁住临界区 77 pthread_mutex_lock(&mutex); 78 cli_socks[++num] = cli_sock; 79 pthread_mutex_unlock(&mutex); 80 81 pthread_create(&id, NULL, t_main, (void *)&cli_sock);//创建线程 82 pthread_detach(id);//线程运行结束自动释放所有资源 83 printf("connect is %d\n", cli_sock); 84 } 85 pthread_mutex_destroy(&mutex);//销毁互斥量 86 close(ser_sock); 87 return 0; 88 }
【客户端】
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <cstring> 4 #include <algorithm> 5 #include <arpa/inet.h> 6 #include <pthread.h> 7 #include <iostream> 8 using namespace std; 9 char name[100], na[100]; 10 void *wr(void *arg) 11 { 12 char s[100], mes[100]; 13 int sock = *(int *)arg; 14 int x = sizeof(s); 15 while(1) 16 { 17 fgets(s, sizeof(s), stdin); 18 s[strlen(s) - 1] = 0; 19 if(!strcmp(s, "q")) break; 20 sprintf(mes, "%s %s", name, s); 21 write(sock, mes, strlen(mes)); 22 } 23 sprintf(mes, "【系统消息】%s退出聊天室", na); 24 write(sock, mes, strlen(mes)); 25 close(sock); 26 exit(0); 27 } 28 void *rd(void *arg) 29 { 30 int sock = *(int *)arg; 31 char s[100]; 32 while(1) 33 { 34 int len = read(sock, s, 100); 35 s[len] = 0; 36 puts(s); 37 } 38 } 39 int main(int argc, char **argv) 40 { 41 puts("请输入用户名!"); 42 char mes[50]; 43 cin >> na; 44 getchar();//防止fgets读回车 45 int sock = socket(PF_INET, SOCK_STREAM, 0);//创建套接字 46 47 sockaddr_in addr; 48 memset(&addr, 0, sizeof(addr)); 49 addr.sin_family = AF_INET; 50 addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 51 addr.sin_port = htons(atoi("9190")); 52 sprintf(name, "[%s]", na); 53 connect(sock, (sockaddr *)&addr, sizeof(addr));//请求连接 54 sprintf(mes, "【系统消息】%s进入聊天室", na); 55 write(sock, mes, strlen(mes)); 56 57 pthread_t id_rd, id_wr; 58 //创建读写线程 59 pthread_create(&id_wr, NULL, wr, (void *)&sock); 60 pthread_create(&id_rd, NULL, rd, (void *)&sock); 61 62 pthread_join(id_wr, NULL);//等待写线程终止 63 //pthread_join(id_rd, NULL); 64 close(sock); 65 return 0; 66 }
【效果截图】
【发现问题】
1.exit和return的区别:传送门
2.linux用gets会出现警告,由于他没有指定输入字符的大小,如果输入字符大于定义的数组长度的时候,那么就会发生内存越界问题。 而用fgets函数则可以根据定义数组的长度自动截断字符,而消除一些安全隐患。 但是fgets会读入最后的回车。
3.客户端代码的21行是把数据送到输出缓冲,34行是从输入缓冲读取,不用担心两个会同时进行,因为并不在一个通道上。
4.全局变量num和cli_socks所在的代码行构成临界区,因为如果不同的线程同时运行可能会引发错误。