源地址:http://blog.csdn.net/zi_jin/article/details/4214359

Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。下面详细介绍一下! 
Select的函数格式(我所说的是Unix系统下的伯克利socket编程,和windows下的有区别,一会儿说明): 
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout); 
先说明两个结构体: 
第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO(fd_set *),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set *),将一个给定的文件描述符从集合中删除FD_CLR(int ,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。一会儿举例说明。 
第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。 
具体解释select的参数: 
int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。 
fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。 
fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。 
fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。 
struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。 
返回值: 
负值:select错误 正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件 
在有了select后可以写出像样的网络程序来!举个简单的例子,就是从网络上接受数据写入一个文件中。 
例子: 
main() 

int sock; 
FILE *fp; 
struct fd_set fds; 
struct timeval timeout={3,0}; //select等待3秒,3秒轮询,要非阻塞就置0 
char buffer[256]={0}; //256字节的接收缓冲区 
/* 假定已经建立UDP连接,具体过程不写,简单,当然TCP也同理,主机ip和port都已经给定,要写的文件已经打开 
sock=socket(...); 
bind(...); 
fp=fopen(...); */ 
while(1) 

FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化 
FD_SET(sock,&fds); //添加描述符 
FD_SET(fp,&fds); //同上 
maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1 
switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用 

case -1: exit(-1);break; //select错误,退出程序 
case 0:break; //再次轮询 
default: 
if(FD_ISSET(sock,&fds)) //测试sock是否可读,即是否网络上有数据 

recvfrom(sock,buffer,256,.....);//接受网络数据 
if(FD_ISSET(fp,&fds)) //测试文件是否可写 
fwrite(fp,buffer...);//写入文件 
buffer清空; 
}// end if break; 
}// end switch 
}//end while 
}//end main 参考资料:http://cuijinbird.blogchina.com/cuijinbird/1921117.html

Part 2:

select()的机制中提供一fd_set的数据结构,实际上是一long类型的数组,  每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他  文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,  当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执  行了select()的进程哪一Socket或文件可读,下面具体解释: 
#include <sys/types.h>  #include <sys/times.h>  #include <sys/select.h> 
int select(nfds, readfds, writefds, exceptfds, timeout)  int nfds;  fd_set *readfds, *writefds, *exceptfds;  struct timeval *timeout; 
ndfs:select监视的文件句柄数,视进程中打开的文件数而定,一般设为呢要监视各文件  中的最大文件号加一。  readfds:select监视的可读文件句柄集合。  writefds: select监视的可写文件句柄集合。  exceptfds:select监视的异常文件句柄集合。  timeout:本次select()的超时结束时间。(见/usr/sys/select.h,  可精确至百万分之一秒!) 
当readfds或writefds中映象的文件可读或可写或超时,本次select()  就结束返回。程序员利用一组系统提供的宏在select()结束时便可判  断哪一文件可读或可写。对Socket编程特别有用的就是readfds。  几只相关的宏解释如下: 
FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。  FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系。  FD_CLR(int fd, fd_set *fdset):清除文件句柄fd与fdset的联系。  FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否  可读写,>0表示可读写。  (关于fd_set及相关宏的定义见/usr/include/sys/types.h) 
这样,你的socket只需在有东东读的时候才读入,大致如下: 
...  int sockfd;  fd_set fdR;  struct timeval timeout = ..;  ...  for(;;) {  FD_ZERO(&fdR);  FD_SET(sockfd, &fdR);  switch (select(sockfd + 1, &fdR, NULL, &timeout)) {  case -1:  error handled by u;  case 0:  timeout hanled by u;  default:  if (FD_ISSET(sockfd)) {  now u read or recv something;  /* if sockfd is father and  server socket, u can now  accept() */  }  }  } 
所以一个FD_ISSET(sockfd)就相当通知了sockfd可读。  至于struct timeval在此的功能,请man select。不同的timeval设置  使使select()表现出超时结束、无超时阻塞和轮询三种特性。由于  timeval可精确至百万分之一秒,所以Windows的SetTimer()根本不算  什么。你可以用select()做一个超级时钟。 
FD_ACCEPT的实现?依然如上,因为客户方socket请求连接时,会发送  连接请求报文,此时select()当然会结束,FD_ISSET(sockfd)当然大  于零,因为有报文可读嘛!至于这方面的应用,主要在于服务方的父  Socket,你若不喜欢主动accept(),可改为如上机制来accept()。 
至于FD_CLOSE的实现及处理,颇费了一堆cpu处理时间,未完待续。 
--  讨论关于利用select()检测对方Socket关闭的问题: 
仍然是本地Socket有东东可读,因为对方Socket关闭时,会发一个关闭连接  通知报文,会马上被select()检测到的。关于TCP的连接(三次握手)和关  闭(二次握手)机制,敬请参考有关TCP/IP的书籍。 
不知是什么原因,UNIX好象没有提供通知进程关于Socket或Pipe对方关闭的  信号,也可能是cpu所知有限。总之,当对方关闭,一执行recv()或read(),  马上回返回-1,此时全局变量errno的值是115,相应的sys_errlist[errno]  为"Connect refused"(请参考/usr/include/sys/errno.h)。所以,在上  篇的for(;;)...select()程序块中,当有东西可读时,一定要检查recv()或  read()的返回值,返回-1时要作出关断本地Socket的处理,否则select()会  一直认为有东西读,其结果曾几令cpu伤心欲断针脚。不信你可以试试:不检  查recv()返回结果,且将收到的东东(实际没收到)写至标准输出...  在有名管道的编程中也有类似问题出现。具体处理详见拙作:发布一个有用  的Socket客户方原码。 
至于主动写Socket时对方突然关闭的处理则可以简单地捕捉信号SIGPIPE并作  出相应关断本地Socket等等的处理。SIGPIPE的解释是:写入无读者方的管道。  在此不作赘述,请详man signal。 
以上是cpu在作tcp/ip数据传输实验积累的经验,若有错漏,请狂炮击之。 
唉,昨天在hacker区被一帮孙子轰得差点儿没短路。ren cpu(奔腾的心) z80 
补充关于select在异步(非阻塞)connect中的应用,刚开始搞socket编程的时候  我一直都用阻塞式的connect,非阻塞connect的问题是由于当时搞proxy scan  而提出的呵呵  通过在网上与网友们的交流及查找相关FAQ,总算知道了怎么解决这一问题.同样  用select可以很好地解决这一问题.大致过程是这样的: 
1.将打开的socket设为非阻塞的,可以用fcntl(socket, F_SETFL, O_NDELAY)完  成(有的系统用FNEDLAY也可). 
2.发connect调用,这时返回-1,但是errno被设为EINPROGRESS,意即connect仍旧  在进行还没有完成. 
3.将打开的socket设进被监视的可写(注意不是可读)文件集合用select进行监视,  如果可写,用  getsockopt(socket, SOL_SOCKET, SO_ERROR, &error, sizeof(int));  来得到error的值,如果为零,则connect成功. 
在许多unix版本的proxyscan程序你都可以看到类似的过程,另外在solaris精华  区->编程技巧中有一个通用的带超时参数的connect模块.

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1684998

转:linux中select()函数分析的更多相关文章

  1. linux中probe函数传递参数的寻找(下)

    点击打开链接 linux中probe函数传递参数的寻找(下) 通过追寻driver的脚步,我们有了努力的方向:只有找到spi_bus_type的填充device即可,下面该从device去打通,当两个 ...

  2. 【转】linux 中fork()函数详解

    在看多线程的时候看到了这个函数,于是学习了下,下面文章写的通俗易懂,于是就开心的看完了,最后还是很愉快的算出了他最后一个问题. linux 中fork()函数详解 一.fork入门知识 一个进程,包括 ...

  3. linux select函数:Linux下select函数的使用详解【转】

    本文转载自;http://www.bkjia.com/article/28216.html Linux下select函数的使用 Linux下select函数的使用 一.Select 函数详细介绍 Se ...

  4. linux中 probe函数的何时调用的?

    点击打开链接 linux中 probe函数何时调用的 所以的驱动教程上都说:只有设备和驱动的名字匹配,BUS就会调用驱动的probe函数,但是有时我们要看看probe函数里面到底做了什么,还有传递给p ...

  5. open()函数 linux中open函数使用

    来源:http://www.cnblogs.com/songfeixiang/p/3733855.html   linux中open函数使用 open函数用来打开一个设备,他返回的是一个整型变量,如果 ...

  6. Linux 中write()函数的出错情况及处理

    write函数首先将进程需要发送的数据先放在进程缓冲区中,然后向socket的发送缓冲区进行拷贝,在此,可能出现这样情况,即当进程缓冲区中的数据量大于此时发送缓冲区中所能接受的数据量时,若此时处于阻塞 ...

  7. Linux下select函数的使用

    一.Select 函数详细介绍 Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect. accept.recv ...

  8. Linux中select poll和epoll的区别

    在Linux Socket服务器短编程时,为了处理大量客户的连接请求,需要使用非阻塞I/O和复用,select.poll和epoll是Linux API提供的I/O复用方式,自从Linux 2.6中加 ...

  9. linux中probe函数中传递的参数来源(上)

    点击打开链接 上一篇中,我们追踪了probe函数在何时调用,知道了满足什么条件会调用probe函数,但probe函数中传递的参数我们并不知道在何时定义,到底是谁定义的,反正不是我们在驱动中定义的(当然 ...

随机推荐

  1. idea中项目发布到svn服务器

    1. 在svn上创建仓库 https://5VY9MN7OCQV7B9N/svn/KH75-myhouse 2. 将idea的项目上传至svn服务器 项目变红 上传完成项目变成绿色 查看svn服务有文 ...

  2. Apache Spark 2.2.0 中文文档 - Spark Streaming 编程指南

    Spark Streaming 编程指南 概述 一个入门示例 基础概念 依赖 初始化 StreamingContext Discretized Streams (DStreams)(离散化流) Inp ...

  3. 8.spark Core 进阶1

        (e.g. standalone manager, Mesos, YARN)   In "cluster" mode, the framework launches the ...

  4. CF1086F Forest Fires

    CF1086F Forest Fires 有点意思的题目 直接统计每个格子的val是非常难办的.很难知道每秒新出来多少个格子 设$F[i]$表示,前i时刻覆盖的格子的数量 则,$ans=\sum_{i ...

  5. Linux下编译VLC for Android源代码总结

    转:http://blog.chinaunix.net/uid-26611383-id-3678766.html 由于项目需要,需要一个在android平台能够支持RTSP协议的播放器,由于之前没有a ...

  6. 如何解决nodemon运行报错问题

    原因 nodemon没有被正确安装 解决方法 如果yarn global add nodemon --verbose安装没用的话,然后输入npm i nodemon -g --verbose使用NPM ...

  7. 尚学linux课程---6、linux命令介绍

    尚学linux课程---6.linux命令介绍 一.总结 一句话总结: linux中命令的一般格式:命令关键字 选项 参数1 参数2 1.linux基本原则? 一切皆文件 配置文件保存为纯文本格式 2 ...

  8. 4_5.springboot2.x之Web开发RestfulCRUD操作

    1).RestfulCRUD:CRUD满足Rest风格 URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作 普通CRUD(uri来区分操作) RestfulCRUD 查询 getE ...

  9. 04_Mybatis输入\出映射

    1. 输入映射 ​ 通过paramterType指定输入参数的类型,类型可以是简单类型.hashmap.pojo的包装类. 1.1 传递pojo的包装对象 1.需求 ​ 完成用户信息的综合查询,需要传 ...

  10. python Six 模块

    Six模块用于python2和python3的兼容 import six 官网链接:https://six.readthedocs.io/