windows Socket编程之select网络模型

在此之前呢,介绍了TCP/UDP的服务端的实现。但是,它们有很大的缺点,比如说,效率很低,开销太大等。因此,接下来我们先介绍select网络模型。

我们在TCP的服务端里边,接收一个客户端的时候,我们调用accept函数,这个函数会返回一个客户端的socket,我们在主线程里边不停的接收客户的连接,每当有客户连接时,我们就会在开一个线程,用于对客户的服务。因此,如果有N个的客户进行连接的话,那么线程数量就会有N+1个(N个服务线程+主线程),若N比较大,则线程就会非常多,以至于将整个电脑都给拖垮掉。而我们的select模型呢,就是为了解决这个问题而设计的。接下来看下它是如何实现的。

首先,TCP的服务端一样,它需要初始化环境,然后执行绑定,监听等操作。但是之后,我们会直接开一个线程来对客户进行服务,然后才是我们原来的一个循环,来接待客户的连接。接下来看一个结构体,其声明如下:

typedef struct fd_set {
  u_int    fd_count;                 // 有多少个socket
  SOCKET   fd_array[FD_SETSIZE];     // 客户端socket数组,FD_SETSIZE为64
} fd_set;
当我们调用accept来获取客户端的连接之后,会调用FD_SET这个宏,它实际上是会将我们的客户那个socket保存到fd_array这个数组里边去,因为这个数组最大为64个,所以最多只能有64个客户端进行连接。

我们把服务客户的那个线程叫做工作者线程,在里边我们会调用一个函数叫做select,其声明如下:

int select(  
  int nfds,                            //已经忽略了
  fd_set FAR *readfds,                 //可读fd_set的地址
  fd_set FAR *writefds,                //可写fd_set地址
  fd_set FAR *exceptfds,               //异常错误fd_set地址
  const struct timeval FAR *timeout    //timeval结构
);

这个函数会检查fd_array这个数组里边所有的socket是否有信号到来,如果有就成功返回,否则会阻塞在这里,不过我们在最后一个参数那里,传一个等待时间。

调用完select之后,我们可以在调用FD_ISSET这个宏来判断是fd_array这个数组里边的那个socket有信号了。之后我们就可以进行数据收发了。

以下是select的示例代码:

#include 
#include 
#define PORT 6000
#pragma comment (lib, "Ws2_32.lib")
fd_set  g_fdClientSock;
int clientNum = 0;

BOOL WinSockInit()
{
	WSADATA data = {0};
	if(WSAStartup(MAKEWORD(2, 2), &data))
		return FALSE;
	if ( LOBYTE(data.wVersion) !=2 || HIBYTE(data.wVersion) != 2 ){
		WSACleanup();
		return FALSE;
	}
	return TRUE;
}
// 工作者线程 
DWORD WINAPI WorkThreadProc(LPARAM lparam)
{
	fd_set fdRead;
	FD_ZERO( &fdRead );
	int nRet = 0;
	char* recvBuffer =(char*)malloc( sizeof(char) * 1024 );
	if ( recvBuffer == NULL )
	    return -1;
	memset( recvBuffer, 0, sizeof(char) * 1024 );
	while ( true )
	{
	    fdRead = g_fdClientSock;
	    timeval tv;
		tv.tv_sec = 0;
		tv.tv_usec = 10;
	    //检查fd_arrray数组里边是否有信号到来
	    nRet = select( 0, &fdRead, NULL, NULL, &tv );
	    if ( nRet != SOCKET_ERROR )
	    {
	    	
	        for ( int i = 0; i < g_fdClientSock.fd_count; i++ )
	        {
	        	// 遍历出来哪些SOCKET有信号
	            if ( FD_ISSET(g_fdClientSock.fd_array[i],&fdRead)  )
	            {
	            	// 下面是数据的收发
	                memset( recvBuffer, 0, sizeof(char) * 1024 );
	                nRet = recv( g_fdClientSock.fd_array[i], recvBuffer, 1024, 0);
	                if ( nRet == SOCKET_ERROR )
	                {
	                    closesocket( g_fdClientSock.fd_array[i]);
	                    clientNum--;
	                    FD_CLR( g_fdClientSock.fd_array[i], &g_fdClientSock );
	                }
	                else if ( nRet == 0 )
	                {
	                    closesocket( g_fdClientSock.fd_array[i]);
	                    clientNum--;
	                    FD_CLR( g_fdClientSock.fd_array[i], &g_fdClientSock );
	                }
	                else
	                {	                    
	                    printf("Recv msg:%s\n",recvBuffer);
	                    send(g_fdClientSock.fd_array[i], recvBuffer, strlen(recvBuffer), 0);
	                }
	            }
	        }
	    }
	}

	if ( recvBuffer != NULL )
	    free( recvBuffer );
	return 0;
}
int main()
{
	//初始化环境
	WinSockInit();

	SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = htonl(INADDR_ANY);
	server.sin_port = htons(PORT);
	//绑定
	int ret = bind(listenSock, (sockaddr*)&server, sizeof(server));
	//监听
	ret = listen(listenSock, 4);

	sockaddr_in clientAddr;
	int nameLen = sizeof( clientAddr );
	// 先把工作者线程创建起来
	CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)WorkThreadProc, NULL, NULL, NULL);

	while( clientNum < FD_SETSIZE )//FD_SETSIZE==64
	{
		// 当有一个客户端进行连接时,主线程的accept会进行返回
		SOCKET clientSock = accept( listenSock, (sockaddr*)&clientAddr, &nameLen );

		FD_SET(clientSock, &g_fdClientSock);

		clientNum++;
	}
	closesocket(listenSock);
	WSACleanup();
	return 0;
}

你可能感兴趣的:(windows编程)