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