传统的socket通信收发数据是阻塞式的,也就是说当一个socket recv时,server socket都不能响应client socket的连接请求,会产生堵塞。
采用多线程的方式,为每一个socket连接创建一个子线程取进行阻塞式的I/O开销太大,服务器资源伤不起。
这时候我们便可以采取select模型。
select允许进程指示内核等待多个事件中的任何一个发生,并仅在有一个或多个事件发生或经历一段指定时间后才唤醒它。select告诉内核对哪些描述字感兴趣以及等待多长时间。这就是所谓的非阻塞模型,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高。提供了一种异步I/O的方式。
select函数原型如下:
int select
(
int nfds, //Winsock中此参数无意义
fd_set* readfds, //进行可读检测的Socket
fd_set* writefds, //进行可写检测的Socket
fd_set* exceptfds, //进行异常检测的Socket
const struct timeval* timeout //非阻塞模式中设置最大等待时间
)
select函数三个集合中至少有一个监视集合不能为空,其他两个都可以为空。
最后一个参数timeout值有下列三种情况:
1.置为NULL的话,设置套接字为阻塞状态,一定要等到相应的套接字完成相应的操作为止才能结束阻塞。
2.置为0s 0ms,就变成了一个纯粹的非阻塞函数,不管套接字状态有没有变化,都立刻返回继续执行,无变化返回0,有变化返回一个正值。
3.置为大于0,这就是等待的超时时间,即socket在timeout时间内阻塞,若timeout时间之内socket状态如完成I/O操作就返回了,否则在超时后不管怎样一定返回。
返回值意义:
负值:select调用错误。
正值:socket可以I/O
0:等待超时,没有可以I/O的socket
和select模型紧密结合的四个宏:
FD_CLR( s,*set) 从队列set删除句柄s。
FD_ISSET( s, *set) 检查句柄s是否存在与队列set中。
FD_SET( s,*set )把句柄s添加到队列set中。
FD_ZERO( *set ) 把set队列初始化成空队列。
select模式依赖于select函数,其思想就是让select函数对传入的fd_set中的套接字进行监视,如果没有什么事情发生就将之前监视的fd_set中的套接字清空。以下是一个select模型的例子:
timeval outTime;
outTime.tv = 1; //设置等待时间为1s
outTime.usec = 0; //毫秒
fd_set fdread;
while(true)
{
FD_ZERO(&fdread);
FD_SET(sessionSock, &fdread) //sessionSock为之前创建的会话套接字
select(0, &fdread, NULL, NULL, &outTime);
if(FD_ISSET(sessionSock, &fdread))//判断套接字是否还在集合中
{
recv_cnt = recv(sessionSock, buf, bufSize, 0);
}
else
{
//没有数据写入,进行其他操作
}
}
所谓的异步还是得借助于多线程,借助于多线程来完成异步。