博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
8-多进程并发型服务器
阅读量:2044 次
发布时间:2019-04-28

本文共 4161 字,大约阅读时间需要 13 分钟。

1. 多进程并发服务器

  前面的学习基于TCP的客户端和服务端通信都是一个服务器处理一个客户端的连接请求,只有在处理完这个客户端时才会去处理下一个客户端的请求,这种服务器我们称之为迭代型服务器。

  假如现在有多个客户端去请求访问一个服务器,会导致这个服务器的处理速度变得很慢不说,后面还有很多客户端正在等待处理请求,这时服务器的压力会很大,显然迭代型服务器已经满足不了需求了,于是就有了并发型服务器。

这里写图片描述
图1-多进程并发服务器

  并发型服务器对于每一个客户端请求,服务器的父进程就fork创建一个子进程来处理客户端的请求,子进程就可以通过accept返回的新的文件描述符跟客户端进行数据通信,且这些子进程都能独立运行,因此可以同时处理多个客户端请求。

  然后父进程就可以继续调用accept取出一个客户端连接,调用fork创建一个服务器子进程处理客户端的请求,从这个过程可以看出,服务器的父进程主要的工作就是处理客户端的请求,即为每个客户端创建一个子进程,而具体的数据交互由子进程来完成。

2. 多进程并发服务器的细节

关于设计并发服务器有以下几个细节需要注意:

  1. 防止文件描述符耗尽
  2. 父进程注册SIGCHILD信号回收子进程

2.1 防止文件描述符耗尽

  父进程在调用fork的时候,子进程会继承父进程的accept函数返回的cfd文件描述符和socket函数创建的lfd文件描述符。

  然后子进程就可以通过cfd文件描述符与客户端进程通信,而子进程继承的lfd文件描述符对子进程来说没有太大的作用,那么子进程需要close(lfd),以防止文件描述符耗尽。父进程只需要处理来自客户端的连接,并不需要与客户端进行数据交互,所以父进程也需要close(cfd)。

  实际上在fork创建子进程后,子进程和父进程是共享listenfd和connfd的,这就使得listenfd和connfd两个套接字的引用计数变成2了。如果父进程不close(cfd)的话,此时cfd的引用计数是2,当子进程close(cfd)时只会让cfd的引用计数减1,而不会关闭与客户端的tcp连接。为了防止这种情况,父进程同样也需要close(cfd)。

2.2 注册信号回收子进程

  2. 当客户端一退出的时候同时也关闭了socket套接字,子进程调用read读取客户端会返回0,紧接着子进程应该close(cfd),然后子进程调用exit退出。

  但是子进程一退出就会变成僵尸进程,需要父进程对子进程回收(父进程除了要负责回收子进程,还要处理客户端的连接,那么父进程应该调用waitpid函数以非阻塞方式回收子进程),另外在学习进程和信号时我们知道,子进程在退出时会发送SIGCHLD信号,该信号的作用就是用来通知父进程对子进程进行回收,因此父进程可以捕捉并注册SIGCHLD信号函数的方式来回收子进程。

3. 并发服务器示例

服务端程序:

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERV_PORT 10001注册SIGCHLD信号回收子进程void wait_child(int signo){ //非阻塞方式回收子进程 while (waitpid(0, NULL, WNOHANG) > 0);}int main(void){ pid_t pid; int lfd, cfd; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; char buf[BUFSIZ], clie_IP[BUFSIZ]; int n, i; lfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(SERV_PORT); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); listen(lfd, 128); while (1) { clie_addr_len = sizeof(clie_addr); cfd = accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len); printf("client IP:%s, port:%d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)), ntohs(clie_addr.sin_port)); //创建子进程 pid = fork(); if (pid < 0) { perror("fork error"); close(cfd); exit(1); } else if (pid == 0) { close(lfd); break; } else { //父进程 close(cfd); //捕捉SIGCHLD信号 signal(SIGCHLD, wait_child); } } //子进程 if (pid == 0) { while (1) { n = read(cfd, buf, sizeof(buf)); if (n == 0) { //如果read返回0,说明对端已关闭,打印退出客户端信息 printf("client IP:%s, port:%d ----- exit\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)) , ntohs(clie_addr.sin_port)); close(cfd); exit(0); //子进程退出 } else if (n == -1) { perror("read error"); exit(1); } else { for (i = 0; i < n; i++){ buf[i] = toupper(buf[i]); } write(cfd, buf, n); } } } close(lfd); return 0;}

客户端程序:

#include 
#include
#include
#include
#include
#define MAXLINE 1024#define SERV_PORT 10001int main(void){ struct sockaddr_in servaddr; //缓冲区 char buf[MAXLINE]; int sockfd, n; sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); //连接服务器 connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //从标准输入读取数据到缓冲区 while (fgets(buf, MAXLINE, stdin) != NULL) { write(sockfd, buf, strlen(buf)); n = read(sockfd, buf, MAXLINE); //判断对端是否被关闭 if (n == 0) { printf("the other side has been closed\n"); break; } else{ //把数据输出到标准输出 write(STDOUT_FILENO, buf, n); } } close(sockfd); return 0;}

程序运行结果:
这里写图片描述

  服务端对每一个连接的客户端都会创建一个子进程并进行处理请求,并将请求处理的结构返回给客户端,客户端退出时,服务端打印了退出的客户端信息,此时服务端依然在accept处等待处理其他客户端发起连接

4. 总结

多进程并发服务器和单进程服务器的区别:

  对于单进程的服务器来说,一次性只能处理一个客户端请求,只有在处理完这个客户端然后断开连接时,才能继续处理下一个客户端的请求。但是如果没有客户端请求时,单进程服务端则会一直等待阻塞在accept处,其实阻塞在accept处倒还好,如果阻塞在read调用处将会导致服务端无法给其他客户端提供服务,这是很可怕的事情。而在多进程并发服务器中父进程只负责处理来自客户端的连接请求,由子进程负责处理客户端的数据请求,所以多进程并发服务器中不会出现这个问题。

你可能感兴趣的文章
LoadRunner中获取当前系统时间方法
查看>>
Python几种并发实现方案的性能比较
查看>>
【实战】10.10.1.9考试系统代码完成一次答题代码备份
查看>>
[Jmeter]jmeter之脚本录制与回放,优化(windows下的jmeter)
查看>>
【编码备份】1.9从Excel中导入用户名进行测试,用户一次进入系统进行答题测试。...
查看>>
Jmeter之正则
查看>>
【JMeter】1.9上考试jmeter测试调试
查看>>
【虫师】【selenium】参数化
查看>>
【JMeter】如何用JMeter进行压力测试
查看>>
【Python练习】文件引用用户名密码登录系统
查看>>
学习网站汇总
查看>>
【Python】用Python打开csv和xml文件
查看>>
【Python】Python基础
查看>>
【Loadrunner】性能测试报告实战
查看>>
【面试】一份自我介绍模板
查看>>
【自动化测试】自动化测试需要了解的的一些事情。
查看>>
【selenium】selenium ide的安装过程
查看>>
【手机自动化测试】monkey测试
查看>>
【英语】软件开发常用英语词汇
查看>>
Fiddler 抓包工具总结
查看>>