select模型详解
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下的实现
让fd_array动态变化
select模型详解相关推荐
- ASP.NET Core的配置(2):配置模型详解
在上面一章我们以实例演示的方式介绍了几种读取配置的几种方式,其中涉及到三个重要的对象,它们分别是承载结构化配置信息的Configuration,提供原始配置源数据的ConfigurationProvi ...
- Socket模型详解
Socket模型详解 两种I/O模式 一.选择模型 二.异步选择 三.事件选择 四.重叠I/O模型 五.完成端口模型 五种I/O模型的比较 两种I/O模式 1. 两种I/O模式 阻塞模式:执行I/O操 ...
- Django MVT模型详解
MVT模型详解 ORM简介 使用MySql数据库 开发流程 使用数据库生成模型类 Model 定义模型 定义属性 字段类型 字段选项 关系 元选项 示例演示 测试数据 类的属性 管理器Manager ...
- 网络套接字编程之IO模型详解
网络套接字编程之IO模型详解 本文主要参考自<UNIX网络编程>(第1卷)(套接口API第3版) Unix下可用的五种I/O模型有: 阻塞式I/O 非阻塞式I/O I/O复用(select ...
- Reactor 模型详解
研究背景 其实我们在研究netty的时候我们必定绕不过NIO的,也必定必须研究一下这个Reactor模型的,如果不进行这个Reactor模型和NIO知识点的研究,那么我们必定掌握不了Netty的精髓, ...
- 【后端开发】Reactor 模型详解
研究背景 其实我们在研究netty的时候我们必定绕不过NIO的,也必定必须研究一下这个Reactor模型的,如果不进行这个Reactor模型和NIO知识点的研究,那么我们必定掌握不了Netty的精髓, ...
- 使用pickle保存机器学习模型详解及实战(pickle、joblib)
使用pickle保存机器学习模型详解及实战 pickle模块实现了用于序列化和反序列化Python对象结构的二进制协议. "Pickling"是将Python对象层次结构转换为字节 ...
- Transformer 模型详解
Transformer 是 Google 的团队在 2017 年提出的一种 NLP 经典模型,现在比较火热的 Bert 也是基于 Transformer.Transformer 模型使用了 Self- ...
- TensorFlow Wide And Deep 模型详解与应用 TensorFlow Wide-And-Deep 阅读344 作者简介:汪剑,现在在出门问问负责推荐与个性化。曾在微软雅虎工作,
TensorFlow Wide And Deep 模型详解与应用 TensorFlow Wide-And-Deep 阅读344 作者简介:汪剑,现在在出门问问负责推荐与个性化.曾在微软雅虎工作,从事 ...
最新文章
- Magento入门基础 - 后台如何批量导入产品及产品图片
- C# Windows基础拾遗01—线条绘制篇
- Keep-Alive模式
- Intellij 14快捷键
- 八大看点丨第十届数据技术嘉年华精彩抢先速览
- Python 爬取 3 万条游戏评分数据,原来程序员最爱玩的游戏竟然是......
- 在C#中调用Java代码
- python完全支持面向对象编程_Python 面向对象编程概要
- 【Lingo】线性规划
- 双人贪吃蛇java 代码_java 双人贪吃蛇
- Java — set 和 list 集合练习题
- php考勤管理系统论文,基于PHP的高职院校学生考勤管理系统的研究
- 阿里云云盘扩容数据盘_Linux
- PS 导入笔刷和导入字体和导入滤镜
- html中图片放大镜效果图,HTML5使用不同精度的图片来实现图像放大镜效果
- 2021年中国嵌入式系统软件业务收入及业务收入结构分析[图]
- PDF编辑方法,怎么把PDF其中一页删除
- SEBank银行项目第一个星期的进度安排
- “诸神之眼”——Nmap端口扫描工具使用小手册
- 大话——从细分市场观商业模式