linux socket 聊天室

linux socket 聊天室,本来这并不是我自己要做的,只是为了帮别人完成作业。刚好最近这段时间的课是关于socket编程,何不拿来练练手?

基于socket的聊天室早在开学初就有做过类似的,只不过当时用的java来实现,而且因为没有正式学过socket,代码只是搬用别人的,并没有深入理解。

单用户-服务的对话还是很好实现的,即使是多用户-服务,只要不是连续服务,服务端还是可以通过轮询的方式服务多个用户。问题就在于,常用socket I/O函数大都是阻塞的,这就意味着,单个线程只能服务于一个用户。于是自然而然的想到用多线程,然而多线程并不是最佳的解决方案,毕竟如果频繁的创建和销毁线程会造成一定的浪费。而利用select的多路复用,就能更好的解决。

客户端实现

客户端是很好实现的,只有2个I/O要通信,server socket和stdin,并且是fd值固定的,maxfdp直接取socket+1就行了。

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <stdbool.h>
 5 #include <unistd.h>
 6 #include <sys/socket.h>
 7 #include <arpa/inet.h>
 8 
 9 #define BUF_SIZE 256 // 缓冲区长度
10 #define STDIN 0 // stdinfd
11 #define STDOUT 1 // stdoutfd
12 #define INVALID -1
13 
14 /** 负责 socket 的初始化,连接到服务器 */
15 int socket_setup(const char *serv_ip, int serv_port)
16 {
17     int rtn, sockfd = socket(AF_INET, SOCK_STREAM, 0);
18     struct sockaddr_in sockaddr;
19 
20     bzero(&sockaddr, sizeof(sockaddr));
21     sockaddr.sin_family = AF_INET;
22     sockaddr.sin_port = htons(serv_port);
23     inet_pton(AF_INET, serv_ip, &sockaddr.sin_addr);
24 
25     rtn = connect(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
26 
27     if (rtn == INVALID)
28     {
29         puts("Connection failure");
30         exit(1);
31     }
32     else
33     {
34         puts("Connection successful");
35         return sockfd;
36     }
37 }
38 
39 int main(int argc, const char *argv[])
40 {
41     int i, read_size, sockfd = socket_setup(argv[1], atoi(argv[2]));
42     char buffer[BUF_SIZE];
43     fd_set fdset;
44 
45     while (true)
46     {
47         FD_ZERO(&fdset);
48         FD_SET(STDIN, &fdset);
49         FD_SET(sockfd, &fdset);
50         select(sockfd + 1, &fdset, NULL, NULL, NULL);
51 
52         /** socket -> 标准输出 */
53         if (FD_ISSET(sockfd, &fdset))
54         {
55             read_size = read(sockfd, buffer, BUF_SIZE);
56             write(STDOUT, buffer, read_size);
57 
58             if (read_size == 0)
59             {
60                 puts("Server close");
61                 exit(1);
62             }
63         }
64 
65         /** 标准输入 -> socket */
66         if (FD_ISSET(STDIN, &fdset))
67         {
68             read_size = read(STDIN, buffer, BUF_SIZE);
69             write(sockfd, buffer, read_size);
70         }
71     }
72 
73     return 0;
74 }

服务端实现

这个聊天室的实现难点是服务端,要能支持多用户聊天。

我的实现方法是,用一个结构体数组clients保存已连接的客户信息,使用遍历数组的方式广播消息,一般来说都能想到这点,重点是利用select实现多路复用。

因为客户socket是动态的,必须小心处理客户的连接/断开,当有用户连接时保存其socket,断开连接后将socket值为无效。

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <stdbool.h>
  5 #include <unistd.h>
  6 #include <time.h>
  7 #include <sys/socket.h>
  8 #include <arpa/inet.h>
  9 
 10 #define TIME_SIZE 16 // 表示时间的字符串长度
 11 #define IP_SIZE 16 // IP 字符串长度
 12 #define BUF_SIZE 256 // 缓冲区大小
 13 #define CLIENT_SIZE 8 // 允许的客户端数量
 14 #define BACKLOG CLIENT_SIZE // listen 队列长度,等于允许的客户端数量
 15 #define INVALID -1
 16 
 17 /** 保存客户端连接信息结构体 */
 18 struct CLIENT {
 19     int clientfd;
 20     struct sockaddr_in sockaddr;
 21     char ip[IP_SIZE];
 22     int port;
 23 } clients[CLIENT_SIZE];
 24 
 25 /** 初始化客户端列表 */
 26 void init_clients(void)
 27 {
 28     int i;
 29 
 30     for (i = 0; i< CLIENT_SIZE; i++)
 31         clients[i].clientfd = INVALID;
 32 }
 33 
 34 /** 向每一个已连接的用户广播消息 */
 35 void broadcast(char *msg)
 36 {
 37     int i;
 38 
 39     for (i = 0; i< CLIENT_SIZE; i++)
 40         if (clients[i].clientfd != INVALID)
 41             write(clients[i].clientfd, msg, strlen(msg));
 42 }
 43 
 44 /** 格式化要发送的消息 */
 45 void strfmsg(int i, char *buffer, const char *msg)
 46 {
 47     char curtime[TIME_SIZE];
 48     time_t curtime_t;
 49     struct tm *timeinfo;
 50 
 51     time(&curtime_t);
 52     timeinfo = localtime(&curtime_t);
 53     strftime(curtime, TIME_SIZE, "%X", timeinfo);
 54 
 55     sprintf(
 56         buffer,
 57         "<%s %s:%d> %s",
 58         curtime,
 59         clients[i].ip,
 60         clients[i].port,
 61         msg);
 62 }
 63 
 64 /** 新连接处理 */
 65 void accept_connect(int listenfd)
 66 {
 67     int connectfd, i;
 68     char buffer[BUF_SIZE];
 69     struct sockaddr_in clientaddr;
 70     socklen_t connectlen = sizeof(struct sockaddr_in);
 71 
 72     connectfd = accept(
 73         listenfd,
 74         (struct sockaddr *)&clientaddr,
 75         &connectlen);
 76 
 77     /** 记录连接者信息 */
 78     for (i = 0; i < CLIENT_SIZE; i++)
 79     {
 80         if (clients[i].clientfd == INVALID)
 81         {
 82             clients[i].clientfd = connectfd;
 83             memcpy(&clients[i].sockaddr, &clientaddr, connectlen);
 84             clients[i].port = ntohs(clients[i].sockaddr.sin_port);
 85             inet_ntop(
 86                 AF_INET,
 87                 &clients[i].sockaddr.sin_addr,
 88                 clients[i].ip,
 89                 IP_SIZE);
 90 
 91             strfmsg(i, buffer, "login\n");
 92             printf("%s", buffer);
 93             broadcast(buffer);
 94 
 95             break;
 96         }
 97     }
 98 
 99     /** 连接数超出 */
100     if (i == CLIENT_SIZE)
101     {
102         strcpy(buffer, "Out of Number\n");
103         write(connectfd, buffer, strlen(buffer));
104         close(connectfd);
105     }
106 }
107 
108 /** 客户端消息处理 */
109 void chat(fd_set fdset)
110 {
111     int sockfd, read_size, i;
112     char read_buf[BUF_SIZE], send_buf[BUF_SIZE];
113 
114     for (i = 0; i < CLIENT_SIZE; i++)
115     {
116         sockfd = clients[i].clientfd;
117 
118         if (sockfd != INVALID && FD_ISSET(sockfd, &fdset))
119         {
120             read_size = read(sockfd, read_buf, BUF_SIZE - 1);
121 
122             if (read_size == 0)
123             {
124                 /** 失去连接 */
125                 close(sockfd);
126                 clients[i].clientfd = INVALID;
127 
128                 strfmsg(i, send_buf, "logout\n");
129                 printf("%s", send_buf);
130                 broadcast(send_buf);
131 
132                 continue;
133             }
134             else
135             {
136                 read_buf[read_size] = '\0';
137                 strfmsg(i, send_buf, read_buf);
138                 printf("%s", send_buf);
139                 broadcast(send_buf);
140             }
141         }
142     }
143 }
144 
145 /** 负责 socket 初始化,绑定监听端口 */
146 int socket_setup(int port)
147 {
148     int rtn, listenfd = socket(AF_INET, SOCK_STREAM, 0);
149     struct sockaddr_in sockaddr;
150 
151     bzero(&sockaddr, sizeof(sockaddr));
152     sockaddr.sin_family = AF_INET;
153     sockaddr.sin_port = htons(port);
154     sockaddr.sin_addr.s_addr= htonl(INADDR_ANY);
155 
156     rtn = bind(listenfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
157 
158     if (rtn == INVALID)
159     {
160         puts("Bind failure");
161         exit(1);
162     }
163 
164     if (listen(listenfd, BACKLOG) == INVALID)
165     {
166         puts("Listen failure");
167         exit(1);
168     }
169 
170     puts("Service startup");
171     return listenfd;
172 }
173 
174 int main(int argc, const char *argv[])
175 {
176     int maxfdp, i, listenfd = socket_setup(atoi(argv[1]));
177     fd_set fdset;
178 
179     init_clients();
180 
181     while (true)
182     {
183         FD_ZERO(&fdset);
184         FD_SET(listenfd, &fdset);
185         maxfdp = listenfd;
186 
187         /** 将可用的客户端 socket 加入 fdset,并计算 maxfdp */
188         for (i = 0; i < CLIENT_SIZE; i++)
189         {
190             if (clients[i].clientfd != INVALID)
191             {
192                 FD_SET(clients[i].clientfd, &fdset);
193 
194                 if (clients[i].clientfd > maxfdp)
195                     maxfdp = clients[i].clientfd;
196             }
197         }
198 
199         select(maxfdp + 1, &fdset, NULL, NULL, NULL);
200 
201         if (FD_ISSET(listenfd, &fdset))
202             accept_connect(listenfd);
203 
204         chat(fdset);
205     }
206 
207     return 0;
208 }
posted @ 2012-11-18 00:45  7c00  阅读(931)  评论(0编辑  收藏  举报