【多線程】聊天室的實現


【目標實現】

模擬一個聊天室,任意一個客戶端窗口可以發送消息,同時也可以接收聊天室內所有人的消息。

 

【服務器端】

 

 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所在的代碼行構成臨界區,因為如果不同的線程同時運行可能會引發錯誤。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM