多线程并发服务器
考虑到每个进程可打开的文件描述符数量有限,且进程占用资源较多,系统中进程的数量又受到内存大小的限制,为在保证服务器效率的前提下,降低服务器的消耗,可利用多线程机制搭建并发服务器。
多线程并发服务器与多进程并发服务器类似,不同的是,当有请求到达时,服务器进程会创建一个子线程,并使子线程处理客户端请求。
下面通过一个案例,来展示使用多线程并发服务器实现网络通信的方法。
案例2:搭建多线程并发服务器,使服务器端可接收多个客户端的数据,并将接收到的数据转为大写,写回客户端;使客户端可向服务器发送数据,并将服务器返回的数据打印到终端。
案例实现如下:
pserver.c //服务器
1 #include <stdio.h>
2 #include <string.h>
3 #include <netinet/in.h>
4 #include <arpa/inet.h>
5 #include <pthread.h>
6 #include "wrap.h"
7 #define MAXLINE 80
8 #define SERV_PORT 8000
9 struct s_info {
10 struct sockaddr_in cliaddr;
11 int connfd;
12 };
13 //请求处理函数
14 void *do_work(void *arg)
15 {
16 int n, i;
17 struct s_info *ts = (struct s_info*)arg;
18 char buf[MAXLINE];
19 char str[INET_ADDRSTRLEN];
20 //使子线程处于分离态,保证子线程资源可被回收
21 pthread_detach(pthread_self());
22 while (1) {
23 n = Read(ts->connfd, buf, MAXLINE);
24 if (n == 0) {
25 printf("the other side has been closed.\n");
26 break;
27 }
28 printf("received from %s at PORT %d\n",
29 inet_ntop(AF_INET,&(*ts).cliaddr.sin_addr, str, sizeof(str)),
30 ntohs((*ts).cliaddr.sin_port));
31 for (i = 0; i < n; i++)
32 buf[i] = toupper(buf[i]);
33 Write(ts->connfd, buf, n);
34 }
35 Close(ts->connfd);
36 }
37 int main(void)
38 {
39 struct sockaddr_in servaddr, cliaddr;
40 socklen_t cliaddr_len;
41 int listenfd, connfd;
42 int i = 0;
43 pthread_t tid;
44 struct s_info ts[383];
45 listenfd = Socket(AF_INET, SOCK_STREAM, 0);
46 bzero(&servaddr, sizeof(servaddr));
47 servaddr.sin_family = AF_INET;
48 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
49 servaddr.sin_port = htons(SERV_PORT);
50 Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
51 Listen(listenfd, 20);
52 printf("Accepting connections ...\n");
53 while (1) {
54 cliaddr_len = sizeof(cliaddr);
55 connfd=Accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);
56 ts[i].cliaddr = cliaddr;
57 ts[i].connfd = connfd;
58 //创建子线程,处理客户端请求
59 pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
60 i++;
61 }
62 return 0;
63 }
同多进程并发服务器相同,服务器中子线程在终止之后,也应被回收,但线程与信号兼容性较差,因此在程序pserver.c的第21行代码,将子线程设置为分离态,使子线程在终止后主动释放资源。
pclient.c //客户端
1 #include <stdio.h>
2 #include <string.h>
3 #include <unistd.h>
4 #include <netinet/in.h>
5 #include "wrap.h"
6 #define MAXLINE 80
7 #define SERV_PORT 8000
8 int main(int argc, char *argv[])
9 {
10 struct sockaddr_in servaddr;
11 char buf[MAXLINE];
12 int sockfd, n;
13 sockfd = Socket(AF_INET, SOCK_STREAM, 0);
14 bzero(&servaddr, sizeof(servaddr));
15 servaddr.sin_family = AF_INET;
16 inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
17 servaddr.sin_port = htons(SERV_PORT);
18 Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
19 while (fgets(buf, MAXLINE, stdin) != NULL) {
20 Write(sockfd, buf, strlen(buf));
21 n = Read(sockfd, buf, MAXLINE);
22 if (n == 0)
23 printf("the other side has been closed.\n");
24 else
25 Write(STDOUT_FILENO, buf, n);
26 }
27 Close(sockfd);
28 return 0;
29 }
分别使用以下语句编译服务器端程序与客户端程序:
gcc pserver.c wrap.c -o server –lpthread
gcc pclient.c wrap.c -o client –lpthread
程序编译完成后,先执行服务器程序,打开服务器,之后在一个终端运行客户端程序(记为客户端1),并在该终端中输入客户端需要发送的数据,此时客户端与服务器端中打印的信息分别如下:
客户端1:
hello
HELLO
服务器端:
Accepting connections ...
received from 127.0.0.1 at PORT 60315
打开新的终端,在该终端中再次运行客户端程序(记为客户端2),并输入要发送的数据,此时终端2与服务器端中打印的信息分别如下:
客户端2:
itcast
ITCAST
服务器端:
Accepting connections ...
received from 127.0.0.1 at PORT 37694
received from 127.0.0.1 at PORT 37695
由以上程序执行结果可知,服务器端进程可以同时处理不止一个客户端请求,多线程并发服务器实现成功。
同进程相比,线程共享进程空间中打开的文件描述符,因此多线程并发服务器中主线程在创建子线程后,无需关闭文件描述符;且线程占用的空间资源大大减少,因此内存对多线程并发服务器的限制也被降低。但相比而言,多线程并发服务器的稳定性较差,因此读者在搭建服务器时,应从需求出发,选择更为合适的服务器。