WinSocket的select函数的用法(windows套接字比较研究)

来源:互联网 发布:淘宝店铺主图视频教程 编辑:程序博客网 时间:2024/05/22 16:44

总体上来说select函数的作用:

确定一个或多个套接口的状态,本函数用于确定一个或多个套接口的状态,对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息,用fd_set结构来表示一组等待检查的套接口,在调用返回时,这个结构存有满足一定条件的套接口组的子集,并且select()返回满足条件的套接口的数目。


简单来说select用来填充一组可用的socket句柄,当满足下列之一条件时:

1.可以读取的sockets。当这些socket被返回时,在这些socket上执行recv/accept等操作不会产生阻塞;
2.可以写入的sockets。当这些socket被返回时,在这些socket上执行send等不会产生阻塞;
3.返回有错误的sockets。

select()的机制中提供一fd_set的数据结构,实际上市一long类型的数组,每一个数组元素都能与一打开的文件句柄(或者是其他的socket句柄,文件命名管道等)建立联系,建立联系的工作实际上由程序员完成,当调用select()的时候,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程那一socket或文件可读。

函数原型

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

nfds:select监视的文件句柄数,视进程中打开的文件数而定。
readfds:select监视的可读文件句柄集合
writefds:select监视的可写文件句柄集合
exceptfds:select监视的异常文件句柄集合
timeout:本次select()超时结束时间,可精确至百万分之一秒

当readfds或者writefds中映像的文件可读或者可写或者超时时,本次select()就结束返回。对socket编程又用的是readfds.

select的处理函数一般有:

同时和select配对使用的还有:
FD_CLR(s, *set) 
Removes the descriptor s from set. 
FD_ISSET(s, *set) 
Nonzero if s is a member of the set. Otherwise, zero. 
FD_SET(s, *set) 
Adds descriptor s to set. 
FD_ZERO(*set) 
Initializes the set to the null set. 


应该都知道,accept()函数是一个阻塞的函数,当有客户端请求连接服务端时才返回。如下面的例子

server端:

<span style="font-family:SimHei;font-size:18px;">#include <iostream>#include <Windows.h>using namespace std;#define  PORT 4000#define  IP_ADDRESS "127.0.0.1"#pragma comment(lib, "WS2_32.lib")int main(int argc, char* argv[]){WSADATA  Ws;SOCKET ServerSocket, CientSocket;struct sockaddr_in LocalAddr, ClientAddr;int Ret = 0;int AddrLen = 0;HANDLE hThread = NULL;//Init Windows Socketif (WSAStartup(MAKEWORD(2, 2), &Ws) != 0){cout << "Init Windows Socket Failed::" << GetLastError() << endl;return -1;}ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (ServerSocket == INVALID_SOCKET){cout << "Create Socket Failed::" << GetLastError() << endl;return -1;}LocalAddr.sin_family = AF_INET;LocalAddr.sin_addr.s_addr = inet_addr(IP_ADDRESS);LocalAddr.sin_port = htons(PORT);memset(LocalAddr.sin_zero, 0x00, 8);Ret = bind(ServerSocket, (struct sockaddr*)&LocalAddr, sizeof(LocalAddr));if (Ret != 0){cout << "Bind Socket Failed::" << GetLastError() << endl;return -1;}Ret = listen(ServerSocket, 10);if (Ret != 0){cout << "listen Socket Failed::" << GetLastError() << endl;return -1;}cout << "服务端已经启动" << endl;char RecvBuffer[MAX_PATH];while (true){AddrLen = sizeof(ClientAddr);CientSocket = accept(ServerSocket, (struct sockaddr*)&ClientAddr, &AddrLen);//阻塞的if (CientSocket == INVALID_SOCKET){cout << "Accept Failed::" << GetLastError() << endl;break;}cout << "客户端连接::" << inet_ntoa(ClientAddr.sin_addr) << ":" << ClientAddr.sin_port << endl;int Ret = 0;while (true){memset(RecvBuffer, 0x00, sizeof(RecvBuffer));Ret = recv(CientSocket, RecvBuffer, MAX_PATH, 0);if (Ret == 0 || Ret == SOCKET_ERROR){cout << "客户端退出!" << endl;break;}cout << "接收到客户信息为:" << RecvBuffer << endl;}CloseHandle(hThread);}closesocket(ServerSocket);closesocket(CientSocket);WSACleanup();return 0;}</span>

client端:

<span style="font-family:SimHei;font-size:18px;">#include <iostream>#include <Windows.h>#pragma comment(lib, "ws2_32.lib")using namespace std;#define  PORT 4000#define  IP_ADDRESS "127.0.0.1"int main(int argc, char* argv[]){WSADATA  Ws;SOCKET CientSocket;struct sockaddr_in ServerAddr;int Ret = 0;int AddrLen = 0;HANDLE hThread = NULL;char SendBuffer[MAX_PATH];//Init Windows Socketif (WSAStartup(MAKEWORD(2, 2), &Ws) != 0){cout << "Init Windows Socket Failed::" << GetLastError() << endl;return -1;}//Create SocketCientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (CientSocket == INVALID_SOCKET){cout << "Create Socket Failed::" << GetLastError() << endl;return -1;}ServerAddr.sin_family = AF_INET;ServerAddr.sin_addr.s_addr = inet_addr(IP_ADDRESS);ServerAddr.sin_port = htons(PORT);memset(ServerAddr.sin_zero, 0x00, 8);Ret = connect(CientSocket, (struct sockaddr*)&ServerAddr, sizeof(ServerAddr));if (Ret == SOCKET_ERROR){cout << "Connect Error::" << GetLastError() << endl;return -1;}else{cout << "连接成功!" << endl;}while (true){cin.getline(SendBuffer, sizeof(SendBuffer));Ret = send(CientSocket, SendBuffer, (int)strlen(SendBuffer), 0);if (Ret == SOCKET_ERROR){cout << "Send Info Error::" << GetLastError() << endl;break;}}closesocket(CientSocket);WSACleanup();return 0;}</span>
上面的例子一个客户端只能接收处理一个客户端的请求!

加上select的应用,就能让一个server处理来自多个客户端的请求:

改写后的server端

<span style="font-family:SimHei;font-size:18px;">#include <WinSock2.h>#include <stdio.h>#pragma  comment(lib,"ws2_32.lib")#define PORT 4000bool InitAndListern(SOCKET &sListen){WSADATA wsaData;sockaddr_in local;WORD version = MAKEWORD(2, 0);int ret = WSAStartup(version, &wsaData);if (ret != 0){printf("WSAStarup failed\n");return 0;}local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY;local.sin_port = htons((u_short)PORT);sListen = socket(AF_INET, SOCK_STREAM, 0);if (sListen == INVALID_SOCKET){printf("Initial socket failed\n");return 0;}if (bind(sListen, (sockaddr*)&local, sizeof(local)) != 0){printf("Bind socket failed\n");return 0;}if (listen(sListen, 10) != 0){printf("Listen socket failed\n");return 0;}return 1;}int main(){SOCKET sListen;if (InitAndListern(sListen) == 0)return 0;printf("Server wait for client connect...\n");fd_set fdSocket;FD_ZERO(&fdSocket);FD_SET(sListen, &fdSocket);//将sListen添加进该集合while (true){fd_set fdRead = fdSocket;int nRet = select(NULL, &fdRead, NULL, NULL, NULL);//if (nRet <= 0)break;for (int i = 0; i < (int)fdSocket.fd_count; ++i){if (FD_ISSET(fdSocket.fd_array[i], &fdRead)){if (fdSocket.fd_array[i] == sListen){sockaddr_in addrRemote;int nAddrLen = sizeof(addrRemote);SOCKET sNew = ::accept(sListen, (sockaddr*)&addrRemote, &nAddrLen);FD_SET(sNew, &fdSocket);printf("Clietn %s connected\n", inet_ntoa(addrRemote.sin_addr));}else{char buffer[1024];memset(buffer, 0, 1024);int nRecev = recv(fdSocket.fd_array[i], buffer, 1024, 0);if (nRecev > 0){printf("Received Client Msg:%s\n", buffer);send(fdSocket.fd_array[i], buffer, strlen(buffer), 0);}else{closesocket(fdSocket.fd_array[i]);FD_CLR(fdSocket.fd_array[i], &fdSocket);}}}}}return 0;}</span>


select()的socket只有在有东西的时候才会读入,其实现过程不过如下:

....

int sockfd;

fd_set fdR;

struct timeval timeout=...;

...

for(;;){

FD_ZERO(&fdR);

FD_SET(&fdR);

switch(select(sockfd+1, &fdR, NULL, &timeout){

case -1:

error;

case 0:

error;

default:

if(FDD_ISSET(sockfd)){

accept();

...

}

}


}

select()方法中,所有文件描述符都是阻塞的。使用select判断一组文件描述符中是否有一个可读(写),如果没有就阻塞,直到有一个的时候就唤醒。


2 0
原创粉丝点击