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

总体上来说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 Socket
	if (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 Socket
	if (WSAStartup(MAKEWORD(2, 2), &Ws) != 0)
	{
		cout << "Init Windows Socket Failed::" << GetLastError() << endl;
		return -1;
	}

	//Create Socket
	CientSocket = 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 4000

bool 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判断一组文件描述符中是否有一个可读(写),如果没有就阻塞,直到有一个的时候就唤醒。


你可能感兴趣的:(windows,tcp,select,套接字,winsock)