Linux select函数用法和原理

select函数的用法和原理

Linux上的select函数

select函数用于检测一组socket中是否有事件就绪.这里的事件为以下三类:

  1. 读事件就绪
    • socket内核中,接收缓冲区中的字节数大于或者等于低水位标记SO_RCVLOWAT,此时调用recread函数可以无阻塞的读取该文件描述符,并且返回值大于零
    • TCP连接的对端关闭连接,此时本端调用rrecvread函数对socket进行读操作,recvread函数返回0
    • 在监听的socket上有新的连接请求
    • socket尚有未处理的错误
  2. 写事件就绪
    • socket内核中,发送缓冲区中的可用字节数大于等于低水位标记时,可以无阻塞的写,并且返回值大于0
    • socket的写操作被关闭时,对一个写操作被关闭的socket进行写操作,会触发SIGPIPE信号
    • socket使用非阻塞connect连接成功或失败时
  3. 异常事件就绪

select()如下:

#include <sys/select.h>   int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

参数说明

nfds:Linux上的socket也叫作fd,将这个参数的值设置为所有需要使用select函数检测事件的fd中的最大值加1即nfds=max(fd1,fd2,...,fdn)+1
readfds:需要监听可读事件的fd集合
writefds:需要监听可写事件fd的集合
exceptfds:需要监听异常事件的fd集合
timeout:超时时间,即在这个参数设定的时间内检测这些fd的事件,超过这个时间后,select函数立即返回,这是一个timeval结构体

其定义如下:

struct timeval{      long tv_sec;   /*秒 */long tv_usec;  /*微秒 */   }

参数readfds,writefds,exceptfds的类型都是fd_set,这是一个结构体信息

定义如下

//#define __FD_SETSIZE		1024
#define __NFDBITS	(8 * (int) sizeof (__fd_mask))
#define	__FD_ELT(d)	((d) / __NFDBITS)
#define	__FD_MASK(d)	((__fd_mask) (1UL << ((d) % __NFDBITS)))/* fd_set for select and pselect.  */
typedef struct{/* XPG4.2 requires this member name.  Otherwise avoid the namefrom the global namespace.  */
#ifdef __USE_XOPEN//typedef long int __fd_mask;__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#endif} fd_set;/* 最大数量`fd_set'.  */
#define	FD_SETSIZE		__FD_SETSIZE

假设未定义__USE_XOPEN整理一年

typedef struct{
//typedef long int __fd_mask;long int fds_bits[__FD_SETSIZE / __NFDBITS];} fd_set;

将一个fd添加到fd_set这个集合中时需要使用FD_SET宏,其定义如下:

void FD_SET(fd, fdsetp)

实现如下:

#define	FD_SET(fd, fdsetp)	__FD_SET (fd, fdsetp)

__FD_SET (fd, fdsetp)实现如下:

/* We don't use `memset' because this would require a prototype andthe array isn't too big.  */
# define __FD_ZERO(set)  \do {									      \unsigned int __i;							      \fd_set *__arr = (set);						      \for (__i = 0; __i < sizeof (fd_set) / sizeof (__fd_mask); ++__i)	      \__FDS_BITS (__arr)[__i] = 0;					      \} while (0)#endif	/* GNU CC */#define __FD_SET(d, set) \((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d)))

举个例子,假设现在fd的值为43,那么在数组下表为0的元素中第43个bit被置为1


再Linux上,向fd_set集合中添加新的fd时,采用位图法确定位置;在windows中添加fd至fd_set的实现规则依次从数组第0个位置开始向后递增


也就是说,FD_SET宏本质上是在一个有1024个连续bit的数组的第fd位置置1.

同理,FD_CLR删除一个fd的原理,也就是将数组的第fd位置置为0

image-20210706144042275

实例;

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <cstring>
#include <sys/time.h>
#include <vector>
#include <cerrno>//Customize the value representing invalid fd
#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
#define INVALID_FD -1
int main(int argc,char * argv[])
{//create a listen socketint listenfd = socket(AF_INET,SOCK_STREAM,0);if(listenfd == INVALID_FD){printf("创建监听socket失败");return -1;}//init server addrsockaddr_in bindaddr{};bindaddr.sin_family = AF_INET;bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);bindaddr.sin_port= htons(3000);if(bind(listenfd,(struct sockaddr*) &bindaddr, sizeof(bindaddr)) == -1){printf("绑定socket失败");close(listenfd);return -1;}//start listenif(listen(listenfd,SOMAXCONN) == -1){printf("监听失败!");close(listenfd);return -1;}//Store the client's socket datastd::vector<int> clientfds;int maxfd;while(true){fd_set readset;FD_ZERO(&readset);FD_SET(listenfd,&readset);maxfd = listenfd;unsigned long clientfdslength = clientfds.size();for (int i = 0; i < clientfdslength; ++i){if(clientfds[i] != INVALID_FD){FD_SET(clientfds[i],&readset);if(maxfd<clientfds[i])maxfd = clientfds[i];}}timeval tm{};tm.tv_sec = 1;tm.tv_usec =0;int  ret = select(maxfd+1,&readset, nullptr, nullptr,&tm);if(ret == -1){if (errno != EINTR)break;}//time outelse if (ret ==0 ){continue;}else{//event detected on a socketif (FD_ISSET(listenfd,&readset)){sockaddr_in clientaddr{};socklen_t  clientaddrlen = sizeof(clientaddr);//accept client connectionint clientfd = accept(listenfd,(struct sockaddr *)&clientaddr,&clientaddrlen);if (clientfd == INVALID_FD){break;}std::cout<<"接受到客户端连接,fd:"<<clientfd<<std::endl;clientfds.push_back(clientfd);}else{//Assume that the data length sent by the client is not greater than 63char recvbuf[64];unsigned long clientfdslength = clientfds.size();for (int i = 0; i < clientfdslength; ++i){if(clientfds[i] != INVALID_FD && FD_ISSET(clientfds[i],&readset)){memset(recvbuf,0, sizeof(recvbuf));//accept dataint length = recv(clientfds[i],recvbuf,64,0);//recv的返回值等于0,表示客户端关闭了连接if (length <=0 ){//errorstd::cout<<"error"<<clientfds[i]<<std::endl;close(clientfds[i]);clientfds[i] == INVALID_FD;continue;}std::cout<<"clientfd: "<<clientfds[i]<<", recv data:"<<recvbuf<<std::endl;}}}}}//close all client socketint clientfdslength = clientfds.size();for (int i = 0; i < clientfdslength; ++i){if(clientfds[i] != INVALID_FD){close(clientfds[i]);}}//close socketclose(listenfd);return 0;
}
#pragma clang diagnostic pop

使用nc -v 127.0.0.1 3000来模拟客户端,打开三个终端

image-20210706165026755

关于以上代码,需要注意以下几点:

  1. select函数在调用前后可能会修改readfds,writefds,exceptfds所以想在下次调用select函数时服用这些fd_set变量需要重新清零,添加内容

    for (int i = 0; i < clientfdslength; ++i){if(clientfds[i] != INVALID_FD){FD_SET(clientfds[i],&readset);if(maxfd<clientfds[i])maxfd = clientfds[i];}}
    
  2. select函数也会修改timeval结构体的值,如果想复用这些变量,需要重新设置

    timeval tm{};tm.tv_sec = 1;tm.tv_usec =0;
    
  3. 如果将selecttimeval参数设置为NULL,则select函数会一直阻塞下去

windows上的socket函数

在windows上,select函数结束后,不会修改timeval函数

TCP网络编程的基本流程

Linux与C++11多线程编程(学习笔记)

Linux select函数用法和原理

socket的阻塞模式和非阻塞模式(send和recv函数在阻塞和非阻塞模式下的表现)

connect函数在阻塞和非阻塞模式下的行为

获取socket对应的接收缓冲区中的可读数据量

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/408167.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

常见日期方法荟萃

一.如何获得当月有多少天 intmSystem.DateTime.DaysInMonth(System.DateTime.Now.Year,System.DateTime.Now.Month);二.日期型格式处理通用方法1.在webconfig中配置如下<add key"ShortDatePattern"value"MM-dd-yyyy"/><add key"LongDatePatt…

socket的阻塞模式和非阻塞模式(send和recv函数在阻塞和非阻塞模式下的表现)

socket的阻塞模式和非阻塞模式 无论是Windows还是Linux,默认创建socket都是阻塞模式的 在Linux中,可以再创建socket是直接将它设置为非阻塞模式 int socket (int __domain, int __type, int __protocol)将__type增加SOCK_NOBLOCK 不仅如此,在Linux上直接利用accept函数返回…

connect函数在阻塞和非阻塞模式下的行为

connect函数在阻塞和非阻塞模式下的行为 当socket使用阻塞模式时,connect函数会阻塞到有明确结果才会返回,如果网络环境较差,可能要等一会,影响体验, 为了解决这个问题,我们使用异步connect技术 创建socket,将socket设置为非阻塞模式 调用connect函数,此时无论connect函数是…

获取socket对应的接收缓冲区中的可读数据量

获取socket对应的接收缓冲区中的可读数据量 本文介绍如何获取当前socket对应的接收缓冲区的可读数据量 在Linux上可以使用ioctl函数 #include <sys/ioctl.h>int ioctl (int __fd, unsigned long int __request, ...)来看一个例子: #include <sys/types.h> #in…

Linux epoll的用法

Linux epoll的用法 epollfd_create函数 #include <sys/epoll.h>int epoll_create (int __size)参数含义__size此参数从Linux 2.6.8后就不再使用了,但必须设置成大于零的值 返回值含义>0可用的epollfd-1调用失败 epollfd_ctl函数 有了epollfd,我们需要将要检测事件…

windows网络编程

windows网络编程 TCP编程 服务端 这里我们有几点需要注意: 使用WSAStartup初始化网络库,即将与socket函数相关dll文件加载到进程地址空间中退出时,使用WSACleanup()卸载相关dll文件与Linux使用close函数关闭socket不同,windows需要使用closesocket函数关闭socket WSAStart…

TCP服务器epoll的多种实现

TCP服务器epoll的多种实现 对于网络IO会涉及到两个系统对象 用户空间中进程或者线程操作系统内核 比如发生read操作时就会经历两个阶段 等待数据就绪将数据从内核缓冲区拷贝到用户缓冲区 由于各个阶段多有不同的情况,一组合么就产生了多种网络 IO 模型 阻塞IO 在Linux中…

侯捷面向对象高级编程(二)

侯捷面向对象高级编程(二) 转换函数 转换函数没有返回值,返回值就是double即函数名,不需要自己写因为转换函数一般不会改变其中内容,所以要加const限定 两条路都可以走,就回产生歧义,报错 explict禁止自动转换,于是4无法转转换为Fraction pointer-like cliasses ->作用之后…

拒绝了对对象 'sp_sdidebug'(数据库 'master',所有者 'dbo')的 EXECUTE 权限

在.net中调用时出现“拒绝了对对象 sp_sdidebug&#xff08;数据库 master&#xff0c;所有者 dbo&#xff09;的 EXECUTE 权限”的错误的解决办法。该问题是我在用指定的URL启动项目后&#xff0c;再“附加进程”后运行程序时出现的。该问题主要是.net2005的调试机制引起的&am…