代码改变世界

Linux TCP server系列(7)-select模式下的prefork server

  Aga.J  阅读(1665)  评论(0编辑  收藏  举报

目标:

  多进程服务器的性能提升。

  前面介绍过一种server模式为每个客户端连接都创建一个子进程,这种方式对server的压力较大,首先创建进程会消耗时间,其次,进程没有办法得到重复利用也会浪费了进程的创建,最后,进程间切换会带来性能上的影响。

  本程序使用prefork思想,预先为server派生多个子进程,方便在需要时可以马上使用,而不需要等待进程创建,同时,进程还可以重复使用。

 

思路:

  程序预先fork多个子进程,用来处理每个客户端socket的信息交互或者请求(决定预先fork多少个子进程是个问题!)一开始每个子进程都阻塞在和父进程的通信通道上,等待父进程的消息通知(处理某个客户请求),然后才执行与客户端socket的交互。

所有accept操作由“父进程”完成接收到客户端连接socket后,将该socket的文件描述符传送给子进程(怎么传送是一个问题!这里使用域socket来作为父子进程之间的通信介质Unix域可以做到让同一台主机上的任何一个进程向其他进程传递打开的文件描述符,进程间用sendmsg在域套接口上发送一个特殊的消息,然后由内核做特殊处理,最后完成将打开的描述字从发送方传递到接收方,同时Unix域套接口也可以作为同一台主机上IPC通信的另一种方式,而且性能速度通常是TCP套接口的两倍【两个好处:速度快,可传递套接字】)。

  同时,父进程还会使用select来监听所有孩子的通信介质,当子进程完成了客户端socket的信息交互请求后,就会通过该通信介质通知父进程,父进程在select捕获到消息后,则将发出该消息的子进程标识为空闲状态。

 

实现:

1)父进程为了获知或者控制子进程,所以需要定义一个结构来维护子进程的信息

  typedef struct

{

  pid_t  child_pid;    //进程ID

  int    child_pipefd;  //父子间的stream pipe(连接父子进程的字节流管道描述字)

  int    child_status;  //状态

  long   child_count;  //已处理的客户数

} Child;

2prefork

  创建一定数量的子进程,同时每个子进程需要开启一条通道以便于和父进程做消息传递。

//initial "server 0.5" element

         nchildren=10;   //假设接收10

         navail=nchildren;

         cptr=(Child*)calloc(nchildren,sizeof(Child));

         for(i=0;i<nchildren;i++)

         {

                   child_make(i,listenfd,addrlen);  

                   //注意 这时候父进程还没有所谓的进程描述表项存在,没有所谓的文件打开描述结构

                   FD_SET(cptr[i].child_pipefd,&allset);

                   maxfd=std::max(maxfd,cptr[i].child_pipefd);

         }

         //initial "server 0.5" element

 

  //child_make实现

pid_t child_make(int i, int listenfd, int addrlen)

{

     int sockfd[2];

     pid_t pid;

 

     void child_main(int,int,int);

 

     socketpair(AF_LOCAL,SOCK_STREAM,0,sockfd);

    

     if ( (pid=fork())>0)

     {

              close(sockfd[1]);

              cptr[i].child_pid=pid;

              cptr[i].child_pipefd=sockfd[0];

              cptr[i].child_status=0;

              return pid;

     }

 

     dup2(sockfd[1],STDERR_FILENO);

     close(sockfd[0]);

     close(sockfd[1]);

     close(listenfd);

     child_main(i,listenfd,addrlen);

}

              注意到,子进程和父进程之间的通信通道是Unixsocket,这也是一种IPC的方式,而且它很适合进程在同一台机器上的情况,注意上述代码中对每个接口都很小心的做了处理(关闭等)。完成创建后,子进程执行child_main阻塞在Unixsocket的信息recv上。(父进程这时候只需要发送新接收的客户端socket fd即可以让子进程马上进行相应的处理)。

         3)父进程监听和消息通知

                   所有子进程创建后,父进程还需要监听和各个子进程的通信fd,因为每个子进程处理完父进程分配给他们的任务后,要告诉父进程,这样父进程就可以标识子进程为可用,从而做到复用子进程。

                   同时父进程当然也会监听listen socketfd,一旦有新连接来,它就会选取一个空闲子进程,然后通过unix域套接字将新连接socket fd作为消息发送给子进程。

        这里的Unix域通信实现参考自网络

                  

复制代码
recv_fd
 1 size_t recv_fd(int fd, void*data, size_t bytes, int*recvfd)
2 {
3 struct msghdr msghdr_recv; /*接收消息接收*/
4 struct iovec iov[1]; /*接收数据的向量*/
5 size_t n;
6 int newfd;
7 union{
8 struct cmsghdr cm;
9 char control[CMSG_SPACE(sizeof(int))];
10 }control_un;
11 struct cmsghdr*pcmsghdr; /*消息头部*/
12 msghdr_recv.msg_control = control_un.control; /*控制消息*/
13 msghdr_recv.msg_controllen = sizeof(control_un.control); /*控制消息的长度*/
14 msghdr_recv.msg_name = NULL; /*消息的名称为空*/
15 msghdr_recv.msg_namelen = 0; /*消息的长度为空*/
16
17 iov[0].iov_base = data; /*向量的数据为传入的数据*/
18 iov[0].iov_len = bytes; /*向量的长度为传入数据的长度*/
19 msghdr_recv.msg_iov = iov; /*消息向量指针*/
20 msghdr_recv.msg_iovlen = 1; /*消息向量的个数为1个*/
21 if((n = recvmsg(fd, &msghdr_recv, 0))<=0) /*接收消息*/
22 return n;
23 if((pcmsghdr = CMSG_FIRSTHDR(&msghdr_recv))!= NULL && /*获得消息的头部*/
24 pcmsghdr->cmsg_len == CMSG_LEN(sizeof(int))) { /*获得消息的长度为int*/
25 if(pcmsghdr->cmsg_level != SOL_SOCKET) /*消息的level应该为SOL_SOCKET*/ printf("control level != SOL_SOCKET\n");
26 if(pcmsghdr->cmsg_type != SCM_RIGHTS) /*消息的类型判断*/
27 printf("control type != SCM_RIGHTS\n");
28 *recvfd =*((int*)CMSG_DATA(pcmsghdr)); /*获得打开文件的描述符*/
29 } else
30 *recvfd = -1;
31 return n; /*返回接收消息的长度*/
32 }
复制代码
复制代码
send_fd
 1 ssize_t send_fd(int fd, void*data, size_t bytes, int sendfd)
2 {
3 struct msghdr msghdr_send; /*发送消息*/
4 struct iovec iov[1]; /*向量*/
5 size_t n; /*大小*/
6 int newfd; /*文件描述符*/ /*方便操作msg的结构*/
7 union{
8 struct cmsghdr cm; /*control msg结构*/
9 char control[CMSG_SPACE(sizeof(int))]; /*字符指针,方便控制*/
10 }control_un;
11 struct cmsghdr*pcmsghdr=NULL; /*控制头部的指针*/
12 msghdr_send.msg_control = control_un.control; /*控制消息*/
13 msghdr_send.msg_controllen = sizeof(control_un.control); /*长度*/
14 pcmsghdr = CMSG_FIRSTHDR(&msghdr_send); /*取得第一个消息头*/
15 pcmsghdr->cmsg_len = CMSG_LEN(sizeof(int)); /*获得长度*/
16 pcmsghdr->cmsg_level = SOL_SOCKET; /*用于控制消息*/
17 pcmsghdr->cmsg_type = SCM_RIGHTS;
18 *((int*)CMSG_DATA(pcmsghdr))= sendfd; /*socket值*/
19 msghdr_send.msg_name = NULL; /*名称*/
20 msghdr_send.msg_namelen = 0; /*名称长度*/
21 iov[0].iov_base = data; /*向量指针*/
22 iov[0].iov_len = bytes; /*数据长度*/
23 msghdr_send.msg_iov = iov; /*填充消息*/
24 msghdr_send.msg_iovlen = 1;
25 return (sendmsg(fd, &msghdr_send, 0)); /*发送消息*/
26 }
复制代码

 

代码:

  

 

 

编辑推荐:
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
· .NET 适配 HarmonyOS 进展
阅读排行:
· 如何给本地部署的DeepSeek投喂数据,让他更懂你
· 超详细,DeepSeek 接入PyCharm实现AI编程!(支持本地部署DeepSeek及官方Dee
· 用 DeepSeek 给对象做个网站,她一定感动坏了
· .NET 8.0 + Linux 香橙派,实现高效的 IoT 数据采集与控制解决方案
· DeepSeek处理自有业务的案例:让AI给你写一份小众编辑器(EverEdit)的语法着色文件
点击右上角即可分享
微信分享提示