程序员人生 网站导航

玩转重要的select函数并分析其行为

栏目:互联网时间:2015-03-04 08:49:08

       说明:  虽然select函数在Windows和Linux上的用法有些差异, 且这些差异值得我们特别注意, 但从功能上来说, 他们还是差不多的。 本文, 我们仅仅斟酌Windows上的select函数。


      关于select函数的原型和用处, 百度和谷歌的介绍到处都是, 在本文中, 我就不赘述了, 我们仅仅来玩代码并作扼要分析。 如果有不对或偏颇的地方, 大家可以各抒己见, 共同进步,我也一定会认真核实后给予回应奋斗, 也建议大家多多实践。



       程序1, 服务端程序:

#include <stdio.h> #include <winsock2.h> // 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); while(1) { getchar(); char sendBuf[100] = "hello"; send(sockConn, sendBuf, strlen(sendBuf) + 1, 0); // 发送数据到客户端,最后1个参数1般设置为0 } closesocket(sockConn); closesocket(sockSrv); WSACleanup(); return 0; }


        程序2, 客户端程序:

#include <winsock2.h> #include <stdio.h> #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(⑴, &read_set, NULL, NULL, &t); printf("ret is %d ", ret); Sleep(1000); } closesocket(sockClient); WSACleanup(); return 0; }
       我们先开启服务端程序1, 然后运行客户端程序2, 然后不要动服务端和客户端, 静静等待, 等20s后, 发现程序2的结果是:

ret is 0
ret is ⑴
ret is ⑴
ret is ⑴
ret is ⑴

...........

       可以看到, 20s后, select函数超时, 返回0. 为何呢? 由于select函数检测到sockClient对应的内核缓冲区没有数据可读, 以超时情势返回。


       好, 我们重新启动程序1对应的服务端, 然后重新启动程序2对应的客户端, 此时(不用等20s), 我们在服务端上按1下Enter键, 向客户端发送"hello"(包括最后的'').  然后, 我们看1下程序2的结果:

ret is 1
ret is 1
ret is 1
ret is 1
ret is 1
ret is 1
ret is 1
ret is 1
ret is 1

......

      上面的打印直到“永久”。为何呢? 由于select函数检测到sockClient对应的内核缓冲区有数据可读(是1直有), 返回1. 


       好, 我们略微修改1下程序2, 构成以下的程序3:

#include <winsock2.h> #include <stdio.h> #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) { printf("xxx "); ret = select(⑴, &read_set, NULL, NULL, &t); printf("ret is %d ", ret); printf("yyy "); char recvBuf[100] = {0}; recv(sockClient, recvBuf, 100, 0); printf("%s ", recvBuf); Sleep(1000); printf("zzz "); } closesocket(sockClient); WSACleanup(); return 0; }

       我们重新启动程序1对应的服务端, 然后重新启动程序3对应的客户端, 此时(不用等20s)我们在服务端上按1下Enter键, 向客户端发送"hello"(包括最后的'').  然后, 我们看1下程序3的结果, 程序会立即输出:

xxx
ret is 1
yyy
hello
zzz
xxx

     然后, 过20s,  程序结果为:

xxx
ret is 1
yyy
hello
zzz
xxx
ret is 0
yyy

         然后, 就1直阻塞在此。 我们来分析1下, 第1次进入while的时候, 服务端发送数据过来, 客户真个select函数检测到sockClient对应的内核缓冲区有数据可读, 因而立即返回, 所以有对应的结果。 当程序第2次进入while后, 客户真个select没有感知到sockClient对应的内核缓冲区没有数据可读(由于已读取了), 故以超时返回, 因而有了对应的结果。最后结果1直如此, 是由于阻塞在recv处。


       我们继续来做有趣的实验, 我们把程序3中的recv函数略微改1下, 构成程序4:

#include <winsock2.h> #include <stdio.h> #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) { printf("xxx "); ret = select(⑴, &read_set, NULL, NULL, &t); printf("ret is %d ", ret); printf("yyy "); char recvBuf[100] = {0}; recv(sockClient, recvBuf, 100, MSG_PEEK); printf("%s ", recvBuf); Sleep(1000); printf("zzz "); } closesocket(sockClient); WSACleanup(); return 0; }
        好, 我们以程序1做服务端, 以程序4做客户端。 进行如上类似的实验, 让服务端向客户端端发送"hello", 此时, 程序41直在循环不停地打印以下信息, 直到“永久”:

xxx
ret is 1
yyy
hello
zzz

       我们来分析1下, 程序4的结果和程序3为何不同。 之前说过了, MSG_PEEK值从内核缓冲区中偷窥1下信息, 并没有偷取, 也就是说, 是复制而不是剪切, 换句话说, 也就是sockClient对应的内核缓冲区1直数据可读,内核缓冲区中的"hello"还在那里, 不增不减。 因此, select函数每次都能监测到可读, 因此, 立即返回1. select函数还是真的有点意思哈。


       不要停止, 我们继续看。 现在, 我们略微修改1下程序3中的recv函数, 构成程序5:

#include <winsock2.h> #include <stdio.h> #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) { printf("xxx "); ret = select(⑴, &read_set, NULL, NULL, &t); printf("ret is %d ", ret); printf("yyy "); char recvBuf[100] = {0}; recv(sockClient, recvBuf, 1, 0); printf("%s ", recvBuf); Sleep(1000); printf("zzz "); } closesocket(sockClient); WSACleanup(); return 0; }
         好, 我们以程序1做服务端, 以程序5做客户端。 进行如上类似的实验, 让服务端向客户端发送"hello", 此时, 程序5的结果以下:
xxx

ret is 1
yyy
h
zzz
xxx
ret is 1
yyy
e
zzz
xxx
ret is 1
yyy
l
zzz
xxx
ret is 1
yyy
l
zzz
xxx
ret is 1
yyy
o
zzz
xxx
ret is 1
yyy


zzz
xxx

         然后再等20s, 结果以下:

xxx
ret is 1
yyy
h
zzz
xxx
ret is 1
yyy
e
zzz
xxx
ret is 1
yyy
l
zzz
xxx
ret is 1
yyy
l
zzz
xxx
ret is 1
yyy
o
zzz
xxx
ret is 1
yyy


zzz
xxx
ret is 0
yyy

        然后, 结果就1直这样了。 为何是这类现象呢?  我们看到,"hello"这个串(包括最后的'')中的6个字符被不断取出, 此时, 在取出之前, select函数进行6次检测, 6次都发现有数据可读, 所以6次都立即返回。 等把数据读后, 发现没数据可读了, 因而不会立即返回, 而是以超时情势进行返回。 最后1直阻塞在recv处。 妙哉妙哉。


       以上只介绍了客户端select的读特性, 以后, 我们肯定还会与select函数见面的, 今天先到此为止。 最后欢迎大家提出不同意见, 共同进步微笑







------分隔线----------------------------
------分隔线----------------------------

最新技术推荐