9. 王道_进程池和线程池

1 进程池和线程池的设计思路

服务端框架

  • 基于线程/进程的 apache在这里插入图片描述

  • 事件驱动模型(适合业务业务逻辑简单性能高的) nginx在这里插入图片描述

  • 事件驱动模型适用于多核机器:进程池

    • 有多种设计方法·
    • 我们采用 : 在这里插入图片描述
    • 我们的设计: 当有多个连接,池子满时,进程池直接close,线程池排队

如何写一个大项目

1. 写文件目录

  • 写好文件目录:编译,连接关系,写makefile在这里插入图片描述在这里插入图片描述

  • client : makfiel

SRCS:=$(wildcard *.c)
EXES:=$(patsubst %.c,%,$(SRCS))
CC:=gcc
all:$(EXES)
%:%.c
        $(CC) $^ -o $@ -g -lpthread
clean:
        $(RM) $(EXES)
rebuild:clean all
  • server: makfie
SRCS:=$(wildcard *.c)
OBJS:=$(patsubst %.c,%.o,$(SRCS))
CC:=gcc
server:$(OBJS)
        $(CC) $^ -o $@ -lpthread
%.o:%.c
        $(CC) -c $^ -o $@  -g
clean:
        $(RM) $(OBJS) server
rebuild:clean all

2. 设计数据结构

  • .h文件

3. 设计相关的函数

1.1 设计的背景

在这里插入图片描述

1.2 进程池的整体结构

在这里插入图片描述

  • 进程的个数
  • 根据cpu的核心数,在1倍-2倍之间实测在这里插入图片描述

2 进程池的实现

我们以一个文件下载的应用为例子来介绍进程池结构:客户端可以向服务端建立连接,随后将服务端中存储的文件通过网络传输发送到客户端,其中一个服务端可以同时处理多个客户端连接的,彼此之间互不干扰。

2.1 父子进程创建

在这里插入图片描述
在这里插入图片描述

2.2 父进程处理网络连接

在这里插入图片描述
在这里插入图片描述

2.3 本地套接字

在这里插入图片描述

2.4 父子进程共享文件描述符

在这里插入图片描述
在这里插入图片描述

  • 父子进程之间只是单纯的传递文件描述符是没用的
    在这里插入图片描述
#include <54func.h>

int main()
{
   
   
    int fds[2];
    pipe(fds);
    if(fork())
    {
   
   
        close(fds[0]);
        int fdfile = open("file1",O_RDWR);
        printf("parent fdfile = %d\n",fdfile);
        write(fdfile,"hello",5);
        write(fds[1],&fdfile,sizeof(int));

        wait(NULL);
    }
    else
    {
   
   
        close(fds[1]);
        int fdfile;
        read(fds[0],&fdfile,sizeof(int));
        printf("Son fdile = %d\n",fdfile);
        ssize_t sret = write(fdfile,"world",5);
        printf("son sret = %ld\n",sret);
    }
    return 0;
}
  • 发送一些内核的高级的数据结构,read和write不能满足要求,需要使用sendmsg和recvmsg。这两个是高级版本的read和write,除了可以发送数据,也可以发送内核的高级数据结构,比如文件对象

在这里插入图片描述

在这里插入图片描述

  • 之前的send和recv都是要发送连续的数据
    在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


#include <54func.h>

int sendfd(int sockfd,int fdtosend)
{
   
   
    struct msghdr hdr;
    bzero(&hdr,sizeof(hdr));
    // 1. 正文部分:无论正文部分是否有用,必须要有正文
    char buf[] = "hello";  // 此外的数据
    struct iovec vec[1];// 数组记录离散区域
    vec[0].iov_base = buf; // 第一个离散碎片的首地址
    vec[0].iov_len = 5;
    hdr.msg_iov = vec;     // 将离散区域的信息放入hdr
    hdr.msg_iovlen = 1;
    // 2. 控制部分的字段
    struct cmsghdr  * pcmsg = (struct cmsghdr *)malloc(CMSG_LEN(sizeof(int))); // data:fd(int)
    pcmsg->cmsg_len = CMSG_LEN(sizeof(int));
    pcmsg->cmsg_level = SOL_SOCKET;
    pcmsg->cmsg_type = SCM_RIGHTS;
    // 通过pcmsg得到data首地址,强转成int*再解引用;
    *(int*)CMSG_DATA(pcmsg) = fdtosend;
    hdr.msg_control = pcmsg; // 将控制字段的信息放入hdr中
    hdr.msg_controllen = CMSG_LEN(sizeof(int));
    int ret = sendmsg(sockfd,&hdr,0);
    ERROR_CHECK(ret,-1,"sendmsg");
}

int recvfd(int sockfd,int * pfdtorecv)
{
   
   
    // 接受和发送的区别,接收方不知道buf的内容和pcmsg->data的内容
    struct msghdr hdr;
    bzero(&hdr,sizeof(hdr));
    // 1. 正文部分:无论正文部分是否有用,必须要有正文
    char buf[6] = {
   
   0};  // 此外的数据
    struct iovec vec[1];// 数组记录离散区域
    vec[0].iov_base = buf; // 第一个离散碎片的首地址
    vec[0].iov_len = 5;
    hdr.msg_iov = vec;     // 将离散区域的信息放入hdr
    hdr.msg_iovlen = 1;
    // 2. 控制部分的字段
    struct cmsghdr  * pcmsg = (struct cmsghdr *)malloc(CMSG_LEN(sizeof(int))); // data:fd(int)
    pcmsg->cmsg_len = CMSG_LEN(sizeof(int));
    pcmsg->cmsg_level = SOL_SOCKET;
    pcmsg->cmsg_type = SCM_RIGHTS;
    // 通过pcmsg得到data首地址,强转成int*再解引用;
    hdr.msg_control = pcmsg; // 将控制字段的信息放入hdr中
    hdr.msg_controllen = CMSG_LEN(sizeof(int));
    int ret = recvmsg(sockfd,&hdr,0);
    ERROR_CHECK(ret,-1,"sendmsg");
    printf("buf = %s,fd = %d\n",buf,*(int*)CMSG_DATA(pcmsg));
    *pfdtorecv = *(int*)CMSG_DATA(pcmsg);
}

int main()
{
   
   
    int fds[2];
    //pipe(fds);
    socketpair(AF_LOCAL,SOCK_STREAM,0,fds);


    if(fork())
    {
   
   
        close(fds[0]);
        int fdfile = open("file1",O_RDWR);
        printf("parent fdfile = %d\n",fdfile);
        write(fdfile,"hello",5);
        sendfd(fds[1],fdfile);

        wait(NULL);
    }
    else
    {
   
   
        // 子进程
        close(fds[1]);
        int fdfile;
        recvfd(fds[0],&fdfile);
        printf("Son fdile = %d\n",fdfile);
        ssize_t sret = write(fdfile,"world",5);
        printf("son sret = %ld\n",sret);
    }
    return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

f

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • head.h
#ifndef __HEAD_H__
#define __HEAD_H__
#include<54func.h>
enum{
   
   
    FREE,
    BUSY
};

typedef struct workerdata_s
{
   
   
    pid_t pid;
    int status;
    int pipesockfd;
}workerdata_t;

// 创造子进程  f:
int makeWorker(int workerNum,workerdata_t * workerArr);
int tcpInit(const char *ip,const char *port,int *psocket);
int epollAdd(int epfd,int fd);
int epollDell(int epfd,int fd);

int workLoop(int sockfd);

int sendfd(int sockfd,int fdtosend);
int recvfd(int sockfd,int * pfdtorecv);
#endif

  • sendfd.c
#include "head.h"

int sendfd(int sockfd,int fdtosend)
{
   
   
    struct msghdr hdr;
    bzero(&hdr,sizeof(hdr));
    // 1. 正文部分:无论正文部分是否有用,必须要有正文
    char buf[] = "hello";  // 此外的数据
    struct iovec vec[1];// 数组记录离散区域
    vec[0].iov_base = buf; // 第一个离散碎片的首地址
    vec[0].iov_len = 5;
    hdr.msg_iov = vec;     // 将离散区域的信息放入hdr
    hdr.msg_iovlen = 1;
    // 2. 控制部分的字段
    struct cmsghdr  * pcmsg = (struct cmsghdr *)malloc(CMSG_LEN(sizeof(int))); // data:fd(int)
    pcmsg->cmsg_len = CMSG_LEN(sizeof(int));
    pcmsg->cmsg_level = SOL_SOCKET;
    pcmsg->cmsg_type = SCM_RIGHTS;
    // 通过pcmsg得到data首地址,强转成int*再解引用;
    *(int*)CMSG_DATA(pcmsg) = fdtosend;
    hdr.msg_control = pcmsg; // 将控制字段的信息放入hdr中
    hdr.msg_controllen = CMSG_LEN(sizeof(int));
    int ret = sendmsg(sockfd,&hdr,0);
    ERROR_CHECK(ret,-1,"sendmsg");
}

int recvfd(int sockfd,int * pfdtorecv)
{
   
   
    // 接受和发送的区别,接收方不知道buf的内容和pcmsg->data的内容
    struct msghdr hdr;
    bzero(&hdr,sizeof(hdr));
    // 1. 正文部分:无论正文部分是否有用,必须要有正文
    char buf[6] = {
   
   0};  // 此外的数据
    struct iovec vec[1];// 数组记录离散区域
    vec[0].iov_base = buf; // 第一个离散碎片的首地址
    vec[0].iov_len = 5;
    hdr.msg_iov = vec;     // 将离散区域的信息放入hdr
    hdr.msg_iovlen = 1;
    // 2. 控制部分的字段
    struct cmsghdr  * pcmsg = (struct cmsghdr *)malloc(CMSG_LEN(sizeof(int))); // data:fd(int)
    pcmsg->cmsg_len = CMSG_LEN(sizeof(int));
    pcmsg->cmsg_level = SOL_SOCKET;
    pcmsg->cmsg_type = SCM_RIGHTS;
    // 通过pcmsg得到data首地址,强转成int*再解引用;
    hdr.msg_control = pcmsg; // 将控制字段的信息放入hdr中
    hdr.msg_controllen = CMSG_LEN(sizeof(int));
    int ret = recvmsg(sockfd,&hdr,0);
    ERROR_CHECK(ret,-1,"sendmsg");
    printf("buf = %s,fd = %d\n",buf,*(int*)CMSG_DATA(pcmsg));
    *pfdtorecv = *(int*)CMSG_DATA(pcmsg);
}
  • tcpInit.c
#include"head.h"
int tcpInit(const char *ip,const char *port,int *psocket)
{
   
   
    // socket setsockopt bind listen
    *psocket = socket(AF_INET,SOCK_STREAM,0);
    int reuse = 1;
    int ret = setsockopt(*psocket,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
    ERROR_CHECK(ret,-1,"setsocketopt");
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(port));
    addr.sin_addr.s_addr = inet_addr(ip);
    ret = bind(*psocket,(struct sockaddr*)&addr,sizeof(addr));
    ERROR_CHECK(ret,-1,"bind");
    listen(*psocket,50);
}

  • worker.c
#include "head.h"

int makeWorker(int workerNum,workerdata_t * workerArr)
{
   
   
     for(int i = 0;i<workerNum;i++)
     {
   
   
         int pipefd[2];
         socketpair(AF_LOCAL,SOCK_STREAM,0,pipefd);
         pid_t pid = fork();
         if(pid==0)
         {
   
   
             close(pipefd[1]);
             workLoop(pipefd[0]);
         }
         close(pipefd[0]);
         workerArr[i].status = FREE;
         workerArr[i].pid = pid;
         workerArr[i].pipesockfd = pipefd[1];
        printf("makeWorker i = %d,pid=%d pipefd=%d\n",i,pid,pipefd[1]);
     }
     return 0;

}

int workLoop(int sockfd)
{
   
   
    while(1)
    {
   
   
        int netfd;
        recvfd(sockfd,&netfd); // 接受任务
        printf("%d:开始干活\n",getpid());
        sleep(5);
        printf("%d:结束干活\n",getpid());

        // 任务完成之后,向主人进程发送自己的pid 通过已经建立好的sockpair(全双工通信)
        pid_t pid = getpid();
        send(sockfd,&pid,sizeof(pid),0); // 即可读又可写  ,此时主进程可能阻塞再epoll_wait中
        // 为了让子进程一发送,主进程立马醒来,可以将管道描述符放入监听集合中
    }
}
  • epoll.c
#include "head.h"

int epollAdd(int epfd,int fd)
{
   
   
    struct epoll_event events;
    events.events = EPOLLIN;
    events.data.fd = fd;
    epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&events);
    return 0;
}

int epollDell(int epfd,int fd)
{
   
   
    epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
    return 0;
}
  • main.c
#include "head.h"

int main(int argc,char **argv)
{
   
   
    // ./server 127.0.0.1 1234 3
    ARGS_CHECK(argc,4);
    int workerNum = atoi(argv[3]);
    workerdata_t *workerArr = (workerdata_t*)calloc(workerNum,sizeof(workerdata_t));

    //  创建子进程,获取每个子进程的状态
    makeWorker(workerNum,workerArr);
    int sockfd;
    // 初始化tcp服务器
    tcpInit(argv[1],argv[2],&sockfd);
    // 初始化epoll
    int epfd = epoll_create(1);
    epollAdd(epfd,sockfd);


    // 为了让子进程一发送,主进程立马醒来,可以将管道描述符放入监听集合中
    for(int i = 0;i<workerNum;i++)
    {
   
   
        epollAdd(epfd,workerArr[i].pipesockfd);
    }
    while(1)
    {
   
   
        struct epoll_event readySet[1024];
        int readyNum = epoll_wait(epfd,readySet,1024,-1);
        for(int i = 0;i<readyNum;i++)
        {
   
   
            if(readySet[i].data.fd == sockfd)
            {
   
   
                int netfd = accept(sockfd,NULL,NULL);

                // 顺序查找一个空闲的进程
                for(int j = 0;j<workerNum;j++)
                {
   
   
                    if(workerArr[j].status == FREE)
                    {
   
   
                        sendfd(workerArr[j].pipesockfd,netfd); // 这里会导致 workLoop中的recvfd由阻塞变成就绪状态
                        workerArr[j].status = BUSY;
                        printf("新的客户端连接上了 %d\n",netfd);
                        break;
                    }
                }
                // 这个close很重要,有空闲的子进程时,就关闭父进程中的netfd,但是由于已经给子进程复制了一个,所以这个描述符不会关闭
                // 当发现没有空闲的子进程时,关闭父进程的netfd,会断开客户端的连接,直接拒绝访问,不做等待队列
                close(netfd);

            }
            else // 某个子进程完成任务了
            {
   
   
                int pipesockfd = readySet[i].data.fd;
                // 使用顺序查找从fd找到是几号进程
                for(int j = 0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值