socket编程的select模型
在掌握了socket相关的一些函数后,套接字编程还是比较简单的,日常工作中碰到很多的问题就是客户端/服务器模型中,如何让服务端在同一时间高效的处理多个客户端的连接,我们的处理办法可能会是在服务端不停的监听客户端的请求,有新的请求到达时,开辟一个新的线程去和该客户端进行后续处理,但是这样针对每一个客户端都需要去开辟一个新的线程,效率必定底下。
其实,socket编程提供了很多的模型来处理这种情形,我们只要按照模型去实现我们的代码就可以解决这个问题。主要有select模型和重叠I/o模型,以及完成端口模型。这次,我们主要介绍下select模型,该模型又分为普通select模型,wsaasyncselect模型,wsaeventselect模型。我们将通过样例代码的方式逐一介绍。
一、select模型
使用该模型时,在服务端我们可以开辟两个线程,一个线程用来监听客户端的连接
请求,另一个用来处理客户端的请求。主要用到的函数为select函数。如:
- 全局变量:
- fd_set g_fdClientSock;
线程1处理函数:
- SOCKET listenSock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
- sockaddr_in sin;
- sin.sin_family = AF_INET;
- sin.sin_port = htons();
- sin.sin_addr.S_un.S_addr = INADDR_ANY;
- int nRet = bind( listenSock, (sockaddr*)&sin, (int)(sizeof(sin)));
- if ( nRet == SOCKET_ERROR )
- {
- DWORD errCode = GetLastError();
return;- }
- listen( listenSock, );
- int clientNum = ;
- sockaddr_in clientAddr;
- int nameLen = sizeof( clientAddr );
- while( clientNum < FD_SETSIZE )
- {
- SOCKET clientSock = accept( listenSock, (sockaddr*)&clientAddr, &nameLen );
FD_SET( clientSock, &g_fdClientSock);- clientNum++;
}
线程2处理函数:
- fd_set fdRead;
- FD_ZERO( &fdRead );
- int nRet = ;
char* recvBuffer =(char*)malloc( sizeof(char) * );- if ( recvBuffer == NULL )
- {
- return;
}- memset( recvBuffer, , sizeof(char) * );
- while ( true )
- {
- fdRead = g_fdClientSock;
- nRet = select( , &fdRead, NULL, NULL, NULL );
- if ( nRet != SOCKET_ERROR )
- {
- for ( int i = ; i < g_fdClientSock.fd_count; i++ )
- {
- if ( FD_ISSET(g_fdClientSock.fd_array[i],&fdRead) )
- {
- memset( recvBuffer, , sizeof(char) * );
- nRet = recv( g_fdClientSock.fd_array[i], recvBuffer, , );
- if ( nRet == SOCKET_ERROR )
- {
- closesocket( g_fdClientSock.fd_array[i] );
- FD_CLR( g_fdClientSock.fd_array[i], &g_fdClientSock );
- }
- else
- {
- //todo:后续处理
- }
- }
- }
- }
- }
- if ( recvBuffer != NULL )
- {
free( recvBuffer );
}
该模型有个最大的缺点就是,它需要一个死循环不停的去遍历所有的客户端套接字集合,询问是否有数据到来,这样,如果连接的客户端很多,势必会影响处理客户端请求的效率,但它的优点就是解决了每一个客户端都去开辟新的线程与其通信的问题。如果有一个模型,可以不用去轮询客户端套接字集合,而是等待系统通知,当有客户端数据到来时,系统自动的通知我们的程序,这就解决了select模型带来的问题了。
二、WsaAsyncSelect模型
WsaAsyncSelect模型就是这样一个解决了普通select模型问题的socket编程模型。它是在有客户端数据到来时,系统发送消息给我们的程序,我们的程序只要定义好消息的处理方法就可以了,用到的函数只要是WSAAsyncSelect,如:
首先,我们定义一个Windows消息,告诉系统,当有客户端数据到来时,发送该消息给我们。
- #define UM_SOCK_ASYNCRECVMSG WM_USER + 1
在我们的处理函数中可以如下监听客户端的连接:
- SOCKET listenSock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
- sockaddr_in sin;
- sin.sin_family = AF_INET;
- sin.sin_port = htons();
- sin.sin_addr.S_un.S_addr = INADDR_ANY;
- int nRet = bind( listenSock, (sockaddr*)&sin, (int)(sizeof(sin)));
- if ( nRet == SOCKET_ERROR )
- {
- DWORD errCode = GetLastError();
- return;
}- listen( listenSock, );
- int clientNum = ;
sockaddr_in clientAddr;- int nameLen = sizeof( clientAddr );
- while( clientNum < FD_SETSIZE )
{- SOCKET clientSock = accept( listenSock, (sockaddr*)&clientAddr, &nameLen );
- //hWnd为接收系统发送的消息的窗口句柄
- WSAAsyncSelect( clientSock, hWnd, UM_SOCK_ASYNCRECVMSG, FD_READ | FD_CLOSE );
- clientNum++;
- }
接下来,我们需要在我们的窗口添加对UM_SOCK_ASYNCRECVMSG消息的处理函数,在该函数中真正接收客户端发送过来的数据,在这个消息处理函数中的wparam参数表示的是客户端套接字,lparam参数表示的是发生的网络事件如:
- SOCKET clientSock = (SOCKET)wParam;
- if ( WSAGETSELECTERROR( lParam ) )
- {
closesocket( clientSock );- return;
}- switch ( WSAGETSELECTEVENT( lParam ) )
{- case FD_READ:
{
char recvBuffer[] = {'\0'};- int nRet = recv( clientSock, recvBuffer, , );
- if ( nRet > )
- {
- szRecvMsg.AppendFormat(_T("Client %d Say:%s\r\n"), clientSock, recvBuffer );
- }
- else
- {
- //client disconnect
- szRecvMsg.AppendFormat(_T("Client %d Disconnect!\r\n"), clientSock );
- }
}- break;
- case FD_CLOSE:
- {
closesocket( clientSock );- szRecvMsg.AppendFormat(_T("Client %d Disconnect!\r\n"), clientSock );
- }
- break;
- }
可以看到WsaAsyncSelect模型是非常简单的模型,它解决了普通select模型的问题,但是它最大的缺点就是它只能用在windows程序上,因为它需要一个接收系统消息的窗口句柄,那么有没有一个模型既可以解决select模型的问题,又不限定只能是windows程序才能用呢?下面我们来看看WsaEventSelect模型。
三、WsaEventSelect模型
WsaEventSelect模型是一个不用主动去轮询所有客户端套接字是否有数据到来的模型,它也是在客户端有数据到来时,系统发送通知给我们的程序,但是,它不是发送消息,而是通过事件的方式来通知我们的程序,这就解决了WsaAsyncSelect模型只能用在windows程序的问题。
该模型的实现,我们也可以开辟两个线程来进行处理,一个用来接收客户端的连接请求,一个用来与客户端进行通信,用到的主要函数有WSAEventSelect,WSAWaitForMultipleEvents,WSAEnumNetworkEvents实现方式如下:
首先定义三个全局数组
- SOCKET g_SockArray[MAX_NUM_SOCKET];//存放客户端套接字
- WSAEVENT g_EventArray[MAX_NUM_SOCKET];//存放该客户端有数据到来时,触发的事件
- UINT32 g_totalEvent = ;//记录客户端的连接数
线程1处理函数如下:
- SOCKET listenSock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
- sockaddr_in sin;
- sin.sin_family = AF_INET;
- sin.sin_port = htons();
- sin.sin_addr.S_un.S_addr = INADDR_ANY;
- int nRet = bind( listenSock, (sockaddr*)&sin, (int)(sizeof(sin)));
- if ( nRet == SOCKET_ERROR )
- {
- DWORD errCode = GetLastError();
- return;
- }
- listen( listenSock, );
- sockaddr_in clientAddr;
- int nameLen = sizeof( clientAddr );
- while( g_totalEvent < MAX_NUM_SOCKET )
- {
- SOCKET clientSock = accept( listenSock, (sockaddr*)&clientAddr, &nameLen );
- if ( clientSock == INVALID_SOCKET )
- {
- continue;
- }
- g_SockArray[g_totalEvent] = clientSock;
- if( (g_EventArray[g_totalEvent] = WSACreateEvent()) == WSA_INVALID_EVENT )
- {
- continue;
- }
- WSAEventSelect( clientSock, g_EventArray[g_totalEvent],FD_READ | FD_CLOSE );
- g_totalEvent++;
- }
线程2的处理函数如下:
- int nIndex = ;
- char* recvBuffer =(char*)malloc( sizeof(char) * );
- if ( recvBuffer == NULL )
- {
- return;
- }
- memset( recvBuffer, , sizeof(char) * );
- while( true )
- {
- nIndex = WSAWaitForMultipleEvents( g_totalEvent, g_EventArray, FALSE, WSA_INFINITE,FALSE );
- if ( nIndex == WSA_WAIT_FAILED )
- {
- continue;
- }
- else
- {
- WSAResetEvent( g_EventArray[ nIndex - WSA_WAIT_EVENT_0]);
- SOCKET clientSock = g_SockArray[ nIndex - WSA_WAIT_EVENT_0 ];
- WSANETWORKEVENTS wsaNetWorkEvent;
- int nRet = WSAEnumNetworkEvents( clientSock, g_EventArray[nIndex - WSA_WAIT_EVENT_0], &wsaNetWorkEvent );
- if ( SOCKET_ERROR == nRet )
- {
- continue;
- }
- else if ( wsaNetWorkEvent.lNetworkEvents & FD_READ )
- {
- if ( wsaNetWorkEvent.iErrorCode[FD_READ_BIT] != )
- {
- //occur error
- closesocket( clientSock );
- }
- else
- {
- memset( recvBuffer, , sizeof(char) * );
- nRet = recv( clientSock, recvBuffer, , );
- if ( nRet == SOCKET_ERROR )
- {
- closesocket( clientSock );
- }
- else
- {
- //todo:对接收到的客户端数据进行处理
- }
- }
- }
- else if( wsaNetWorkEvent.lNetworkEvents & FD_CLOSE )
- {
- if ( wsaNetWorkEvent.iErrorCode[FD_CLOSE_BIT] != )
- {
- //occur error
- closesocket( clientSock );
- }
- else
- {
- closesocket( clientSock );
- }
- }
- }
- }
- if ( recvBuffer != NULL )
- {
- free( recvBuffer );
- }
该模型通过一个死循环里面调用WSAWaitForMultipleEvents函数来等待客户端套接字对应的Event的到来,一旦事件通知到达,就通过该套接字去接收数据。虽然WsaEventSelect模型的实现较前两种方法复杂,但它在效率和兼容性方面是最好的。
以上三种模型虽然在效率方面有了不少的提升,但它们都存在一个问题,就是都预设了只能接收64个客户端连接,虽然我们在实现时可以不受这个限制,但是那样,它们所带来的效率提升又将打折扣,那又有没有什么模型可以解决这个问题呢?我们的下一篇重叠I/0模型将解决这个问题
socket编程的select模型的更多相关文章
- socket编程五种模型
客户端:创建套接字,连接服务器,然后不停的发送和接收数据. 比较容易想到的一种服务器模型就是采用一个主线程,负责监听客户端的连接请求,当接收到某个客户端的连接请求后,创建一个专门用于和该客户端通信的套 ...
- socket编程以及select、epoll、poll示例详解
socket编程socket这个词可以表示很多概念,在TCP/IP协议中“IP地址 + TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP + 端口号”就称为socket.在TCP协议中,建立 ...
- 从 Socket 编程谈谈 IO 模型(三)
快过年啦,估计很多朋友已在摸鱼的路上.而我为了兄弟们年后的追逐,却在苦苦寻觅.规划,导致文章更新晚了些,各位猿粉谅解. 上期分享,我们结合新春送祝福的场景,通过一坨坨的代码让 BIO.NIO 编程过程 ...
- 【socket编程】select manual page翻译
原文: select manual page 依赖的头文件 /* According to POSIX.1-2001, POSIX.1-2008 */ #include <sys/select. ...
- socket之 select模型
前段时间一直想学习网络编程的select模型,看了<windows网络编程>的介绍,参考了别人的博客. 这里的资料主要来自http://www.cnblogs.com/RascallySn ...
- windows socket编程select模型使用
int select( int nfds, //忽略 fd_ser* readfds, //指向一个套接字集合,用来检测其可读性 ...
- winsock编程select模型
winsock编程select模型 网络服务端连接数量过多时,为每一个连接申请一个线程会让机器性能急剧下降(大多说是因为线程在用户态和内核态之间切换会占用大量的CPU时间片).为了解决多线程带来的性能 ...
- 网络编程第六讲Select模型
网络模型第六讲Select模型 一丶Select模型是什么 以前我们讲过一个迭代模型.就是只服务一个客户端连接.但是实际网络编程中.复杂的很多. 比如一个 C/S架构程序 (客户端/服务端) 客户端很 ...
- 网络编程中select模型和poll模型学习(linux)
一.概述 并发的网络编程中不管是阻塞式IO还是非阻塞式IO,都不能很好的解决同时处理多个socket的问题.操作系统提供了复用IO模型:select和poll,帮助我们解决了这个问题.这两个函数都能够 ...
随机推荐
- 数据分析和R语言的那点事儿_1
最近遇到一些程序员同学向我了解R语言,有些更是想转行做数据分析,故开始学习R或者Python之类的语言.在有其他编程语言的背景下,学习R的语法的确是一件十分简单的事.霸特,如果以为仅仅是这样的话那就图 ...
- IntelliLock
IntelliLock的使用说明: http://blog.csdn.net/gnicky/article/details/20737107 http://download.csdn.net/deta ...
- 关于Android的背景色配色小结
三基色原理:三基色是指红,绿,蓝三色,人眼对红.绿.蓝最为敏感,大多数的颜色可以通过红.绿.蓝三色按照不同的比例合成产生.同样绝大多数单色光也可以分解成红绿蓝三种色光.这是色度学的最基本原理,即三基色 ...
- 一张图片说明MII
- windows下与linux下安装redis及redis扩展
1. Redis的介绍 Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.从2010年3月15日起 ...
- SQLServer内核架构剖析 (转载)
SQL Server内核架构剖析 (转载) 这篇文章在我电脑里好长时间了,今天不小心给翻出来了,觉得写得很不错,因此贴出来共享. 不得不承认的是,一个优秀的软件是一步一步脚踏实地积累起来的,众多优秀的 ...
- Java Spring IOC用法
Java Spring IOC用法 Spring IoC 在前两篇文章中,我们讲了java web环境搭建 和 java web项目搭建,现在看下spring ioc在java中的运用,开发工具为In ...
- HTTP协议中POST、GET、HEAD、PUT等请求方法以及一些常见错误
(来源:http://www.tuicool.com/articles/Ermmmyn) HTTP请求方法: 常用方法: Get\Post\Head (1)Get方法. 取回请求URL标志的任何信息, ...
- asp.net core webapi之跨域(Cors)访问
这里说的跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据.只要协议.域名.端口有任何一个不同,都被当作 ...
- [.NET领域驱动设计实战系列]专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
一.前言 在前面2篇博文中,我分别介绍了规约模式和工作单元模式,有了前面2篇博文的铺垫之后,下面就具体看看如何把这两种模式引入到之前的网上书店案例里. 二.规约模式的引入 在第三专题我们已经详细介绍了 ...