您的位置 首页 国产IC

需求了解Linux编程中的select

需要了解Linux编程中的select-select系统调用的的用途是:在一段指定的时间内,监听用户感兴趣的文件描述符上可读、可写和异常等事件。

select体系调用的的用处是:在一段指定的时刻内,监听用户感兴趣的文件描述符上可读、可写和反常等工作。

select 机制的优势

为什么会呈现select模型?

先看一下下面的这句代码:

int iResult = recv(s, buffer,1024);

这是用来接纳数据的,在默许的堵塞形式下的套接字里,recv会堵塞在那里,直到套接字衔接上有数据可读,把数据读到buffer里后recv函数才会回来,否则就会一向堵塞在那里。在单线程的程序里呈现这种状况会导致主线程(单线程程序里只要一个默许的主线程)被堵塞,这样整个程序被死在这儿,假如永 远没数据发送过来,那么程序就会被永久锁死。这个问题能够用多线程处理,可是在有多个套接字衔接的状况下,这不是一个好的挑选,扩展性很差。

再看代码:

int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);iResult = recv(s, buffer,1024);

这一次recv的调用不论套接字衔接上有没有数据能够接纳都会立刻回来。原因就在于咱们用ioctlsocket把套接字设置为非堵塞形式了。不过你盯梢一下就会发现,在没有数据的状况下,recv确实是立刻回来了,可是也回来了一个过错:WSAEWOULDBLOCK,意思便是恳求的操作没有成功完结。

看到这儿许多人或许会说,那么就重复调用recv并查看回来值,直到成功停止,可是这样做功率很成问题,开支太大。

select模型的呈现便是为了处理上述问题。
select模型的关键是运用一种有序的办法,对多个套接字进行统一管理与调度 。

如上所示,用户首要将需求进行IO操作的socket增加到select中,然后堵塞等候select体系调用回来。当数据抵达时,socket被激活,select函数回来。用户线程正式建议read恳求,读取数据并持续履行。

从流程上来看,运用select函数进行IO恳求和同步堵塞模型没有太大的差异,乃至还多了增加监督socket,以及调用select函数的额定操作,功率更差。可是,运用select今后最大的优势是用户能够在一个线程内一起处理多个socket的IO恳求。用户能够注册多个socket,然后不断地调用select读取被激活的socket,即可到达在同一个线程内一起处理多个IO恳求的意图。而在同步堵塞模型中,有必要经过多线程的办法才干到达这个意图。

select流程伪代码如下:

{ select(socket); while(1) { sockets = select(); for(socket in sockets) { if(can_read(socket)) { read(socket, buffer); process(buffer); } } }}

select相关API介绍与运用

#include #include #include #include int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *TImeout);

参数阐明:

maxfdp:被监听的文件描述符的总数,它比一切文件描述符调会集的文件描述符的最大值大1,由于文件描述符是从0开端计数的;

readfds、writefds、exceptset:别离指向可读、可写和反常等工作对应的描述符调集。

TImeout:用于设置select函数的超时时刻,即告知内核select等候多长时刻之后就抛弃等候。TImeout == NULL 表明等候无限长的时刻

TImeval结构体界说如下:

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

回来值:超时回来0;失利回来-1;成功回来大于0的整数,这个整数表明安排妥当描述符的数目。

以下介绍与select函数相关的常见的几个宏:

#include int FD_ZERO(int fd, fd_set *fdset); //一个 fd_set类型变量的一切位都设为 0int FD_CLR(int fd, fd_set *fdset); //铲除某个位时能够运用int FD_SET(int fd, fd_set *fd_set); //设置变量的某个方位位int FD_ISSET(int fd, fd_set *fdset); //测验某个位是否被置位

select运用典范:
当声明晰一个文件描述符集后,有必要用FD_ZERO将一切方位零。之后将咱们所感兴趣的描述符所对应的方位位,操作如下:

fd_set rset; int fd; FD_ZERO(&rset); FD_SET(fd, &rset); FD_SET(stdin, &rset);

然后调用select函数,拥塞等候文件描述符工作的到来;假如超越设定的时刻,则不再等候,持续往下履行。

select(fd+1, &rset, NULL, NULL,NULL);

select回来后,用FD_ISSET测验给定位是否置位:

if(FD_ISSET(fd, &rset) { … //do something }

下面是一个最简略的select的运用比如:

#include #include #include #include #include int main(){ fd_set rd; struct timeval tv; int err; FD_ZERO(&rd); FD_SET(0,&rd); tv.tv_sec = 5; tv.tv_usec = 0; err = select(1,&rd,NULL,NULL,&tv); if(err == 0) //超时 { printf(“select time out!\n”); } else if(err == -1) //失利 { printf(“fail to select!\n”); } else //成功 { printf(“data is available!\n”); } return 0;}

咱们运转该程序而且随意输入一些数据,程序就提示收到数据了。

深化了解select模型:

了解select模型的关键在于了解fd_set,为阐明便利,取fd_set长度为1字节,fd_set中的每一bit能够对应一个文件描述符fd。则1字节长的fd_set最大能够对应8个fd。

(1)履行fd_set set; FD_ZERO(&set); 则set用位表明是0000,0000。

(2)若fd=5,履行FD_SET(fd,&set);后set变为0001,0000(第5方位为1)

(3)若再参加fd=2,fd=1,则set变为0001,0011

(4)履行select(6,&set,0,0,0)堵塞等候

(5)若fd=1,fd=2上都产生可读工作,则select回来,此刻set变为0000,0011。留意:没有工作产生的fd=5被清空。

根据上面的评论,能够轻松得出select模型的特色:

(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表明一个文件描述符,则我服务器上支撑的最大文件描述符是512*8=4096。听说可调,还有说尽管可调,但调整上限受于编译内核时的变量值。

(2)将fd参加select监控集的一起,还要再运用一个数据结构array保寄存到select监控会集的fd,一是用于再select回来后,array作为源数据和fd_set进行FD_ISSET判别。二是select回来后会把曾经参加的但并无工作产生的fd清空,则每次开端select前都要从头从array获得fd逐个参加(FD_ZERO最早),扫描array的一起获得fd最大值maxfd,用于select的第一个参数。

(3)可见select模型有必要在select前循环加fd,取maxfd,select回来后运用FD_ISSET判别是否有工作产生。

用select处理带外数据

网络程序中,select能处理的反常状况只要一种:socket上接纳到带外数据。

什么是带外数据?

带外数据(out—of—band data),有时也称为加快数据(expedited data),
是指衔接双方中的一方产生重要工作,想要迅速地告诉对方。
这种告诉在现已排队等候发送的任何“一般”(有时称为“带内”)数据之前发送。
带外数据规划为比一般数据有更高的优先级。
带外数据是映射到现有的衔接中的,而不是在客户机和服务器间再用一个衔接。

咱们写的select程序常常都是用于接纳一般数据的,当咱们的服务器需求一起接纳一般数据和带外数据,咱们怎么运用select进行处理二者呢?

下面给出一个小demo:

#include #include #include #include #include #include #include #include #include #include int main(int argc, char* argv[]){ if(argc <= 2) { printf("usage: ip address + port numbers\n"); return -1; } const char* ip = argv[1]; int port = atoi(argv[2]); printf("ip: %s\n",ip); printf("port: %d\n",port); int ret = 0; struct sockaddr_in address; bzero(&address,sizeof(address)); address.sin_family = AF_INET; inet_pton(AF_INET,ip,&address.sin_addr); address.sin_port = htons(port); int listenfd = socket(PF_INET,SOCK_STREAM,0); if(listenfd < 0) { printf("Fail to create listen socket!\n"); return -1; } ret = bind(listenfd,(struct sockaddr*)&address,sizeof(address)); if(ret == -1) { printf("Fail to bind socket!\n"); return -1; } ret = listen(listenfd,5); //监听行列最大排队数设置为5 if(ret == -1) { printf("Fail to listen socket!\n"); return -1; } struct sockaddr_in client_address; //记载进行衔接的客户端的地址 socklen_t client_addrlength = sizeof(client_address); int connfd = accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength); if(connfd < 0) { printf("Fail to accept!\n"); close(listenfd); } char buff[1024]; //数据接纳缓冲区 fd_set read_fds; //读文件操作符 fd_set exception_fds; //反常文件操作符 FD_ZERO(&read_fds); FD_ZERO(&exception_fds); while(1) { memset(buff,0,sizeof(buff)); /*每次调用select之前都要从头在read_fds和exception_fds中设置文件描述符connfd,由于工作产生今后,文件描述符调集将被内核修正*/ FD_SET(connfd,&read_fds); FD_SET(connfd,&exception_fds); ret = select(connfd+1,&read_fds,NULL,&exception_fds,NULL); if(ret < 0) { printf("Fail to select!\n"); return -1; } if(FD_ISSET(connfd, &read_fds)) { ret = recv(connfd,buff,sizeof(buff)-1,0); if(ret <= 0) { break; } printf("get %d bytes of normal data: %s \n",ret,buff); } else if(FD_ISSET(connfd,&exception_fds)) //反常工作 { ret = recv(connfd,buff,sizeof(buff)-1,MSG_OOB); if(ret <= 0) { break; } printf("get %d bytes of exception data: %s \n",ret,buff); } } close(connfd); close(listenfd); return 0;}

用select来处理socket中的多客户问题

上面提到过,,运用select今后最大的优势是用户能够在一个线程内一起处理多个socket的IO恳求。在网络编程中,当涉及到多客户拜访服务器的状况,咱们首要想到的办法便是fork出多个进程来处理每个客户衔接。现在,咱们相同能够运用select来处理多客户问题,而不必fork。

服务器端

#include #include #include #include #include #include #include #include int main() { int server_sockfd, client_sockfd; int server_len, client_len; struct sockaddr_in server_address; struct sockaddr_in client_address; int result; fd_set readfds, testfds; server_sockfd = socket(AF_INET, SOCK_STREAM, 0);//树立服务器端socket server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = htonl(INADDR_ANY); server_address.sin_port = htons(8888); server_len = sizeof(server_address); bind(server_sockfd, (struct sockaddr *)&server_address, server_len); listen(server_sockfd, 5); //监听行列最多包容5个 FD_ZERO(&readfds); FD_SET(server_sockfd, &readfds);//将服务器端socket参加到调会集 while(1) { char ch; int fd; int nread; testfds = readfds;//将需求监督的描述符集copy到select查询行列中,select会对其修正,所以一定要分隔运用变量 printf(“server waiting\n”); /*无限期堵塞,并测验文件描述符变化 */ result = select(FD_SETSIZE, &testfds, (fd_set *)0,(fd_set *)0, (struct timeval *) 0); //FD_SETSIZE:体系默许的最大文件描述符 if(result < 1) { perror("server5"); exit(1); } /*扫描一切的文件描述符*/ for(fd = 0; fd < FD_SETSIZE; fd++) { /*找到相关文件描述符*/ if(FD_ISSET(fd,&testfds)) { /*判别是否为服务器套接字,是则表明为客户恳求衔接。*/ if(fd == server_sockfd) { client_len = sizeof(client_address); client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len); FD_SET(client_sockfd, &readfds);//将客户端socket参加到调会集 printf("adding client on fd %d\n", client_sockfd); } /*客户端socket中有数据恳求时*/ else { ioctl(fd, FIONREAD, &nread);//获得数据量交给nread /*客户数据恳求完毕,封闭套接字,从调会集铲除相应描述符 */ if(nread == 0) { close(fd); FD_CLR(fd, &readfds); //去掉封闭的fd printf("removing client on fd %d\n", fd); } /*处理客户数据恳求*/ else { read(fd, &ch, 1); sleep(5); printf("serving client on fd %d\n", fd); ch++; write(fd, &ch, 1); } } } } } return 0;}

客户端

//客户端#include #include #include #include #include #include #include #include int main() { int client_sockfd; int len; struct sockaddr_in address;//服务器端网络地址结构体 int result; char ch = 'A'; client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//树立客户端socket address.sin_family = AF_INET; address.sin_addr.s_addr = inet_addr(“127.0.0.1”); address.sin_port = htons(8888); len = sizeof(address); result = connect(client_sockfd, (struct sockaddr *)&address, len); if(result == -1) { perror(“oops: client2”); exit(1); } //第一次读写 write(client_sockfd, &ch, 1); read(client_sockfd, &ch, 1); printf(“the first time: char from server = %c\n”, ch); sleep(5); //第2次读写 write(client_sockfd, &ch, 1); read(client_sockfd, &ch, 1); printf(“the second time: char from server = %c\n”, ch); close(client_sockfd); return 0; }

运转流程:

客户端:发动->衔接服务器->发送A->等候服务器回复->收到B->再发B给服务器->收到C->完毕

服务器:发动->select->收到A->发A+1回去->收到B->发B+1曩昔

测验:咱们先运转服务器,再运转客户端

select总结:

select本质上是经过设置或许查看寄存fd标志位的数据结构来进行下一步处理。这样所带来的缺陷是:

1、单个进程可监督的fd数量被约束,即能监听端口的巨细有限。一般来说这个数目和体系内存联系很大,详细数目能够cat/proc/sys/fs/file-max观察。32位机默许是1024个。64位机默许是2048.

2、 对socket进行扫描时是线性扫描,即选用轮询的办法,功率较低:当套接字比较多的时分,每次select()都要经过遍历FD_SETSIZE个Socket来完结调度,不论哪个Socket是活泼的,都遍历一遍。这会糟蹋许多CPU时刻。假如能给套接字注册某个回调函数,当他们活泼时,主动完结相关操作,那就避免了轮询,这正是epoll与kqueue做的。

3、需求保护一个用来寄存很多fd的数据结构,这样会使得用户空间和内核空间在传递该结构时仿制开支大。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/bandaoti/ic/90080.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部