1.select模型原理

使用select函数检查文件描述符上是否有io事件发生,包括可读,可写以及异常

select参数和返回值意义如下:

int select (

IN int nfds,                           //0,无意义

IN OUT fd_set* readfds,      //检查可读性

IN OUT fd_set* writefds,     //检查可写性

IN OUT fd_set* exceptfds,  //例外数据

IN const struct timeval* timeout);    //函数的返回时间

参数说明:

第一个参数nfds在linux表示要监视的最大文件描述符+1,在windows下为0

第二个参数readfds检查文件描述符集合可读

第三个参数writefds检查文件描述符集合可写

第四个参数exceptfds检查文件描述符集合异常

第五个参数timeout结构如下

struct  timeval {

long    tv_sec;        //秒

long    tv_usec;     //毫秒

};

设置select函数返回的等待时间

如果参数timeout设为:

NULL,则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件。

0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。

特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。

返回值:

执行成功则返回文件描述符状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,没有返回;当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。错误值可能为:
EBADF 文件描述词为无效的或该文件已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值。
ENOMEM 核心内存不足

select返回fd_set中可用的套接字个数。
系统调用:

fd_set是一个SOCKET集合(数组),以下宏可以对该集合进行操作:

FD_CLR( s, *set) 从集合set删除句柄s;

FD_ISSET( s, *set) 检查句柄s是否存在与集合set中;

FD_SET( s, *set )把句柄s添加到集合set中;

FD_ZERO( *set ) 把set队列初始化集合成空.

2.select工作流程

1:用FD_ZERO宏来初始化我们感兴趣的fd_set。

也就是select函数的第二三四个参数。

2:用FD_SET宏来将套接字句柄分配给相应的fd_set。

如果想要检查一个套接字是否有数据需要接收,可以用FD_SET宏把套接接字句柄加入可读性检查集合中

3:调用select函数。

如果该套接字没有数据需要接收,select函数会把该套接字从可读性检查集合中删除掉,

4:用FD_ISSET对套接字句柄进行检查。

如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操 作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了, 马上可以读取成功而不会被阻塞。

3.使用实例

下面给出一个基于udp组播在windows下的实现,linux下可能略有不同

#include <winsock2.h>
#include <mswsock.h>
#include <MSTcpIP.h>
#include <errno.h>
#include <ws2ipdef.h>
#include <process.h>#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "mswsock.lib")
#define MAX_FD 16
fd_set fdread;
SOCKET fd_arr[MAX_FD] = {0};
unsigned int __stdcall threadfunc(void *)
{while (1){fd_set fd_tmp = fdread;timeval tmv;tmv.tv_sec = 1;tmv.tv_usec = 0;int ret = select(0, &fd_tmp, NULL, NULL, &tmv);if (ret < 0){printf("select failed:%d\n",ret);break;}else{for (unsigned int i=0;i<fd_tmp.fd_count;i++){SOCKET fd = fd_tmp.fd_array[i];if (fd > 0){SOCKADDR_IN client_addr;int nlen_addr = sizeof(SOCKADDR_IN);char buff[40960]={0};int nrecv = recvfrom(fd, buff, 40960, 0, (SOCKADDR *)&client_addr, &nlen_addr);if (nrecv < 0){printf("recvfrom error:%d\n", WSAGetLastError());continue;}printf("sock:%d src addr:%s:%d recv data:%s\n", fd, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port),buff);}}/*for (int i=0;i<MAX_FD;i++){SOCKET fd = fd_arr[i];if (FD_ISSET(fd, &fd_tmp)){}}*/}}return 0;
};#define MULTI_IP "239.0.0.37"#define RECV_START_PORT 16000#define   LOCAL_BIND_PORT 20260int _tmain(int argc, _TCHAR* argv[])
{WSADATA wsaData;if (WSAStartup(MAKEWORD(2,2), &wsaData)!=0){printf("init socket failed\n");return -1;}FD_ZERO(&fdread);for (int i=0;i<MAX_FD;i++){SOCKADDR_IN udplocal;udplocal.sin_family = AF_INET;udplocal.sin_addr.s_addr = inet_addr("0.0.0.0");//htonl(ADDR_ANY)udplocal.sin_port = htons(RECV_START_PORT+2*i);SOCKET fd_local= socket(PF_INET, SOCK_DGRAM, IPPROTO_IP | IPPROTO_UDP);if (fd_local == INVALID_SOCKET){printf("create socket() failed\n");return -1;}//int nbroadcast = 1;//setsockopt(fd_local, SOL_SOCKET, SO_BROADCAST, (char *)&nbroadcast, sizeof(int));int err = bind(fd_local, (SOCKADDR *)&udplocal, sizeof SOCKADDR_IN);if (err != 0){printf("bind() socket failed\n");return -1;}//char buff[1024]={0};//memset(buff, 0x0, 1024);//add multicast group IP_MREQ mreq;mreq.imr_interface.s_addr = htonl(ADDR_ANY);mreq.imr_multiaddr.s_addr = inet_addr(MULTI_IP);setsockopt(fd_local, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char*)&mreq, sizeof mreq);//set client socket ttlint ttl_value = 4;if (setsockopt(fd_local, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&ttl_value, sizeof(ttl_value)) != 0){printf("setsockopt multicast_ttl failed\n");return -1;}fd_arr[i] = fd_local;FD_SET(fd_local,&fdread);}::_beginthreadex(NULL,0,&threadfunc,NULL,0,NULL);printf("select io model server thread start running\n");getchar();for (int i=0;i<MAX_FD;i++){//shutdown(fd_arr[i], 1);closesocket(fd_arr[i]);}WSACleanup();return 0;
}

linux下检查键盘stdio输入

    #include<sys/time.h>  #include<sys/types.h>  #include<unistd.h>  #include<string.h>  #include<stdlib.h>  #include<stdio.h>  int main()  {  char buf[10]="";  fd_set rdfds;  struct timeval tv;  int ret;  FD_ZERO(&rdfds);  FD_SET(0,&rdfds);   //文件描述符0表示stdin键盘输入  tv.tv_sec = 3;  tv.tv_usec = 500;  ret = select(1,&rdfds,NULL,NULL,&tv);  if(ret<0)  printf("\n selcet error");  else if(ret == 0)  printf("\n select timeout");  else  printf("\n ret = %d",ret);  if(FD_ISSET(1,&rdfds))  //如果有输入,从stdin中获取输入字符  {  printf("\n reading");  fread(buf,9,1,stdin);  }  write(1,buf,strlen(buf));  printf("\n %d \n",strlen(buf));  return 0;  }  //执行结果ret = 1.  

4.总结讨论

1.select为何效率低

通过select的代码流程,我们发现首先要将文件描述符循环拷贝到select调用的临时集合,内核在调用select时要将用户态数组拷贝到内核态并执行轮询操作,轮询完成后将有事件发送的文件描述符拷贝到select轮询后的集合,并将内核太数据拷贝到用户态,处理时在循环处理返回集合,这里至少3次的循环和数据拷贝,并有用户态数据到内核态,内核态到用户态数据的拷贝,epoll为文件描述符建立一个以红黑树结构的文件系统,大大提高了搜索效率,另外在文件描述符通过epoll_ctl时已经将文件描述符拷贝到内核中,并没有文件描述符的在内核与用户之间的来回拷贝,epoll_wait后之间返回事件节点,不用轮询集合了,iocp也是在套接字和完成端口关联时拷贝到内核态,并在完成后放在完成队列中,用户直接取出处理,不用在循环集合,总结起来就两点,一是避免了大量文件描述符集合的拷贝及重复的处理流程,二是内核在搜索文件描述符可用事件的方法高效

2.如何突破64的限制

#ifndef FD_SETSIZE
#define FD_SETSIZE      64
#endif /* FD_SETSIZE */typedef struct fd_set {u_int fd_count;               /* how many are SET? */SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

方法1:修改宏定义
linux和windows下重定义FD_SETSIZE宏的大小,linux下修改内核FD_SETSIZE宏定义

方法2:分段轮询

使用一个select轮询多个文件描述符集合,完成一个集合轮询后,切换到下一个集合,轮询的数量可以达到n*64

方法3:多线程

使用多个线程select,每个线程中select轮询64个文件描述符,轮询的文件描述符数量可达到64*n

方法4:动态数组

linux内核代码中有很多零长度数组的运用,只不过不是c/c++标准,由于轮询的是一个数组,那么也可以定义为动态数组;

这也是libevent的做法,这是libevent在win32下的实现

struct win_fd_set {
u_int fd_count;
SOCKET fd_array[1];
};
使用时
win_fd_set * Set = (win_fd_set*)malloc(sizeof(win_fd_set) + sizoef(SCOEKT) * 10);
让fd_array动态变化
Set->fd_array 可以放11 个 SOCKET,因为动态开辟的内存控件足够存放11个SOCKET。

select模型详解相关推荐

  1. ASP.NET Core的配置(2):配置模型详解

    在上面一章我们以实例演示的方式介绍了几种读取配置的几种方式,其中涉及到三个重要的对象,它们分别是承载结构化配置信息的Configuration,提供原始配置源数据的ConfigurationProvi ...

  2. Socket模型详解

    Socket模型详解 两种I/O模式 一.选择模型 二.异步选择 三.事件选择 四.重叠I/O模型 五.完成端口模型 五种I/O模型的比较 两种I/O模式 1. 两种I/O模式 阻塞模式:执行I/O操 ...

  3. Django MVT模型详解

    MVT模型详解 ORM简介 使用MySql数据库 开发流程 使用数据库生成模型类 Model 定义模型 定义属性 字段类型 字段选项 关系 元选项 示例演示 测试数据 类的属性 管理器Manager ...

  4. 网络套接字编程之IO模型详解

    网络套接字编程之IO模型详解 本文主要参考自<UNIX网络编程>(第1卷)(套接口API第3版) Unix下可用的五种I/O模型有: 阻塞式I/O 非阻塞式I/O I/O复用(select ...

  5. Reactor 模型详解

    研究背景 其实我们在研究netty的时候我们必定绕不过NIO的,也必定必须研究一下这个Reactor模型的,如果不进行这个Reactor模型和NIO知识点的研究,那么我们必定掌握不了Netty的精髓, ...

  6. 【后端开发】Reactor 模型详解

    研究背景 其实我们在研究netty的时候我们必定绕不过NIO的,也必定必须研究一下这个Reactor模型的,如果不进行这个Reactor模型和NIO知识点的研究,那么我们必定掌握不了Netty的精髓, ...

  7. 使用pickle保存机器学习模型详解及实战(pickle、joblib)

    使用pickle保存机器学习模型详解及实战 pickle模块实现了用于序列化和反序列化Python对象结构的二进制协议. "Pickling"是将Python对象层次结构转换为字节 ...

  8. Transformer 模型详解

    Transformer 是 Google 的团队在 2017 年提出的一种 NLP 经典模型,现在比较火热的 Bert 也是基于 Transformer.Transformer 模型使用了 Self- ...

  9. TensorFlow Wide And Deep 模型详解与应用 TensorFlow Wide-And-Deep 阅读344 作者简介:汪剑,现在在出门问问负责推荐与个性化。曾在微软雅虎工作,

    TensorFlow Wide And Deep 模型详解与应用 TensorFlow Wide-And-Deep 阅读344  作者简介:汪剑,现在在出门问问负责推荐与个性化.曾在微软雅虎工作,从事 ...

最新文章

  1. Magento入门基础 - 后台如何批量导入产品及产品图片
  2. C# Windows基础拾遗01—线条绘制篇
  3. Keep-Alive模式
  4. Intellij 14快捷键
  5. 八大看点丨第十届数据技术嘉年华精彩抢先速览
  6. Python 爬取 3 万条游戏评分数据,原来程序员最爱玩的游戏竟然是......
  7. 在C#中调用Java代码
  8. python完全支持面向对象编程_Python 面向对象编程概要
  9. 【Lingo】线性规划
  10. 双人贪吃蛇java 代码_java 双人贪吃蛇
  11. Java — set 和 list 集合练习题
  12. php考勤管理系统论文,基于PHP的高职院校学生考勤管理系统的研究
  13. 阿里云云盘扩容数据盘_Linux
  14. PS 导入笔刷和导入字体和导入滤镜
  15. html中图片放大镜效果图,HTML5使用不同精度的图片来实现图像放大镜效果
  16. 2021年中国嵌入式系统软件业务收入及业务收入结构分析[图]
  17. PDF编辑方法,怎么把PDF其中一页删除
  18. SEBank银行项目第一个星期的进度安排
  19. “诸神之眼”——Nmap端口扫描工具使用小手册
  20. 大话——从细分市场观商业模式

热门文章

  1. mysql union join_MySQL 超新手入门(5) JOIN 与 UNION 查询
  2. oracle9i解密rewrap,ORACLE9I+DATAGUARD+RMAN
  3. typedef的用途
  4. 【Socket网络编程】4.tcp和udp的客户端和服务端收发流程
  5. Spring Boot【快速入门】
  6. Dubbo 源码分析 - 服务引用
  7. 双边滤波器的原理及实现
  8. 有关内存释放的一些问题
  9. 为什么算法渐进复杂度中对数的底数总为2
  10. openssl下开发sm4-gcm-ciphers