在网络通信中,服务器通常需要处理多个客户端。由于多个客户端的请求可能会同时到来,服务器端可采用不同的方式来处理。目前最常用的服务器模型为循环服务器模型与并发服务器模型。
循环服务器模型是指服务器端依次处理每个客户端,直到当前客户端的所有请求处理完毕,在处理下一个客户端。此类模型的特点就是简单,但也容易造成除当前客户端以外的其他客户端等待时间过长的情况。
循环服务器的实现其实很简单,通常可以采用循环嵌套的方式来实现。即外层循环依次接收客户端的请求,建立TCP连接。内层循环接收并处理客户端的所有数据。直到客户端关闭连接。如果当前客户端没有处理结束,其他客户端必须一直等待。因此需要特别注意的是循环服务器不能在同一时刻响应多个客户端的请求。
下面将展示TCP的循环服务器的基本功能实现。服务器接收到客户端的数据之后,对数据加以修改,再发送给客户端。循环服务器,服务器端的代码如下所示。
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7
8 #define N 128
9 #define errlog(errmsg) do{perror(errmsg);\
10 printf("---%s---%s---%d---\n",\
11 __FILE__, __func__, __LINE__);\
12 return -1;\
13 }while(0)
14 int main(int argc, const char *argv[])
15 {
16 int sockfd, acceptfd;
17
18 struct sockaddr_in serveraddr, clientaddr;
19 socklen_t addrlen = sizeof(serveraddr);
20 char buf[N] = "";
21
22 bzero(&serveraddr, addrlen);
23 bzero(&clientaddr, addrlen);
24
25 /*提示程序需要命令行传参*/
26 if(argc < 3){
27 fprintf(stderr, "Usage: %s ip port\n", argv[0]);
28 return -1;
29 }
30
31 /*创建套接字*/
32 if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
33 errlog("socket error");
34 }
35
36 /*填充网络信息结构体
37 *inet_addr:将点分十进制地址转换为网络字节序的整型数据
38 *htons:将主机字节序转换为网络字节序
39 *atoi:将数字型字符串转化为整型数据
40 */
41 serveraddr.sin_family = AF_INET;
42 serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
43 serveraddr.sin_port = htons(atoi(argv[2]));
44
45 /*将套接字与服务器网络信息结构体把绑定*/
46 if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) < 0){
47 errlog("bind error");
48 }
49
50 /*将套接字设置为被动监听模式*/
51 if(listen(sockfd, 5) < 0){
52 errlog("listen error");
53 }
54
55 /*循环的方式接收客户端的请求*/
56 while(1){
57 /*可以将后两个参数设置为NULL,
58 *表示不关注客户端的信息,不影响通信
59 */
60 if((acceptfd = accept(sockfd,\
61 (struct sockaddr *)&clientaddr, &addrlen)) < 0){
62 errlog("accept error");
63 }
64
65 printf("ip: %s, port: %d\n",\
66 inet_ntoa(clientaddr.sin_addr),\
67 ntohs(clientaddr.sin_port));
68
69 ssize_t bytes;
70
71 while(1){
72 if((bytes = recv(acceptfd, buf, N, 0)) < 0){
73 errlog("recv error");
74 }
75 else if(bytes == 0){
76 errlog("no data");
77 }
78 else{
79 if(strncmp(buf, "quit", 4) == 0){
80 printf("client quit\n");
81 break;
82 }
83 else{
84 printf("client: %s\n", buf);
85 strcat(buf, "-server");
86
87 if(send(acceptfd, buf, N, 0) < 0){
88 errlog("send error");
89 }
90 }
91 }
92 }
93 }
94 return 0;
95 }
客户端的代码如下所示,一旦连接成功之后,客户端可以持续向服务器发送数据,直到输入“quit”可退出。
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7
8 #define N 128
9 #define errlog(errmsg) do{perror(errmsg);\
10 printf("---%s---%s---%d---\n",\
11 __FILE__, __func__, __LINE__);\
12 return -1;\
13 }while(0)
14 int main(int argc, const char *argv[])
15 {
16 int sockfd;
17 struct sockaddr_in serveraddr;
18 socklen_t addrlen = sizeof(serveraddr);
19 char buf[N] = "";
20
21 /*提示程序需要命令行传参*/
22 if(argc < 3){
23 fprintf(stderr, "Usage: %s ip port\n", argv[0]);
24 return -1;
25 }
26
27 /*创建套接字*/
28 if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
29 errlog("socket error");
30 }
31
32 /*填充网络信息结构体
33 *inet_addr:将点分十进制地址转换为网络字节序的整型数据
34 *htons:将主机字节序转换为网络字节序
35 *atoi:将数字型字符串转化为整型数据
36 */
37 serveraddr.sin_family = AF_INET;
38 serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
39 serveraddr.sin_port = htons(atoi(argv[2]));
40
41 #if 0
42 系统可以随机为客户端指定IP地址和端口号,客户端也可以自己指定
43 struct sockaddr_in clientaddr;
44 clientaddr.sin_family = AF_INET;
45 clientaddr.sin_addr.s_addr = inet_addr(argv[3]);
46 clientaddr.sin_port = htons(atoi(argv[4]));
47
48 if(bind(sockfd, (struct sockaddr *)&clientaddr, addrlen) < 0){
49 errlog("bind error");
50 }
51 #endif
52
53 /*发送客户端连接请求*/
54 if(connect(sockfd, (struct sockaddr *)&serveraddr, addrlen) < 0){
55 errlog("connect error");
56 }
57
58 while(1){
59 fgets(buf, N, stdin);
60 buf[strlen(buf) - 1] = '\0';
61
62 if(send(sockfd, buf, N, 0) < 0){
63 errlog("send error");
64 }
65 else{
66 if(strncmp(buf,"quit", 4) == 0){
67 break;
68 }
69
70 if(recv(sockfd, buf, N, 0) < 0){
71 errlog("recv error");
72 }
73
74 printf("server: %s\n", buf);
75 }
76 }
77
78 return 0;
79 }
先运行服务器,等待客户端的连接。再运行客户端,发送信息,并执行退出。可多次运行客户端,发送数据,之后退出。服务器可以一直循环接收连接,并处理发送数据。
服务器运行结果如下所示,接收两次客户端的连接请求。
linux@Master:~/1000phone/net/tcp_echo$ ./server 10.0.36.199 7777
ip: 10.0.36.199, port: 53383
client: hello
client quit
ip: 10.0.36.199, port: 53384
client: world
client quit
客户端其一运行结果如下所示,发送数据,并接收服务器修改之后的数据。
linux@Master:~/1000phone/net/tcp_echo$ ./client 10.0.36.199 7777
hello
server: hello-server
quit
另一个客户端运行结果如下所示,发送数据,并接收服务器修改之后的数据。
linux@Master:~/1000phone/net/tcp_echo$ ./client 10.0.36.199 7777
world
server: world-server
quit