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

多路io复用Select [补档-2023-07-16]

时间:2024-01-13 18:14:21浏览次数:27  
标签:文件 07 16 补档 描述符 client fd select 客户端

select

2.1 简介

​ select函数可以用于实现高效的多路复用 I/O,同时处理多个文件描述符的事件,包括监听可读、可写和异常条件,具有阻塞和非阻塞模式,并可以设置超时时间。这使得程序能够高效地处理并发任务,提高性能和响应性。

2.2 select函数

头文件:#include <sys/select>

函数原型:int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);

函数参数:

nfds:监视文件描述符的最大值加一(文件描述符集合中所有的文件描述符的最大值+1)

readfds:指向一个文件描述符集合,用于监视是否有数据可读。

writefds:指向一个文件描述符集合,用于监视是否可以写入数据。

exceptfds:指向一个文件描述符集合,用于监视是否有异常情况。

timeout:指向一个struct timeval结构体,表示超时时间,即select函数最多等待的时间。

结构体struct timeval 的成员:

​ time_t tv_sec:表示秒数。

​ suseconds_t tv_usec:表示微妙数。

函数返回值

​ 成功返回准备就绪的文件描述符总数。

​ 监听超时则返回0。

​ 发生错误返回-1。

2.3 select的几个宏函数

2.3.1将指定的一个文件描述符从集合中清除

​ 函数原型:FD_CLR(int fd, fd_set *set);

​ 函数参数:fd为指定的文件描述符,set为指定的文件描述符集合

2.3.2 将指定的文件描述符集合清空,所有位置都置为0

​ 函数原型:FD_ZERO(fd_set *set);

​ 函数参数:set为指定的文件描述符集合

2.3.3 将指定的文件描述符加入到指定的集合中

​ 函数原型:FD_SET(int fd, fd_set *set);

​ 函数参数:fd为指定的文件描述符,set为指定的文件描述符集合

2.3.4 判断指定的文件描述符是否在某个文件描述符集合中

​ 函数原型:FD_ISSET(int fd, fd_set *set);

​ 函数参数:fd为指定的文件描述符,set为指定的文件描述符集合

2.4 使用select创建一个多路io复用的服务端:

​ 第一步:创建套接字。

​ 第二步:准备一些变量和常量,用于记录最大文件描述符,客户端发来的数据,循环次数,条件判断等待。

​ 第三步:初始化本地(服务器)地址结构体sockaddr_in。

​ 第四步:将套接字与本地地址绑定,并且建议判断一下是否绑定成功。

​ 第五步:切换为监听状态。

​ 第六步:进入一个无限循环然后持续监听。

​ 马上进入循环时初始化文件描述符集合,进入后将刚才监听的文件描述符添加到里面。

​ 在所有已连接的客户端文件描述符集合中寻找最大的文件描述符,然后记录下来。

​ 使用select函数对整个文件描述符集合进行监听,如果监听失败则退出程序,如果有文件描述符可操作, 则进行具体操作。

​ 当select监听有变化则检测监听的套接字是否有新的连接请求,如果有则使用accept接收连接,并且将accept返回的已经和客户端建立连接的描述符加入到已连接的客户端数组中。

​ 处理客户端的请求。遍历已连接的客户端数组,逐个判断每个客户端文件描述符数组中的元素是否中文件描述符集合中,如果在则代表有数据可读。

​ 从客户端接收数据,并且进行处理。如果无法接收数据则关闭客户端文件描述符然后将其从描述符集合中移除。否则就对客户端发来的数据进行相关操作。

​ 第七步:当循环结束后,记得关闭所有已连接的客户端文件描述符和服务器文件描述符。

2.5 使用select创建的多路io的服务端代码示例

点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>

 

#define MAX_CLIENTS 100 //最大客户端描述符数
#define BUF_SIZE 128 //字符串数组的最大长度

 

int main()

{

  int client_fds[MAX_CLIENTS]; //用于存储客户端文件描述符,用来辅助文件描述符集使用

  int server_fd; //服务端文件描述符(用于监听)

  fd_set readfds; //可读文件描述符集

  char data[BUF_SIZE];//用于存储接收来自客户端的信息

  int maxfd; //最大文件描述符

  int i;//下面可能会用到循环,它用来循环

  int retval; //用于记录select的值

 

  //创建套接字,ipv4,tcp

  server_fd = socket(AF_INET, SOCK_STREAM, 0);

  if (server_fd == -1) {

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

​     exit(-1);

  }

  //分别用于存储服务端和客户端的地址信息

  struct sockaddr_in server_addr, client_addr;

  //初始化本地地址信息

  server_addr.sin_family = AF_INET;

  server_addr.sin_port = htons(10066);

  server_sddr.sin_addr.s_addr = htonl(INADDR_ANY);

  

  //将套接字与本地服务器地址绑定

  int ret;//用于检测绑定或者切换至监听是否成功

  ret = bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));

  if (ret == -1) {

​     printf("本地地址与套接字失败!\n");

​     exit(-1);

  }

 

  //切换监听

  ret = listen(server_fd, MAX_CLIENTS);

  if (ret == -1) {

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

​     exit(-1);

  }

 

  printf("服务器开始监听,监听端口:10066 监听地址:任意\n");

 

  //因为接下来的select需要接收一个集合的最大描述符,先用一个已有的文件描述符给他赋值

  maxfd = server_fd;

 

  //初始化客户端文件描述符集合,-1代表可用

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

​     client_fds[i] = -1;

  }

 

  FD_ZERO(&readfds); //初始化可读文件描述符集

 

  while (1) {

​     FD_SET(server_fd, &readfds); //将当前监听的描述符加入到里面

 

​     //将已连接的文件描述符加入到集合中

​     for (i = 0; i < MAX_CLIENTS; i++) {

​       int client_fd = client_fds[i];

​       if (client_fd != -1) {

​         //不等于-1则代表在被使用中代表已连接

​         FD_SET(client_fd, &readfds);

​         if (client_fd > maxfd) {

​           //如果当前已连接的文件描述符比之前的最大描述符要大,记得交换

​           //因为select函数接收的是集合中值最大的

​           maxfd = client_fd; 

​         }

​       }

​     }

 

​     //使用select函数进程监听整个集合

​     retval = select(maxfd + 1, &readfds, NULL, NULL, NULL);

​     if (retval == -1) {

​       //等于-1代表监听失败

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

​       exit(-1);

​     }

​     else if (retval > 0) {

​       //大于0代表描述符集readfds有变化

​       //检测socket_fd是否有新的链接

​       if (FD_ISSET(server_fd, &readfds)) {

​         socklen_t client_len = sizeof(client_addr);

​         int client_fd = accept(server_fd,(struct sockaddr *)&client_addr,&client_len);

​         if (client_fd == -1) {

​           printf("接收链接失败!!!\n");

​         }

​         else {

​           //将刚才接收到的新的来自客户端的socket添加到结合中

​           for (i = 0; i < MAX_CLIENTS; i++) {

​             if (client_fds[i] == -1) {

​                //-1则代表当前数组位置可用

​                client_fds[i] = client_fd;

​                break;

​             }

​           }

 

​         }

​       }

 

​       //处理来自客户端的请求

​       for (i = 0; i < MAX_CLIENTS; i++) {

​         int client_fd = client_fds[i];

​         if (client_fd != -1 && FD_ISSET(client_fd, &readfds)) {

​           //首先数组元素是被使用的状态,并且要判断数组元素i对应的描述符是否中集合中

​           memset(data, 0, BUF_SIZE);

 

​           //接收信息

​           ssize_t n = recv(client_fd, data, sizeof(data),0);

​           if (n <= 0) {

​             printf("未能接收客户端数据!\n");

​             close(client_fd);//关闭文件描述符

​             client_fds[i] = -1;//记得将这个坑置为-1,因为咱们操作完了

​           }

​           else {

​             //回应客户端

​             printf("服务端已经接收到客户端数据:\n%s\n",data);

​             if (send(client_fd, data, sizeof(data), 0) == -1) {

​                printf("回送服务端数据失败!\n");

​             }

​           }

​         }

​       }

 

 

​     }

 

  }

 

  //用完了记得循环关闭链接

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

​     int client_fd = client_fds[i];

​     if (client_fd != 1) {

​       close(client_fd);

​     }

  }

 

  close(server_fd);

  return 0;

 

}

2.6 select的优缺点

select优点:

​ 1 一个进程可以支持多个客户端

​ 2 select支持跨平台

select缺点:

​ 1 代码编写困难

​ 2 会涉及到用户区到内核区的来回拷贝,销毁资源大。

​ 3 当客户端多个连接, 但少数活跃的情况, select效率较低

​ 例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低 下

4 最大支持1024个客户端连接

​ select最大支持1024个客户端连接不是有文件描述符表最多可以支持1024个文件描述符限制的, 而是由 FD_SETSIZE=1024限制的.

​ FD_SETSIZE=1024 fd_set使用了该宏, 当然可以修改内核, 然后再重新编译内核, 一般不建议这么做.

标签:文件,07,16,补档,描述符,client,fd,select,客户端
From: https://www.cnblogs.com/xiaobai1523/p/17962687

相关文章

  • ARC169
    ARC169前言学校晚饭后不让来机房,时间卡的很死。基本赶不上赛时AtCoder,更不能谈codeforces了。这就导致到现在一场ARC没参加过。于是今天VP了一下,A题很水十分钟碾过去了,B题先想到了一个假贪心,答题思路不变稍微改改改成dp就可以了。C题写的比B还快,比较套路,一眼dp。D......
  • 多路io复用pool [补档-2023-07-19]
    多路IO-poll3.1简介​poll的机制与select类似,他们都是让内核在以线性的方法对文件描述符集合进行检测,根据描述符的状态进行具体的操作。并且poll和select在检测描述符集合时,会在检测的过程中频繁的进行用户区和内核区的拷贝,随着文件描述符集合中的数量增大,开销也随之增大,效......
  • 多路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文件那样......
  • 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个程序。​并行:大于等......