学科分类
目录
Linux编程

多线程并发服务器

考虑到每个进程可打开的文件描述符数量有限,且进程占用资源较多,系统中进程的数量又受到内存大小的限制,为在保证服务器效率的前提下,降低服务器的消耗,可利用多线程机制搭建并发服务器。

多线程并发服务器与多进程并发服务器类似,不同的是,当有请求到达时,服务器进程会创建一个子线程,并使子线程处理客户端请求。

下面通过一个案例,来展示使用多线程并发服务器实现网络通信的方法。

案例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

由以上程序执行结果可知,服务器端进程可以同时处理不止一个客户端请求,多线程并发服务器实现成功。

同进程相比,线程共享进程空间中打开的文件描述符,因此多线程并发服务器中主线程在创建子线程后,无需关闭文件描述符;且线程占用的空间资源大大减少,因此内存对多线程并发服务器的限制也被降低。但相比而言,多线程并发服务器的稳定性较差,因此读者在搭建服务器时,应从需求出发,选择更为合适的服务器。

点击此处
隐藏目录