前面, 我已经陆续介绍过select函数的一些零碎知识, 在本文中,我们来讨论这样一个问题:socket何时处于读就绪状态? 其实主要讨论select函数, 毕竟socket的读就绪状态会导致select函数立即返回。select函数的重要性不言而喻, 我在这里就不再强调了。 需要注意的是: Windows环境下的select和Linux环境下的select大同小异, 为了便于叙述和实战, 我们以Windows下的select函数为例。
我们再次来复习一下select函数的原型吧:int PASCAL FAR select( int nfds, fd_set FAR* readfds, fd_set FAR* writefds, fd_set FAR* exceptfds, const struct timeval FAR* timeout);
nfds:是一个整数值,是指集合中所有文件描述符的范围,在Linux中必须是所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,会被忽略。
readfds:(可选)指针,指向一组等待可读性检查的套接口。
writefds:(可选)指针,指向一组等待可写性检查的套接口。
exceptfds:(可选)指针,指向一组等待错误检查的套接口。
timeout:select()最多等待时间,对阻塞操作则为NULL。
那么, select函数有什么作用呢?何时返回呢? 下面是应用程序(程序员写的, 比如你, 我)和操作系统内核(微软的一些人写的)之间的精彩对话
内核: 应用程序大爷, 您好, 我提供了一个名叫select的函数, 供你用, 你爱咋调用就咋调用, 具体怎么用, 请见select函数原型说明。 大爷, 您只需要负责下发命令即可, 我负责执行。
应用程序: 喂, 内核孙子, 我要调用你提供的select函数了, 听说你可以帮我去监测一些我感兴趣的描述符的状态? 一旦处于所谓的就绪状态(比如读就绪状态), 你可以报告给我, 对么?
内核: 是的, 大爷。
应用程序: 那好, 我要你去监测一些事件(比如读就绪状态), 等待某个事件发生, 如果这个事件发生, 那你就好通知我。 否则,有你好受的。
内核: 好的, 大爷。 那要是大爷您定义的这个事件(比如读就绪状态)一直不发生呢? 我建议大爷您再定义一个超时时间, 如果在这个时间内没有发生, 我给大爷您报一个超时的信息。 你看看我提供的select函数, 最后一个时间参数就是特别为您准备的。
应用程序: 你这个孙子很乖啊, 知道大爷我没有耐心, 主动给我报超时, 好吧, 那我就给你下发一个超时时间。 如果在超时时间内我定义的事件(比如读就绪状态)没有发生, 你务必通知到我, 否则, 没有否则!
内核: 一定一定。哦, 对了, 大爷, 既然我们已经沟通好了, 那我们就尝试着实战交流沟通一下吧。
应用程序: 好的, 好孙子。
好吧, 精彩对话已经对完了, 我们来看看socket何时处于读就绪状态?(当socket处于读就绪状态时, select函数会立即返回)
server1.cpp为:
#include
#include // winsock接口
#pragma comment(lib, "ws2_32.lib") // winsock实现
int main()
{
WORD wVersionRequested; // 双字节,winsock库的版本
WSADATA wsaData; // winsock库版本的相关信息
wVersionRequested = MAKEWORD(1, 1); // 0x0101 即:257
// 加载winsock库并确定winsock版本,系统会把数据填入wsaData中
WSAStartup( wVersionRequested, &wsaData );
// AF_INET 表示采用TCP/IP协议族
// SOCK_STREAM 表示采用TCP协议
// 0是通常的默认情况
unsigned int sockSrv = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET; // TCP/IP协议族
addrSrv.sin_addr.S_un.S_addr = INADDR_ANY;
addrSrv.sin_port = htons(8888); // socket对应的端口
// 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程)
bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
// 将socket设置为监听模式,5表示等待连接队列的最大长度
listen(sockSrv, 5);
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len);
int flag = 0;
scanf("%d", &flag);
if(1 == flag)
{
char sendBuf[100] = "hello";
send(sockConn, sendBuf, strlen(sendBuf) + 1, 0); // 发送数据到客户端,最后一个参数一般设置为0
}
if(2 == flag)
{
closesocket(sockConn);
}
if(3 == flag)
{
closesocket(sockSrv);
}
if(4 == flag)
{
WSACleanup();
}
while(1);
return 0;
}
client1.cpp为:
#include
#include
#pragma comment(lib, "ws2_32.lib")
int main()
{
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(1, 1);
WSAStartup( wVersionRequested, &wsaData );
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(8888);
int ret = connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
fd_set read_set;
struct timeval t;
FD_ZERO(&read_set);
FD_SET(sockClient, &read_set);
t.tv_sec = 20;
t.tv_usec = 0;
while(1)
{
ret = select(-1, &read_set, NULL, NULL, &t);
printf("ret is %d\n", ret);
Sleep(1000);
}
closesocket(sockClient);
WSACleanup();
return 0;
}
实战开始啦:
1. 开启服务端, 再开启客户端, 然后啥也不干, 过约20秒后, 客户端的select才返回0. 看来, 内核没有撒谎, 没有事件发生的时候, 超过20秒后, 确实如实把情况反馈给了应用程序, 真的很乖。 好, 我们关闭服务端和客户端。
2. 开启服务端, 再开启客户端。 我们在服务端让flag为1, 发送数据, 制造客服端的可读就绪状态, 我们看到, 客户端的select函数立即返回了1. 看来, 内核确实如实反馈了socket的可读就绪状态。 好, 我们关闭服务端和客户端。 (备注: 在这种情况下, 一旦select检测到读就绪状态, 此时调用recv函数能立即返回)
3. 开启服务端, 再开启客户端。 我们在服务端让flag为2, 关闭通信的socket, 我们看到, 客户端的select函数立即返回了1. 好, 我们关闭服务端和客户端。
4. 开启服务端, 再开启客户端。 我们在服务端让flag为3, 关闭非通信的socket, 我们看到, 客户端没有啥变化, 20秒后, select函数才返回0, 实际就是1中描述的情况。 好,我们关闭服务端和客户端。
5. 开启服务端, 再开启客户端。 我们在服务端让flag为4, 调用WSACleanup, 我们看到, 客户端的select函数立即返回了1. 好, 我们关闭服务端和客户端。
6. 开启服务端, 再开启客户端, 然后又关掉服务端(关掉这个服务进程), 我们看到, 客户端的select函数立即返回了1. 好, 我们把客户端也关了吧。
别急, 还有一种情况的读就绪状态, 请直接参考我之前的博文:http://blog.csdn.net/stpeace/article/details/21129637, 我来把这个总结为第7点。
7. 如果某socket处于监听状态(当然啦, 这肯定是服务端的某socket),且设置了readfs, 那么一旦客户端发起了connect连接, 服务端的select函数会立即返回。 由此可知, 一旦服务端的select函数返回了非负值(比如1), 则表明肯定有客户端来connect连接, 此时服务端调用accept函数会立即成功。 具体实战请参考之前的博文。
总结一下: 1和4是超时返回, 2, 3, 5, 6, 7都相当于客户端套接字处于读就绪状态, 内核检测到这个读就绪状态后, select函数立即返回, 将信息报告给应用程序。 其中仅有7是特别针对服务端的socket.
目前来讲, 我主要接触到的就是读就绪状态或者超时后select函数的返回, 至于写就绪状态下select的返回以及异常就绪状态下select函数的返回, 其实也是很简单的, 以后如果有需要, 再学学, 那也不是什么难事, 毕竟网上资料一大堆啊。 OK, 本文到此为止。