为了提高服务器的并发处理能力,在这里又引入了并发服务器的模型。并发服务器解决了循环服务器的缺陷,即可以在同一时刻响应多个客户端的请求。其基本设计思想是在服务器端采用多任务机制(即多进程或多线程),分别为每一个客户端创建一个任务处理。也可以使用select函数实现并发服务器。
首先介绍使用父子进程实现并发服务器(多线程与之类似),具体设计细节为由子进程来处理客户端的消息,而父进程接收客户端的连接请求,但不处理具体消息。也就是说,服务器端父进程一旦接收到客户端的连接请求,便建立好连接并创建新的子进程。这意味着每个客户端在服务器端有一个专门的子进程为其服务。
根据流程图的展示的过程,其服务器端编程的示例代码如下所示。
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/socket.h>
4 #include <string.h>
5 #include <netinet/in.h>
6 #include <arpa/inet.h>
7 #include <stdlib.h>
8 #include <sys/wait.h>
9 #include <unistd.h>
10 #include <signal.h>
11
12 #define N 128
13 #define errlog(errmsg) do{perror(errmsg);\
14 printf("---%s---%s---%d---\n",\
15 __FILE__, __func__, __LINE__);\
16 return -1;\
17 }while(0)
18 void handler(int sig){
19 /*通过信号使父进程执行等待回收子进程的资源的操作*/
20 wait(NULL);
21 }
22 int main(int argc, const char *argv[])
23 {
24 int sockfd, acceptfd;
25
26 struct sockaddr_in serveraddr, clientaddr;
27 socklen_t addrlen = sizeof(serveraddr);
28 char buf[N] = "";
29 pid_t pid;
30
31 bzero(&serveraddr, addrlen);
32 bzero(&clientaddr, addrlen);
33
34 /*提示程序需要命令行传参*/
35 if(argc < 3){
36 fprintf(stderr, "Usage: %s ip port\n", argv[0]);
37 return -1;
38 }
39
40 /*创建套接字*/
41 if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
42 errlog("socket error");
43 }
44
45 /*填充网络信息结构体
46 *inet_addr:将点分十进制地址转换为网络字节序的整型数据
47 *htons:将主机字节序转换为网络字节序
48 *atoi:将数字型字符串转化为整型数据
49 */
50 serveraddr.sin_family = AF_INET;
51 serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
52 serveraddr.sin_port = htons(atoi(argv[2]));
53
54 /*将套接字与服务器网络信息结构体把绑定*/
55 if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) < 0){
56 errlog("bind error");
57 }
58
59 /*将套接字设置为被动监听模式*/
60 if(listen(sockfd, 5) < 0){
61 errlog("listen error");
62 }
63
64 /*注册信号*/
65 signal(SIGUSR1, handler);
66
67 while(1){
68 /*阻塞等待客户端的连接请求*/
69 if((acceptfd = accept(sockfd,\
70 (struct sockaddr *)&clientaddr, &addrlen)) < 0){
71 errlog("accept error");
72 }
73
74 printf("ip: %s, port: %d\n",\
75 inet_ntoa(clientaddr.sin_addr),\
76 ntohs(clientaddr.sin_port));
77
78 pid = fork();
79
80 if(pid < 0){
81 errlog("fork error");
82 }
83 else if(pid == 0){
84 /*子进程专门负责处理客户端的消息,不负责客户端的连接*/
85
86 /*释放资源,子进程不需要接收客户端的连接*/
87 close(sockfd);
88 while(1){
89 ssize_t bytes;
90 if((bytes = recv(acceptfd, buf, N, 0)) < 0){
91 errlog("recv error");
92 }
93 else if(bytes == 0){
94 errlog("no data\n");
95 }
96 else{
97 if(strncmp(buf, "quit", 4) == 0){
98 printf("client quit\n");
99 sleep(1);
100 break;
101 }
102 else{
103 printf("client: %s\n", buf);
104 strcat(buf, "-server");
105
106 if(send(acceptfd, buf, N, 0) < 0){
107 errlog("send error");
108 }
109 }
110 }
111 }
112 kill(getppid(), SIGUSR1);
113 exit(0);
114 }
115 else{
116 /*
117 *父进程的执行代码段释放资源,不需要收发信息
118 *关闭收发信息的文件描述符
119 */
120 close(acceptfd);
121 }
122 }
123 return 0;
124 }
客户端的代码如下所示。
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/socket.h>
4 #include <string.h>
5 #include <netinet/in.h>
6 #include <arpa/inet.h>
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_concurrent$ ./server 10.0.36.199 7777
ip: 10.0.36.199, port: 53385
ip: 10.0.36.199, port: 53386
client: hello
client: world
client quit
client quit
客户端其一的运行结果如下所示。发送数据,并接收服务器端发送修改之后的数据。
linux@Master:~/1000phone/net/tcp_concurrent$ ./client 10.0.36.199 7777
hello
server: hello-server
quit
另一个客户端的运行结果如下所示。发送数据,并接收服务器端发送修改之后的数据。
linux@Master:~/1000phone/net/tcp_concurrent$ ./client 10.0.36.199 7777
world
server: world-server
quit
select实现且看下周分享