【多线程】聊天室的实现

【目标实现】

模拟一个聊天室,任意一个客户端窗口可以发送消息,同时也可以接收聊天室内所有人的消息。

 

【服务器端】

 

 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所在的代码行构成临界区,因为如果不同的线程同时运行可能会引发错误。

posted @ 2018-09-22 20:43  LesRoad  阅读(2361)  评论(1编辑  收藏  举报