# 【NO.478】tcp服务器epoll的多种实现







![在这里插入图片描述](https://img-blog.csdnimg.cn/293806a5504c4302ac756493524007d6.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oiR5Lmf6KaB5b2T5piP5ZCb,size_15,color_FFFFFF,t_70,g_se,x_16)


我们在读写文件的时候，这是一款服务器，CS，这是一个服务器，这个客户端去连接服务器的时候，中间大家知道从连接的这个过程中间产生通过三次握手连接，服务器先进行监听一个端口，监听的时候是用调用listen进行监听，



TCP网络编程模型就好比这样一个模型，大家去酒店吃饭，走到那个饭店的门口门口有一个迎宾的人，有迎宾的小姐姐，然后你跟她说吃饭，她把你带进餐馆里面，然后给你介绍一个真正的服务员，后面你点菜买单，然后包括像夹菜点酒都是由这个服务员为你去服务。

这里面这个酒店出现两个人，一个是对你的迎宾的人，第二个为你服务吃饭的服务员，就这个模式。

大家可以看到客户端和服务通信也是这样一个情况，客户端就是跑到酒店去吃饭，
走到服务器这地方，服务器这里有一个listen的端口，就是迎宾的人，然后介绍一个服务员，后面客户端进行通信的时候就是介绍的这个服务员进行通信。

就是这个迎宾的人怎么让他迎宾，怎么让他开始工作？
迎宾的人上哪里去找？首先记清楚他是一个人，那这个人怎么来呢？
这里面所提到的人就是 fd，我们可以通过socket创建，创建完之后，还有比如说一个酒店特别大，比如说这个酒店特别大，它有几个门那这个迎宾的小姐姐他去那个门？他只能做一件事情，他去哪个门，所以这里我们需要绑定他在哪个门，bind()一个端口，绑定完之后，他才开始工作，真正开始工作是进入listen状态。

我想问一下这迎宾的小姐姐在listen的过程中间，他跟酒店这个管理制度有没有关系，跟这个酒店的工作流程有没有关系？没用，所以刚才讲个迎宾的小姐姐，她是一个被动的，她是一个被动的工作，就是他的工作就是listen，他不影响酒店的内部的整个工作流程，首先这点一定要记住，listen一旦进入之后，他不影响其它流程，

然后当来了一个客人之后，这时候要创建一个新的，accept才是真正接待的人，listen这个过程是指定这个迎宾的小姐姐在这个门口迎宾的工作。真的来一个客人的话，accept()介绍一个新的服务员,后面的动作点菜也好，买单也好，recv和send都是跟这个服务员之间沟通,包括close买单的时候也是。

服务器它写代码的就这么写，它就是这些接口，这个listen这个过程他就是让这个小姐姐在门口迎宾，然后剩下的就是小姐姐把这个人带进去介绍一个新的服务员

**这个过程，三次握手是服务员在迎宾的这个过程中间发生的，三次握手他是被动发生的，**

三次握手是在这个迎宾的人在这等待的过程中间完成，三次握手之后才把他介绍了这个新的服务员，
有人吃饭你们有人问到你是在这吃饭都说是的，这个过程叫三次握手，收到客户说是的，再把他带进去介绍服务员,

客户端这边首先吃饭，客户端也是一个人，这个人也通过socket创建的，创建完对应的他去哪里吃饭，他去哪里吃饭？准备一个地址，准备一个地址然后connect。

这个过程就是客户端连接服务器，这个过程可以绑定也可以不绑定，这个绑定的就相当于这个他出去吃饭，他的的每个门主他可以从大门走也可以小门，所以这个都okay，如果只允许他从正门走，那这时候就给他绑定一下

它是去连接的，你可以绑定也可以不绑定，所以我认为在客户当中出现了两个地址，一个是绑定的，一个是不绑定的，绑定和绑定它是一个可选项，它是一个可选项

客户端调用contact函数就是决定了它去连接这个服务器，每一个服务器IP地址端口都是多少，去找哪个酒店迎宾的小姐姐，这是确定的是在connect确定的，
这地方就连接完成之后再调用的是send和recv这是网络编程的整个流程。

这是网络编程核心的，以这些东西为基础就衍生出来了很多的做法，客户端和服务器就是这个流程

![在这里插入图片描述](https://img-blog.csdnimg.cn/ce8a5cbd9ea7476d9af1bb00b5fff962.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oiR5Lmf6KaB5b2T5piP5ZCb,size_20,color_FFFFFF,t_70,g_se,x_16)



udp的服务器该怎么做？
**udp就好比你去一个小餐馆，那个迎宾的人跟服务员他是一个人**，就是你去了一个小饭店，没有连接在这方面，这个客户端和服务器之间不涉及到任何的连接，这个应该不用去争议的时候，

我们在设计函数的时候它的功能要做到单一讲究的一个功能，就是还是单一功能单一越单一越好。可利用可复用性就越强，就是一个函数设计功能越单一，它的可复用性就会越强，一个函数它越符号它的可复用性就越低。

recvfrom函数就有点意思，它不单只接收数据，并且还把哪个客户端给谁发都带出来了，就是recvfrom函数数据是谁发的都知道。

那现在考虑一下，比如说现在有三个udp的客户端，就是三个函数，同样都是都用sendto都是发给这一个端口，都是recvfrom由他来一个接受，

就好比一个小的餐馆，现在这个餐馆只有一个迎宾的人，现在过去了多批人多活人同时走到这个门口，我就想问一下，那这个迎宾的人，**他能分清楚这三户人？他不可以，**

这个服务端我们只有1个，然后如果10个1000个2000个同时调用sendoto，
我就想问一下那这个服务呢它能区分区分不了，区分不了也就是换一个说法，udp的服务器如何做多个客户端并发？

那也就是我们在udp的这个基础上面recvfrom，可能很多人会想一个办法，就是buffer里面想办法，也就是说从客户端发到服务器的时候，这个数据上面想办法，
就是我定义一种本地的协议，就是说我发送的数据包我知道是购买a发的，购完b发的数据我能知道它是b发的，然后c发的我就知道那可能同学在想的，**就是我能不能通过这个数据包括应用协议来解决这个问题**。

就是给buffer加客户端头，但是客户端口端加客户端头是有一个巨大的前提？

我们现在讲的第一个方法就是在我们发送的数据包上面加一层协议能不能行？
好很多朋友认为可以，好，
我跟大家讲一下不可以，为什么？

其它是有很大的前提，就是需要顺序的接收，就是需要顺序接收这是可以的。
就是我们知道a发的先到先发的先到后，发的后到那这种情情况下面它是ok的，那你可以在协议里面加一协议，这是可以的，在公网的情况下，面udp是很难保证先发的先到后发的后报可能有可能先发的数据后到后发的，数据先到。

第二种方法
其实原理也很简单，请他注意那我们用udp来做，其他注意我们也可以仿造了刚刚TCP这个迎宾的这个模式迎宾的这个人的模式，那么也我们也可以仿照这个模式，
好这样一个模式做，那怎么做呢你怎么做对吧？

我们也是这样的，创建一个迎宾的人在这里等着，
也就客户他先sendto给服务器这个迎宾的人创建一个，然后迎宾的接收到一个数据包之后，再为他准备一个服务员跟TCP一样，与其实那个就是一模一样，也准备一个服务员，也就是在那个为了真的区分好每一个客户每一个群体每个群体每个客户是谁，那个火车站前面那个小饭馆也采用了酒店这种模式，一个迎宾者和一个点赞的人，一个迎宾者一个点赞的，在各方面就采用这种模式，做一连接之后，sendto先给个迎宾的人，然后服务端分配一个独有的fd给这个客户端进行服务，然后后面sendto就回来的数据，就是从这个新的fd开始发送出来

就是说新的这个 fd对它进行发送。
这就是刚才讲的用udp模拟TCP的三次过程实现udp并发

socket是什么？

很难理解很难理解这个为什么叫做插座，fd与五元组配套

![在这里插入图片描述](https://img-blog.csdnimg.cn/9ccae4f9bf3c414ea434005f7afa5d53.png)



为什么这个fd它只对应到这个五元组里面，只有这个tcp和udp？

比如说我想去抓以太网的包那怎么做，其实相当于那时候我们使用的是一个叫原生的socket

好，我讲就这一份关于fd我们既可以把它当做文件描述，主要理解也可以把它当做网络通信的一个观点，一个通信的一个句名就是我们拿着它，就相当于拿到一个客户端一样，这个fd给代表来理解说我们可以把它当文件描述，也可以当做一个客户端，

先跟大家解释一下它关于文件描述的一些事情，文件描述的一些事情那就是io的一些事情。

讲到这里之后要给大家介绍一个很老很老的东西，为什么跟大家去讲这些非常老的东西，啊大家才能够知道这个技术它迭代的这个分数它怎么迭代的一个非常非常老的东西，大家可能清楚。
就是叫sigio有可能听过，有些没听过叫sigio



![在这里插入图片描述](https://img-blog.csdnimg.cn/d8fade7951594d9d8631a3a305cb4246.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oiR5Lmf6KaB5b2T5piP5ZCb,size_20,color_FFFFFF,t_70,g_se,x_16)



现在一个客户端给他发送一个数据，我们调一个sendto给他发一点数据，请大家注意请大家注意，这是一个socket，我们在一个端口进行接受recvfrom。
由于当发过去的时候，这个 io他现在有数据了，他就会去触犯这个 singio信号 也就是说
这个东西就有意思，他就跟我们进程通信，就好比我们常用的一个命令叫kill-9，比如在后面加一个进程ID1234一样，
要那也就是说出现一个信息一个信息，大家看到这个kill-9什么意思？9是信号那个 ID给这个进程1234这个进程发一个-9的信号

数据先由操作系统接收的数据，然后再由操作系统去通知对应的这个进程来接触这个数据，sigio是由操作系统把这个信号抛给了对应的这个进程，然后这个进程捕获到这个 sigio的信号，捕获到这个 sigio的信号，所以它才能正常接收数据

```cpp
#include <stdio.h>



#include <sys/types.h>



#include <sys/socket.h>



#include <netinet/in.h>



 



#include <string.h>



#include <unistd.h>



#include <signal.h>



#include <fcntl.h>



 



 



int sockfd = 0;



 



void do_sigio(int sig) {



 



    struct sockaddr_in cli_addr;



    int clilen = sizeof(struct sockaddr_in);



    int clifd = 0;



#if 0



    clifd = accept(sockfd, (struct sockaddr*)&cli_addr, &clilen);



 



    char buffer[256] = {0};



    int len = read(clifd, buffer, 256);



    printf("Listen Message : %s\r\n", buffer);



 



    int slen = write(clifd, buffer, len);



#else



 



    char buffer[256] = {0};



    int len = recvfrom(sockfd, buffer, 256, 0, (struct sockaddr*)&cli_addr, (socklen_t*)&clilen);



    printf("Listen Message : %s\r\n", buffer);



 



    int slen = sendto(sockfd, buffer, len, 0, (struct sockaddr*)&cli_addr, clilen);



#endif



}



 



 



int main(int argc, char *argv[]) {



 



    sockfd = socket(AF_INET, SOCK_DGRAM, 0);



 



    struct sigaction sigio_action;



    sigio_action.sa_flags = 0;



    sigio_action.sa_handler = do_sigio;



    sigaction(SIGIO, &sigio_action, NULL);



 



 



    struct sockaddr_in serv_addr;



    memset(&serv_addr, 0, sizeof(serv_addr));



    



    serv_addr.sin_family = AF_INET;



    serv_addr.sin_port = htons(9096);



    serv_addr.sin_addr.s_addr = INADDR_ANY;



 



    fcntl(sockfd, F_SETOWN, getpid());



 



    int flags = fcntl(sockfd, F_GETFL, 0);



    flags |= O_ASYNC | O_NONBLOCK;



 



    fcntl(sockfd, F_SETFL, flags);



 



    bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));



 



    while(1) sleep(1);



 



    close(sockfd);



 



    return 0;



 



}



 



 



 



 
```



![在这里插入图片描述](https://img-blog.csdnimg.cn/b741eedf0ed94718a7cc5b9518a5d7d5.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oiR5Lmf6KaB5b2T5piP5ZCb,size_17,color_FFFFFF,t_70,g_se,x_16)



![在这里插入图片描述](https://img-blog.csdnimg.cn/49a86fb056bf43809f02dbac20f4a6aa.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oiR5Lmf6KaB5b2T5piP5ZCb,size_20,color_FFFFFF,t_70,g_se,x_16)



我们接着来分析一下这个代码意味着什么？

那我们现在问一下那个sigio是谁发出来的？
也就是这个进程他能接受的信号，这里有两个点要跟大家讲，第一个点是说的信号接收的流程是
第二个要跟大家讲的就是这个 sigio它意味着什么？
第一个就是关于信号接收是怎么接受?第二个CIO意味着什么？

操作系统发的，首先他解释一下那这个操作系统，他就是当接受那个数据的时候，在网卡接受那个数据的时候，既然是网卡发出来的，就是网卡接收一些数据通知，
操作系统发出sigio消息，那既然它是网卡接收到数据就会发出sigio，那各位我问大家有问题，TCP可不可以？不行，为什么？

因为TCP这个过程中间，TCP的过程中间第一个网络io的消息会很多，
好，那各位部门你可以想象一下，如果我们用TCP来做的话，它的single的信号会比udp要多很多的会要多很多很多，
对吧？
因为大家可以看到这个sigio的时候，它没有区分，是哪个fd没有区分，

为什么tcb不行，是因为tcp处理的时候，
这个情况太多，

那这东西怎么处理？
TCP不可以，那现在问一下大家如果，**现在操作系统要把TCP不去响应tcp的话**，那各位们你应该在哪个地方实现？
你看或者用哪一种形式来屏蔽到来，忽略到这个，那 TCP这过程我们应该在哪个地方去忽略掉这个sigio？

在协议栈这个地方TCP接收到一针数据的时候，不去处理sigio

sigio 与poll select是怎么联系的？

关于信号我跟大家解释三个点，
第一个点对这个进程而言这些信号如何保存，也就是我们注册的就是在我们这个代码上面，就是关于sigio的这个信号这个回调函数它怎么保存的？

就是大家可以看到这个进程，在我这里执行的时候，它是一个进程，在这个进程里面它关于这个回调函数它怎么保存的，
**第一点就是进程内部如何保存
第二个就是当我们调用signal这个函数的时候，signal这个函数的时候如何把这个信号保存到进程里？
第三个就是当我们在什么时候发，比如我们kill-9的时候这个信号如何发送？**

这三点从这三点各方面来理解一下，所以各方面如果他有被问到后面他面试的时候有被问到信号是怎么工作，也是这三个方面回答的。

就相当于这么一个进程，中间有一块空间，
他专门用了存信号，这个信号他也可以思考一下，但现在也可以思考一下这个信号的集合，我们总共一起有那些信号？比如刚刚那个kill-9，sigint,sigterm,sigio这些信号总共一起信号大概是有这么多有这么多，我找一下还有这么多，31种信号，那这31种信号我们怎么去存？

第二个这是一个进程，然后我们在应用程序的时候，我们调用signal的这个函数的时候，如何把这个信号保存到进程里面，还有一个就是我们如何通过其他的进程，比如说kill-9或者操作系统sigio如何触发到这个里面，

第一个就是说进程集合怎么存的，这一块怎么存的。
第二个是说信号signal的函数，如何把这信号保存到结构里面。
第三个就是kill-9，以及这个信号怎么发送到进程，进程怎么捕获这东西的。

首先来看一下这个进程里面保存它，
首先第一个想到的是task_struct

![在这里插入图片描述](https://img-blog.csdnimg.cn/9facb7f2131140c1b5aa17304eeb2b68.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oiR5Lmf6KaB5b2T5piP5ZCb,size_20,color_FFFFFF,t_70,g_se,x_16)



就是存到这个地方就这个 sighand_struct



![在这里插入图片描述](https://img-blog.csdnimg.cn/02e34b859eec4e37bf174ffb3b7e1ada.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oiR5Lmf6KaB5b2T5piP5ZCb,size_20,color_FFFFFF,t_70,g_se,x_16)



像这个sighand_struct从这里面大家看到这里有一个k_sigaction
大家看到这里有个k_signaction，我们点进去之后，
可以看到里面对应的是有一个signaction，

![在这里插入图片描述](https://img-blog.csdnimg.cn/f5d805b439ef4330bb975d5237e3ee1c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oiR5Lmf6KaB5b2T5piP5ZCb,size_20,color_FFFFFF,t_70,g_se,x_16)



这跟用户空间的是一样的，这里面action这里面它就以这个集合就是对应的这个 action的集合，

![在这里插入图片描述](https://img-blog.csdnimg.cn/aa5287c7e2684fe1a5fd5ba591ca3fe0.png)



这有个_NSIGl对应的就是这个数组,这个数组呢大小是64，也就是在每一个进程里面在每一个进程里面有一个空间sigaction的一个结构体，一个对应的数组叫action，这里面这个数组大小有64，它用来去存储所有的信号集合，

这个数字64存储信号怎么存的呢？

比如说有的时候如果一下触发大于64的信号怎么办？
比如说我一下触发128个，他是不是就溢出来了呢？
关于这signal他是怎么存的，信号只有31个，

再谈一个函数，
可以看到这是一个系统调用，这是内核里面内核里面这个系统调用SYSCALL_DEFINE2

![在这里插入图片描述](https://img-blog.csdnimg.cn/0b518c880f4f43a4b4ebfaa3b5417c01.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oiR5Lmf6KaB5b2T5piP5ZCb,size_20,color_FFFFFF,t_70,g_se,x_16)



这个代码里面我们调用的这个signal，它是一个系统调用，它是一个系统调用,

![在这里插入图片描述](https://img-blog.csdnimg.cn/f4538307954b4e0285318b8b00665c46.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oiR5Lmf6KaB5b2T5piP5ZCb,size_13,color_FFFFFF,t_70,g_se,x_16)



它调用的这个函数是哪个呢？

系统调用他调到内核里面的哪个函数，就是当我们在调用signal的函数的时候，他走到了内核里面这个地方

![在这里插入图片描述](https://img-blog.csdnimg.cn/cd8bfa8f16f745e682074a22498f7233.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oiR5Lmf6KaB5b2T5piP5ZCb,size_13,color_FFFFFF,t_70,g_se,x_16)



从这里我们往下走就可以了，走到这里之后大家可以看到这个sighand的设置,

![在这里插入图片描述](https://img-blog.csdnimg.cn/7d9a54905caa474a8312b9abe54acf11.png)



这一行什么意思的，大家看看这个p就是进程,进程对应的这个就是我们sighand，然后对应的action然后sig-1，可以记住也就是刚刚这个数组64，

就是这个信号每一个信号只有一个，也就是说大家在对应这个信号的值，也就是如果我们一个sigio信号，他就被存储到这个数组里面的第sigio位，

sigio的值是多少呢？
sigio是29，也就这个信号，
它存储到28从零开始存储到28的位置，也是把之前的就进行覆盖，没错，把前面的就进行覆盖，没有触发的最后一次设置的把它覆盖，

这就是我们在调用sin的这个函数，
基本这个函数就是对应的把这个函数把我们对应的x设置那个数组里面这个内容。

kill这个函数在调用的时候带两个参数，第一个对应是PID就是进程ID。
第二个对应就是发送了什么信号

给哪个进程发什么信号，PID就是进程的ID，就这个 Id大于0的话，拿到对应的进程，然后往下再send_signal，就生产在发送的时候1层1层剥到这方面了。
直接给大家看最后一个东西怎么就是所谓的激活

![在这里插入图片描述](https://img-blog.csdnimg.cn/5780bc9452b540d8bc4c80a647e12a3a.png)



就是对应的大家可以看到这里有个signalfd_notify，大家可能看到这个中断其实对应来说就是一个信号等待的意思，

epoll,poll
他为什么要这么做？

![在这里插入图片描述](https://img-blog.csdnimg.cn/e48f83170bf144b8a0aead50c30e9ade.png)



就是不同的信号通过不同的位置那个安全的那个下标的位置一个信号，它是个整形的数字对应的数组的不同的位置。

就还是以服务器为例子，还是以一个酒店为例子？
就是酒店没调好，酒店刚刚跟大家讲了一个饭，一个饭馆一个一个很大的酒店吃饭没错没错，这是一个迎宾的小姐姐来了一个客人之后
来了一个客人，然后这个客人给他介绍了一个新的服务员，好请来注意就是每一个客户端对应一个服务员，来一桌客户就有一个服务员，
随着这个酒店规模越来越大，所以这个酒店一天可能是几十桌几百桌都有，好，那也就是对于这些服务员，**对这些服务员我怎么进行管理**，那各位朋友们可能在想一个问题，这还有什么管理嘛？有事情就向上面反馈，没错，就是有什么事情对外
反馈的这个过程
比如说还要点个菜，你去下餐，你是要下到后面厨房里面，那也就说一个客户端他点菜的时候还有他下到厨房里面去，在这个厨房在处理的时候，他知道是哪个服务员吗？
**io多路复用就这意思，为了管理多个服务员来处理的。**

这里每一个 fd有这么几种方式可以处理，第一种就是派一个人随着这个酒店可能有一两百个服务员的话，专门派一个人，这个酒店服务员根本就不动，站在他们面前，专门找一个人挨一个问，你好了没有你好了没有？你有没有要点菜的？然后再派个人往后面走，然后拿到对应的把他由厨房来派？

![在这里插入图片描述](https://img-blog.csdnimg.cn/b4d45f97e6dc46049f5a64e46c0272eb.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oiR5Lmf6KaB5b2T5piP5ZCb,size_20,color_FFFFFF,t_70,g_se,x_16)



你看这种好太麻烦，
你派一个人专门去做，而且你们这个中间还会出现一些现象，比如说我们为了管理，管理这个酒店所有的服务员，一桌一个服务员，酒店为了管理这个服务员，首先想到第一个方法，一个非常非常老套的方法啊就是，找一个人专门去问专门去咨询，那咨询哪些内容，只咨询三个状态，只咨询三个功能，
第一个状态要不要点菜？
第二个要不要点酒？
第三个就是要不要结账？
只问这三个状态，
就是每一桌一个服务员去服务一桌的时候，就是这3个轮训，感觉相当于正跑堂的，就相当于大堂经理就是这边问他那要不要？
注意这三个状态，**结合了刚刚这个人做的事情，找一个人专门去问这些服务员，那这个专门这个人是什么？他就是select。**

那大家想一下这个select它有几个参数？
第一个参数就是酒店有多少服务员，酒店的服务员的数量有多少，他要问多少次，问的次数
第二个参数，哪一些桌
可能刚开始带着一种目的看到了一个菜吃的差不多了，然后可能就说要要不要结账，可能看到有些菜刚刚点完，就它主动去问要不要加酒，刚坐下来可能就主动去问他有没有点菜，
这里面有三个集合主动的问一些桌里面需要点餐，首先进行保证这是查询的态度。
第三个就是点酒的集合，第四个结账的集合，第五个参数多长时间一次

![在这里插入图片描述](https://img-blog.csdnimg.cn/6ad172d25d514b8d9c331f2339feaa70.png)



就是这个专门去问的这个人就是相当于大堂的一个经理，
每一桌都跑过去，然后得出这么一个结果
这是他的工作是这样的，整个一起
有个前提，就是你可以想象到一个酒店里面，比如说一个酒店里面有几百桌好几百座，然后每一个桌前面站在一个服务员，
然后这个大堂的经理也好，还是助理也好，还是秘书也好，他就专门为了去传达信号去问站在桌子旁边的那个服务员，
你问一下这个你问一下这桌有没有，你问一下这个要不要不点菜要不点酒要不结账，这么一个工作

然后这5个参数就是这个点菜的集合，我们现在不知道他要不点菜，select返回的时候，他会带着哪些桌要点菜，刚开始是我们主观的认为他点菜，但是后面可能有一些要点菜，有些桌不要点菜，好返回的时候才是真正的结果

结合我们的socket理解，它也是这样，也是5个参数，
第一个我们有多少个io？第二个就是可读的集合，我们认为他需要的读，第三个就是可写的集合，第四个是出错的集合，第五个就是多长时间轮询一次。

这个应该叫中间有一个点要跟大家解释的，就是这个服务员的数量以及这个 io的数量怎么判断，这是很难理解，就是io的数量

这里是巧合的地方，
最大fd+1那为什么会采用最大的fd+1？两个原因这种方式，第一个因为我们的fd是int型的，
int型的就出现这个情况，其实这个 fd+1或者不加都可以，你就是不加1，加1是为了我们把它放大一点，那个就采用一种方式是采用这个 max fd其实ok，

比如说这个酒店有200个服务员
正在工作，有200个服务员，今天正在工作的有150位，好，正在工作的有100个人，
对于这个助理而言，我们压根就不用去管另外50位多少请假了，

我们在这地方查询的时候，我们就直接以200为计算，因为这里面还有一部分就是那些桌点菜了，我们不知道，那其实在这块我们做的一件事情就是最大的fd进行加一，最大的fd当做数量就可以，那我们就可以等同为
这个 max fd的这个值，它最大的值就相当于这个 fd总共一起有多少个数量

这个酒店工作了一段时间之后呢，发现这个专门做事的人要做一个事情，就是需要带三个小本本，第一个点菜的集合，第二点点酒的集合，第三个结账的集合，

这3个小本本记录有点麻烦，我们就进行1个小本本，就把这三个分别记在一个小本上面，大家发现也是跑也是跑，**但只是记录在一个小本本上面，这就是poll带三个参数**
一个是pfd,第二个参数是它有多大length,第三个参数是说的timeout，

可以想象一下一个酒店里面几百多的人，然后每一桌旁边带一个小姐姐，就是服务员为他倒酒倒点菜，然后在大堂里面就专门安排一个人挨桌问，他每次开始工作的这个工作的人他就是select，
他一开始工作就带三个小本本，然后里面哪些需要点酒，哪些需要点菜，然后问一次
然后在里面那些画勾，然后再带回来，隔一段时间在继续，他就取决于timeout多长时间跑，
**select翻译到io里面就是哪一些可读，哪一些可写，哪一些出错了，然后多长时间培训一次，每一次需要把这个集合进行更新，每一次把数据带进去，**

select五个参数,还有就是poll这东西每次记三个小本本有点麻烦，然后后面改成一个把这三个集合记到一个本本里面，记在一个本里面，那对应就变成了poll模式。
同样poll他还是需要定时的去跑一次，多长时间跑一次，多长时间跑一次，但是只是由三个本变成一个本，由三个数组变成一个数，
其他的没有改变，其他的没有改变，他还是多长时间轮训一次，多长时间去整个大堂里跑圈 ，他不是一直在轮训，它是根据时间定时的，

这个定时谁定的这个时间，我想问一下这个秘书这个专门跑这个人，**这个时间期间他在干什么？**

他是在哪个地方休息，在这个中间时间空档的时候这个专门跑堂这个人他在哪个地方休息，他休息在哪里？

请大家注意这select的timeout它的时间是在哪里休息，请大家注意它阻塞在哪里，也就是说这个时间比如说我们3秒钟,也就说这io三秒钟之内，他阻塞是阻塞在select函数的内部，三秒钟没有我就返回，如果有的话就低于三秒，最长等待三秒，没有的话我就最长等待三秒，有的话我就提前返回没有，然后poll也是这样

剩下一个主角，一个非常重要的东西，就是epoll，epoll是怎么样的，epoll就比较高级

每一桌上面放一个按钮，现在有些酒店点菜，你按那个按钮就可以，就变成这种模式，服务员还在那里，但是每个服务员手里捧着一个小按钮要点菜了然后按一下，要点酒了按一下，要结账了按一下。这个人只有遇到按钮的时候，他就不需要每一桌去问，而是直接按按钮的那一桌，我们才开始把这信息收回来，就这样。

![在这里插入图片描述](https://img-blog.csdnimg.cn/194fa65b083e4beba8e3f5637c48b640.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oiR5Lmf6KaB5b2T5piP5ZCb,size_19,color_FFFFFF,t_70,g_se,x_16)



这epoll是这样的，比如一个小区有n多的住户，现在在考虑这个快递怎么记的问题，怎么记录，就在楼下做一个东西就是蜂巢。

![在这里插入图片描述](https://img-blog.csdnimg.cn/ae360552fbac40eb911e1a8d181ea0c7.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oiR5Lmf6KaB5b2T5piP5ZCb,size_10,color_FFFFFF,t_70,g_se,x_16)



那这个快递员他去工作的时候什么样？
送快递的时候就把那个快递放在蜂巢里面，收的时候也是要从这个里面拿，这样呢他就跟那个住户进行了一个隔离，
隔离那大家可以节省了很多时间，以前快递员去跑整个小区的情况，都不需要挨家去敲，直接从这个蜂巢里面拿取，

这里有前提什么？
中间可能有一部分住户，不允许快递员拿？
首先第一个快递员，我们通过epoll_create()创建一个快递员，
第二个比如说这个住户里面新搬了一个，我们通过epoll_ctl(ADD/DEL/MOD)增加一个或者说搬走一个，或者说有一个人从5楼搬到4楼，从4楼搬到3楼房进行mod修改。
第三个函数是epoll_wait()多长时间轮询一次，epoll总归一起由这三个函数所组成。

epoll_create()你就可以理解，聘请一个快递员，然后为小区收快递这个集体里面增加一个减一个，然后修改一个，然后epoll_wait()是多长时间跑一次，

epoll_wait()总归一起带4个参数，一个是epfd是哪个快递员，第二个就是他收快递的那个袋子events，第三个是他那个袋子有多大length，第四个是多长时间跑一次timeout。

epoll_create(1)这里面有个参数，它带这个参数只有0和1的区别，不管你写多大，不管你填多大，你就是填1万与填10万，它的效果是一样的,那么这个参数有什么用？

这个参数只有0和1的区别，就是为什么带这个参数？就是epoll最早的版本
一次性最多有多少，就是后面这个快递员收快递的时候，

它最大一次性能够收多少？最多最多，这是一个预估的值，最多最多最初是这样一个值，随着后面这个一点一点的修改，其实在前期实现的时候，用的是一些数组或者用的是一个固定的空间或者是用一个list分配的，

创建的快递员在蜂巢里面最多能收多少个，就类似，但是随着后面的版本的修改，相当于这个蜂巢的这个盒子就变成了无线大，就变成了所谓我们的一个链表来处理，
所以后面这个值就没有太大的意义，所以它只有0合1的区别，0就是失败，1就成功，大于0的数都成功就可以了，其实这个参数它没有什么意义，现在来说已经没什么意义，只要大于0就 ok。

第二个就是epoll_ctl()这个函数，它是为小区里面，我们添加io，删除io，修改io，就这个意思。

**总结**

第一个跟TCP编程的方式。
第二个udp并发udt并发
第三个就是关于socket是怎么回事，这个插座怎么理解，然后解释sigio就是从网络最古老的版本这个sigio接受数据，然后解释了这个网络sigio的是从哪里接收的数据，这个信号从哪里来的，然后解释了信号的整个数据流程，
然后讲select,epoll.

原文作者：[也要当昏君](https://blog.csdn.net/qq_46118239)

原文链接：https://bbs.csdn.net/topics/605102147