socket select()用法,哪些socket api会阻塞

一、winsock中
#include <winsock.h>
//#include <winsock2.h>
原型

int select(
int nfds,
fd_set* readfds,
fd_set* writefds,
fd_set* exceptfds,
const struct timeval* timeout
);

nfds:本参数忽略,仅起到兼容作用。
   readfds:(可选)指针,指向一组等待可读性检查的套接口。
   writefds:(可选)指针,指向一组等待可写性检查的套接口。
   exceptfds:(可选)指针,指向一组等待错误检查的套接口。
   timeout:select()最多等待时间,对阻塞操作则为NULL。


windows 套接字默认是阻塞式的

注释:
   本函数用于确定一个或多个套接口的状态。对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息。用fd_set结构来表示一组等待检查的套接口。在调用返回时,这个结构存有满足一定条件的套接口组的子集,并且select()返回满足条件的套接口的数目。有一组宏可用于对fd_set的操作,这些宏与Berkeley Unix软件中的兼容,但内部的表达是完全不同的。
   readfds参数标识等待可读性检查的套接口。如果该套接口正处于监听listen()状态,则若有连接请求到达,该套接口便被标识为可读,这样一个accept()调用保证可以无阻塞完成。对其他套接口而言,可读性意味着有排队数据供读取。或者对于SOCK_STREAM类型套接口来说,相对于该套接口的虚套接口已关闭,于是recv()或recvfrom()操作均能无阻塞完成。如果虚电路被“优雅地”中止,则recv()不读取数据立即返回;如果虚电路被强制复位,则recv()将以WSAECONNRESET错误立即返回。如果SO_OOBINLINE选项被设置,则将检查带外数据是否存在(参见setsockopt())。
   writefds参数标识等待可写性检查的套接口。如果一个套接口正在connect()连接(非阻塞),可写性意味着连接顺利建立。如果套接口并未处于connect()调用中,可写性意味着send()和sendto()调用将无阻塞完成。〔但并未指出这个保证在多长时间内有效,特别是在多线程环境中〕。
   exceptfds参数标识等待带外数据存在性或意味错误条件检查的套接口。请注意如果设置了SO_OOBINLINE选项为假FALSE,则只能用这种方法来检查带外数据的存在与否。对于SO_STREAM类型套接口,远端造成的连接中止和KEEPALIVE错误都将被作为意味出错。如果套接口正在进行连接connect()(非阻塞方式),则连接试图的失败将会表现在exceptfds参数中。
   如果对readfds、writefds或exceptfds中任一个组类不感兴趣,可将它置为空NULL。
   在winsock.h头文件中共定义了四个宏来操作描述字集。FD_SETSIZE变量用于确定一个集合中最多有多少描述字(FD_SETSIZE缺省值为64,可在包含winsock.h前用#define FD_SETSIZE来改变该值)。对于内部表示,fd_set被表示成一个套接口的队列,最后一个有效元素的后续元素为INVAL_SOCKET。宏为:
   FD_CLR(s,*set):从集合set中删除描述字s。
   FD_ISSET(s,*set):若s为集合中一员,非零;否则为零。
   FD_SET(s,*set):向集合添加描述字s。
   FD_ZERO(*set):将set初始化为空集NULL。
   timeout参数控制select()完成的时间。若timeout参数为空指针,则select()将一直阻塞到有一个描述字满足条件。否则的话,timeout指向一个timeval结构,其中指定了select()调用在返回前等待多长时间。如果timeval为{0,0},则select()立即返回,这可用于探询所选套接口的状态。如果处于这种状态,则select()调用可认为是非阻塞的,且一切适用于非阻塞调用的假设都适用于它。举例来说,阻塞钩子函数不应被调用,且WINDOWS套接口实现不应yield。

返回值:
   select()调用返回处于就绪状态并且已经包含在fd_set结构中的描述字总数;如果超时则返回0;否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。

错误代码:
   WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。
   WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。
   WSAEINVAL:超时时间值非法。
   WSAEINTR:通过一个WSACancelBlockingCall()来取消一个(阻塞的)调用。
   WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。
   WSAENOTSOCK:描述字集合中包含有非套接口的元素。

范例 :

sock= socket(AF_INET,SOCK_STREAM,0);

struct sockaddr_in addr;      //告诉sock 应该再什么地方licence
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(11111);   //端口啦
addr.sin_addr.s_addr=htonl(INADDR_ANY);        //在本机的所有ip上开始监听

bind (sock,(sockaddr *)&addr,sizeof(addr));//bind....

listen(sock,5);                   //最大5个队列

SOCKET socka;                    //这个用来接受一个连接
fd_set rfd;                     // 描述符集 这个将用来测试有没有一个可用的连接
struct timeval timeout;

FD_ZERO(&rfd);                     //总是这样先清空一个描述符集

timeout.tv_sec=60;                //等下select用到这个
timeout.tv_usec=0;

u_long ul=1;

ioctlsocket(sock,FIONBIO,&ul);    //用非阻塞的连接

//现在开始用select
FD_SET(sock,&rfd);    //把sock放入要测试的描述符集 就是说把sock放入了rfd里面 这样下一步调用select对rfd进行测试的时候就会测试sock了(因为我们将sock放入的rdf) 一个描述符集可以包含多个被测试的描述符, 
if(select(sock+1,&rfd,0,0, &timeout)==0) 
{ //这个大括号接上面的,返回0那么就超过了timeout预定的时间 

//处理....

}

if(FD_ISSET(sock,&rfd))
{      //有一个描述符准备好了

socka=accept(sock,0,0);     //一个用来测试读 一个用来测试写

FD_ZERO(&rfd);

FD_ZERO(&wfd);

FD_SET(socka,&rfd);//把socka放入读描述符集

FD_SET(sockb,&rfd);//把sockb放入读描述符集

FD_SET(socka,&wfd);把socka放入写描述符集

FD_SET(sockb,&wfd);把sockb放入写描述符集

if(SOCKET_ERROR!=select(0,&rfd,&wfd,0,0))      //测试这两个描述符集,永不超时 其中rfd只用来测试读 wfd只用来测试写

{      //没有错误

if(FD_ISSET(socka,&rfd))    //socka可读

{...}

if(FD_ISSET(sockb,&rfd)   //sockb可读

{...}

if(FD_ISSET(socka,&wfd) //socka 可写

{...}

if(FD_ISSET(sockb,&wfd) //sockb可写

{...}

}



二、linux c中

select(I/O多工机制)


//表头文件
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>

//定义函数

int select(int n,

fd_set * readfds,

fd_set * writefds,

fd_set * exceptfds,

struct timeval * timeout);


函数说明

select()用来等待文件描述词状态的改变。参数n代表最大的文件描述词加1,参数readfds、writefds 和exceptfds 称为描述词组,是用来回传该描述词的读,写或例外的状况。底下的宏提供了处理这三种描述词组的方式:
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set); 用来清除描述词组set的全部位

参数

timeout为结构timeval,用来设置select()的等待时间,其结构定义如下
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};

返回值

如果参数timeout设为NULL则表示select()没有timeout。

错误代码

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

范例

常见的程序片段:fs_set readset;
FD_ZERO(&readset);
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset){……}

下面是linux环境下select的一个简单用法

#include <sys/time.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>

int main ()
{
	int keyboard;
	int ret,i;
	char c;
	fd_set readfd;
	struct timeval timeout;
	keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK);
	assert(keyboard>0);
	while(1)
	{
			timeout.tv_sec=1;
			timeout.tv_usec=0;
			FD_ZERO(&readfd);
			FD_SET(keyboard,&readfd);
			ret=select(keyboard+1,&readfd,NULL,NULL,&timeout);
			if(FD_ISSET(keyboard,&readfd))
	    {
	      i=read(keyboard,&c,1);
	          if('\n'==c)
	          continue;
	      printf("hehethe input is %c\n",c);
	     
	       if ('q'==c)
	      break;
	    }
	}
}

【我的笔记】tcp/udp的server,如果是非阻塞socket,都不用sleep,tcp/udp server线程也不会占用过多cpu资源;select本身能起到sleep的作用。

【more参考】(我如果贴了原文链接,帖子就显示不正常,我去)

序:前段时间狂看了很多关于网络编程的资料,这里自己总结一下,以便自己以后可以参考。


什么是阻塞socket,什么是非阻塞socket。对于这个问题,我们要先弄清什么是阻塞/非阻塞。阻塞与非阻塞是对一个文件描述符指定的文件或设备的两种工作方式。 阻塞的意思是指,当试图对该文件描述符进行读写时,如果当时没有东西可读或者暂时不可写,程序就进入等待状态,直到有东西可读或者可写为止。 非阻塞的意思是,当没有东西可读或者不可写时,读写函数就马上返回,而不会等待。


现在来理解什么是阻塞socket,什么是非阻塞socket。每个通过socket()函数创建的socket,本质就是一个文件描述符,所以对该文件描述符的IO操作方式不同,就有了阻塞socket和非阻塞socket。 那是不是说阻塞socket下的所以socket api函数都是阻塞的呢,如果你还不能正确的回答这个问题,说明上面简短的说明并没有让你真正的明白什么是阻塞socket和非阻塞socket。这个问题的答案是否定的,为什么是否定的,因为并不是每个socket的api都会涉及到对文件描述符的IO操作。


 这里我列举了,哪些socket api会阻塞:

accept,connect,recv(recvfrom),send(sendto),closesocket,select(poll或epoll)

1)accept在阻塞模式下,没有新连接时,线程会进入睡眠状态;非阻塞模式下,没有新连接时,立即返回WOULDBLOCK错误。

【winsock MSDN】The accept function can block the caller until a connection is present if no pending connections are present on the queue, and the socket is marked as blocking. If the socket is marked as nonblocking and no pending connections are present on the queue, accept returns an error as described in the following. After the successful completion ofaccept returns a new socket handle, the accepted socket cannot be used to accept more connections. The original socket remains open and listens for new connection requests.

2)connect在阻塞模式下,仅TCP连接建立成功或出错时才返回,分几种具体的情况,这里不再叙述(通常都是在阻塞模式调用connect);非阻塞模式下,该函数会立即返回INPROCESS错误(需用select检测该连接是否建立成功,有点啰嗦)

【winsock MSDN】If no error occurs, connect returns zero. Otherwise, it returns SOCKET_ERROR, and a specific error code can be retrieved by callingWSAGetLastError.

On a blocking socket, the return value indicates success or failure of the connection attempt.

With a nonblocking socket, the connection attempt cannot be completed immediately. In this case, connect will return SOCKET_ERROR, andWSAGetLastError will return WSAEWOULDBLOCK. In this case, there are three possible scenarios:

  • Use the select function to determine the completion of the connection request by checking to see if the socket is writeable. 

3)recv/recvfrom/send/sendto很好理解,因为这两类函数读写socket文件描述符的接收/发送缓冲区。 

4) select/poll/epoll并不是真正意义上的阻塞(也就是说与socket是否阻塞无关),它们的阻塞是由于它们最后一个timeout参数决定的,timeout大于0时,它们会一直等待直到超时才退出(相等于阻塞了吧,^_^),而timeout=-1即永远等待。 

5)closesocket也不是真正意义上的阻塞,它其实是指是否等待关闭(相当于阻塞了吧,^_^),它受套接字选项SO_LINGER和SO_DONTLINGER的影响。若SO_DONTLINGER或SO_LINGER的间隔=0时,closesocket就是非等待关闭的,但是当SO_LINGER的间隔>0时,closesoket就是等待关闭的,直到剩余数据都发送完毕或直到超时才退出。(但是这个地方只有对于阻塞的套接口才有用,如果是非阻塞的套接口,它会立即返回并且指示错误WOULDBLOCK)。

最后提供linux下和windows下设置阻塞和非阻塞的几种方式:

【linux】:

int flags = fcntl(listenSock, F_GETFL, 0);
fcntl(listenSock, F_SETFL, flags|O_NONBLOCK);

read/recv函数的最后一个参数也可以设置阻塞或非阻塞方式

【windows】:

ioctlsocket,WSAAsyncselect()和WSAEventselect()

read/recv函数的最后一个参数也可以设置阻塞或非阻塞方式


//ioctlsocket() 设置非阻塞的示例
	u_long iMode = 1;
#ifndef WIN32 //Linux OS
#define ioctlsocket(a,b,c) ioctl(a,b,c)
#endif
	ioctlsocket(m_sock, FIONBIO, &iMode);

【附录】

【MSDN send】

The successful completion of a send function does not indicate that the data was successfully delivered and received to the recipient. This function only indicates the data was successfully sent.

If no buffer space is available within the transport system to hold the data to be transmitted,send will block unless the socket has been placed in nonblocking mode. On nonblocking stream oriented sockets, the number of bytes written can be between 1 and the requested length, depending on buffer availability on both the client and server computers.The select, WSAAsyncSelect or WSAEventSelect functions can be used to determine when it is possible to send more data.

Calling send with a len parameter of zero is permissible and will be treated by implementations as successful. In such cases,send will return zero as a valid value. For message-oriented sockets, a zero-length transport datagram is sent.

send返回成功,并不代表接收方收到了数据,仅表示已经发出去了。

当socket发送缓存不足时,对于阻塞socket,send会被阻塞住(直到缓存有剩余空间能发出去);对于非阻塞socket,发送的数据长度可能从1~指定发送length,这取决于收/发双方的计算机。select可以用于判断是否能继续发送更多数据。


【MSDN recv】

Return Value

If no error occurs, recv returns the number of bytes received. If the connection has been gracefully closed, the return value is zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.

Remarks

The recv function is used to read incoming data on connection-oriented sockets, or connectionless sockets. When using a connection-oriented protocol, the sockets must be connected before callingrecv. When using a connectionless protocol, the sockets must be bound before callingrecv.

The local address of the socket must be known. For server applications, use an explicitbind function or an implicit accept or WSAAccept function. Explicit binding is discouraged for client applications. For client applications, the socket can become bound implicitly to a local address usingconnect, WSAConnect, sendto,WSASendTo, or WSAJoinLeaf.

For connected or connectionless sockets, the recv function restricts the addresses from which received messages are accepted. The function only returns messages from the remote address specified in the connection. Messages from other addresses are (silently) discarded.

For connection-oriented sockets (type SOCK_STREAM for example), calling recv will return as much data as is currently available—up to the size of the buffer specified. If the socket has been configured for in-line reception of OOB data (socket option SO_OOBINLINE) and OOB data is yet unread, only OOB data will be returned. The application can use the ioctlsocket orWSAIoctlSIOCATMARK command to determine whether any more OOB data remains to be read.

For connectionless sockets (type SOCK_DGRAM or other message-oriented sockets), data is extracted from the first enqueued datagram (message) from the destination address specified by theconnect function.

If the datagram or message is larger than the buffer specified, the buffer is filled with the first part of the datagram, andrecv generates the error WSAEMSGSIZE.For unreliable protocols (for example, UDP) the excess data is lost; for reliable protocols, the data is retained by the service provider until it is successfully read by callingrecv with a large enough buffer.

If no incoming data is available at the socket, the recv call blocks and waits for data to arrive according to the blocking rules defined forWSARecv with the MSG_PARTIAL flag not set unless the socket is nonblocking. In this case, a value of SOCKET_ERROR is returned with the error code set toWSAEWOULDBLOCK. The select,WSAAsyncSelect, or WSAEventSelect functions can be used to determine when more data arrives.

If the socket is connection oriented and the remote side has shut down the connection gracefully, and all data has been received, arecv will complete immediately with zero bytes received. If the connection has been reset, arecv will fail with the error WSAECONNRESET.

对于阻塞socket,如果socket没来数据,则recv调用会被阻塞,直到有数据到来。

Note  When issuing a blocking Winsock call, such as send, recv, select, accept, or connect function calls, Winsock may need to wait for a network event before the call can complete. Winsock performs an alertable wait in this situation, which can be interrupted by an asynchronous procedure call (APC) scheduled on the same thread, and thereby create unspecified results. Do not issue blocking Winsock function calls without being certain that the current thread is not waiting inside another blocking Winsock function call; doing so results in unpredictable behavior.

如果你不确定调用阻塞recv的线程中,是否还需要等待其他的Winsock阻塞接口调用,那么请不要使用阻塞recv。如果这种情况下,使用了阻塞recv,可能不正确的返回(没有收到数,被其它异步调用引起的返回)。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值