首页 > 其他分享 >多路io复用pool [补档-2023-07-19]

多路io复用pool [补档-2023-07-19]

时间:2024-01-13 18:13:07浏览次数:32  
标签:文件 07 19 补档 int 描述符 poll 监听 客户端

多路IO- poll

3.1简介

​ poll的机制与select类似,他们都是让内核在以线性的方法对文件描述符集合进行检测,根据描述符的状态进行具体的操作。并且poll和select在检测描述符集合时,会在检测的过程中频繁的进行用户区和内核区的拷贝,随着文件描述符集合中的数量增大,开销也随之增大,效率也会变低。

​ select检测的文件描述符数量最大为1024个,而poll则没有数量限制。并且select可以跨平台,而poll只能在Linux系统中使用

3.2 poll函数

头文件:#include <poll.h>

函数原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);

函数参数:

​ fds:是一个struct pollfd类型的结构体数组,里面存储了要检测的文件描述符的信息,稍后详述。

​ nfds:数组实际有效内容的个数。

​ timeout:

	-1 一直阻塞,直到检测的集合中有就绪的文件描述符(发送事件的),然后解除阻塞。

	 0 不阻塞,不管检测集合中有没有已经就绪的文件描述符,函数会马上返回。

	大于0 阻塞指定的毫秒数之后,然后解除阻塞。

函数返回值:

大于0的整数:表示检测的集合中已就绪的文件描述符总个数。

-1:表示调用失败。

结构体struct pollfd的成员(三个):

int fd:委托内核检测的文件描述符。

short events:委托内核要检测文件描述符的事件。

short revents:文件描述符实际发送的事件,这是一个传出参数。

结构体struct pollfd事件表:

事件 常值 作为events的值 作为revents的值 说明
读事件 POLLIN 普通或优先带数据可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读
POLLPRI 高优先级带数据可读
写事件 POLLOUT 普通或优先带数据可写
POLLWRNORM 普通数据可写
POLLWRBAND 优先级带数据可写
错误事件 POLLERR 发生错误
POLLHUP 发送挂起
POLLNVAL 描述不是打开的文件

3.3 使用poll实现高并发的步骤:

第一步:通过socket函数创建一个监听描述符。

第二步:通过bind函数将监听描述符和服务端本地地址进行绑定。

第三步:通过listen函数将监听文件描述符切换至监听状态。

第四步:自定义一个文件描述符集和结构体,然后对其初始化,然后准备一个变量用于记录最大描述符的值。

第五步:将监听文件描述符放入自定义文件描述集合下标为0的地方(不这样做的话集合为空,poll的第三参数是-1则会一直阻塞,如果是其他数则还是不行),然后进入无限循环

​ 使用poll函数检测刚才自定义的文件描述符集合,如果集合中的某个描述符有信号了(读,写,错误等),那么则根据poll的第三参数来判断是否阻塞。(因为集合第0个位置是监听符,所以如果监听符有动静代表有客户端连接服务端了)

​ 然后通过对监听描述符的读缓冲区进行判断,因为如果有客户端连接服务端,那么监听描述符的读缓存区就是这个客户端的信息,然后通过accept和监听文件描述符与刚才的客户端产生连接,返回一个与客户端产生连接的描述符,然后再拿这个与客户端建立连接的文件描述符存入自定义文件描述符集合供poll进行检测,加入之后记得跳出循环。

​ 之后我们判断现在已建立链接的文件描述符是否是最大的,如果是则记录一下。

​ 然后我们可以使用循环遍历的方法来遍历整个自定义文件描述符集合,来看看那个文件描述符是已建立链接的状态,然后通过使用这个文件描述符与对应的客户端进行数据传输,回应和处理客户端的各种请求等等。当回应完毕,处理完毕之后又或者客户端没有请求了,那么记得关闭这个与客户端建立连接的文件描述符,然后将这个文件描述符所在的自定义文件描述符集合中的位置置空,留给其他人使用。

第六步:可以不断重复第五步,如果所有的事情都做完了,那么可以关闭监听文件描述符。

3.4 使用poll实现高并发的服务端代码:

点击查看代码
#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <arpa/inet.h>

#include <sys/select.h>

#include <poll.h>

 

int main()

{

  int i;

  //1.创建套接字,使用ipv4和tcp

  int sfd = socket(AF_INET, SOCK_STREAM, 0);

  if (sfd == -1) {

​     printf("创建监听套接字失败!\n");

​     exit(0);

  }

 

  //2.绑定套接字

  struct sockaddr_in serv;

  serv.sin_family = AF_INET;

  serv.sin_port = htons(10066);

  serv.sin_addr.s_addr = INADDR_ANY;

  int rent = bind(sfd, (struct sockaddr*)&serv, sizeof(serv));

  if (rent == -1) {

​     printf("绑定套接字失败!\n");

​     exit(0);

  }

 

  //3.监听

  rent = listen(sfd, 128);

  if (rent == -1) {

​     printf("监听失败!\n");

​     exit(0);

  }

 

  //4.创建和初始化自定义文件描述符集合

  struct pollfd fds[1024];

  for (i = 0; i < 1024; i++) {

​     //初始化,-1代表这个位置为空,可用

​     fds[i].fd = -1;

​     fds[i].events = POLLIN;//设定事件

  }

  fds[0].fd = sfd;//自定义文件描述符集合第一个位置存放监听文件描述符

  int maxfd = 0;//用来记录最大的文件描述符

 

  //5.使用循环来等待链接。

  while (1) {

​     //委托内核检测

​     rent = poll(fds, maxfd + 1, -1);

​     if (rent == -1) {

​       printf("失败!\n");

​       exit(0);

​     }

​     

​     //代码执行到这里了代表有客户端的连接请求了

​     //因为集合的第一个位置是监听文件描述符,监听文件描述符有动静了

​     //代表有连接

​     if (fds[0].revents & POLLIN) {

​       //接收连接请求

​       struct sockaddr_in sockcli;

​       int len = sizeof(sockcli);

​       int connfd = accept(sfd, (struct sockaddr*)&sockcli, &len);

 

​       //将已建立连接的描述符交给内核来检测读缓冲区

​       for (i = 1; i < 1024; i++) {

​         if (fds[i].fd == -1) {

​           fds[i].fd = connfd;//将已连接的描述符加入检测

​           break;

​         }

​       }

​       //记录最大的值

​       maxfd = i > maxfd ? i : maxfd;

​       

​     }

 

​     //如果有客户端发送数据

​     int j;

​     for (j = 1; j <= maxfd; j++) {

​       if (fds[i].revents & POLLIN) {

​         char buf[100];

​         //读数据

​         int ret = read(fds[i].fd, buf, sizeof(buf));

​         if (ret == -1) {

​           printf("读取来自数据失败!\n");

​           exit(0);

​         }

​         else if (ret == 0) {

​           printf("对方已经关闭了连接!\n");

​           //关闭连接之后记得关闭对应的描述符,然后将自定义的集合中相应的位置值为-1

​           close(fds[i].fd);

​           fds[i].fd = -1;

​         }

​         else {

​           printf("来自客户端:%s\n",buf);

​           //回应客户端

​           write(fds[i].fd, buf, sizeof(buf));

​         }

 

​       }

​     }

 

  }

 

  close(sfd);

  return 0;

}

标签:文件,07,19,补档,int,描述符,poll,监听,客户端
From: https://www.cnblogs.com/xiaobai1523/p/17962691

相关文章

  • 多路io复用epoll [补档-2023-07-20]
    多路io-epoll4-1简介​它是linux中内核实现io多路/转接复用的一个实现。(epoll不可跨平台,只能用于Linux)io多路转接是指在同一个操作里,同时监听多个输入输出源,在其中一个或多个输入输出源可用时范慧慧这个源,然后对其进行操作。​epoll采用红黑树来管理待检测的集合,而p......
  • UDP通信 [补档-2023-07-22]
    UDP通信6-1简介​UDP通信是面向无链接的,不稳定,不可靠,不安全的一种通信方式。TCP在通信前发送方会向接收方进行三次握手链接,然后确认双方链接后才会进行数据传输,最后四次挥手保证链接关闭。而UDP不会三次握手四次挥手,它会直接向发送方发送数据,无论接收方是否会收到,所以UDP更......
  • socket编程 [补档-2023-07-10]
    Linux网络编程1.socket编程socket是一种通信机制,用于在网络中不同计算机之间进行数据传输,当然也可用用于进程间通信。在linux中,有文件描述符这么个东西,我们可以通过socket函数创建一个网络连接,socket的返回值为一个文件描述符,我们拿到这个文件描述符就可以像操作普通io文件那样......
  • P2198 杀蚂蚁 题解
    题目大意有一条长度为\(n\)个单位长度的路,蚂蚁们要从起点走到终点。蚂蚁们每走\(1\)个单位距离需要\(T\)秒钟。现在,出题人可以在路上修筑\(3\)种防御塔来阻挡蚂蚁的进攻,每个单位距离只能修筑\(1\)座塔,塔的作用分别如下:激光塔:蚂蚁在塔前时每秒会受到\(r\)点伤害。......
  • P9007 [入门赛 #9] 最澄澈的空与海 (Hard Version) 题解
    Updon2023.10.1408:21:修改了推式子和题意的一些小错误。前言一道恐怖的绿题。显然我认为应该是蓝题。(不过在这篇题解写到一半的时候升蓝了,感谢@StudyingFather。)名字挺好的。题意给定\(n\),求出满足以下条件的三元组\((x,y,z)\)的组数:\(x\ge0,z\ge1\)。\(......
  • Linux进程间通信 [补档-2023-07-27]
    Linux进程间通信10-1简介​在Linux下,进程之间相互独立,每个进程都有自己不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问。如果非要交换数据则必须通过内核,在内核中开辟一块缓冲区。假设有两个进程AB,他们之间想......
  • Linux的信号管理 [补档-2023-07-30]
    信号11-1简介:​信号只是表示某个信号,不可以携带大量信息,信号需要满足特点的条件才会产生。是一种特别的通信手段。11-2信号机制:​假设有两个进程A,B,现在进程A给进程B发送信号,进程B在收到信号之前会执行自己的代码,当收到信号后,无论执行到了哪里,都要暂停执......
  • Linux文件IO之二 [补档-2023-07-21]
    8-5linux系统IO函数:open函数:​函数原型:intopen(constchar*pathname,intflags,mode_tmode);​功能:打开一个文件并返回文件描述符。与c库中的fopen差不多​参数:pathname:要打开的文件路径名。flags:打开文件的标志O_RDONLY(只读)O_WRONLY(只写)O_RD......
  • Linux的进程管理 [补档-2023-07-25]
    Linux进程管理9-1并发与并行:​并发:在同一个cpu上,并且在一个时间段时,同时运行多个程序。比如在1000毫秒内,我们有5个程序需要执行,所以我们可以将1000毫秒分为5个200毫秒,让每个程序都占用200毫秒的cpu使用权,这样在1000毫秒内就可以执行5个程序。​并行:大于等......
  • [Keyence2019] Paper Cutting
    PaperCuttingLuoguAT_keyence2019_f题面翻译有一个\((H+1)\times(W+1)\)的网格,网格中有\(H\)条水平线和\(W\)条竖直线。你需要执行\(K\)次操作,每次沿一条水平线或竖直线将网格切开。定义一次操作的权值为切割后网格被切分的块数。定义一个操作序列的权值为\(K\)......