socket之 select模型
前段时间一直想学习网络编程的select模型,看了《windows网络编程》的介绍,参考了别人的博客。
这里的资料主要来自http://www.cnblogs.com/RascallySnake/archive/2013/07/11/3185071.html ,感谢博主的无私奉献。
在掌握了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++;
}
26 接下来,我们需要在我们的窗口添加对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模型的更多相关文章
- windows socket编程select模型使用
int select( int nfds, //忽略 fd_ser* readfds, //指向一个套接字集合,用来检测其可读性 ...
- socket select模型
由于socket recv()方法是堵塞式的,当多个客户端连接服务器时,其中一个socket的recv调用时,会产生堵塞,使其他连接不能继续. 如果想改变这种一直等下去的焦急状态,可以多线程来实现(不 ...
- socket编程的select模型
在掌握了socket相关的一些函数后,套接字编程还是比较简单的,日常工作中碰到很多的问题就是客户端/服务器模型中,如何让服务端在同一时间高效的处理多个客户端的连接,我们的处理办法可能会是在服务端不停的 ...
- socket select()模型
转载:http://www.cnblogs.com/xiangshancuizhu/archive/2012/10/05/2711882.html 由于socket recv()方法是阻塞式的,当有多 ...
- Windows socket I/O模型 之 select(2)
在Windows socket I/O模型 之 select(1)中.我们仅仅是在console中简单的模拟了select的处理方法. 还有非常多特性不能改动.比方仅仅能写,不能读. 没使用线程.也 ...
- Socket I/O模型之select模型
socket网络编程中有多种常见的I/O模型: 1.blocking阻塞 2.nonblocking非阻塞 3.I/O multiplexing复用 4.signal driven 5.asynchr ...
- 关于 Poco::TCPServer框架 (windows 下使用的是 select模型) 学习笔记.
说明 为何要写这篇文章 ,之前看过阿二的梦想船的<Poco::TCPServer框架解析> http://www.cppblog.com/richbirdandy/archive/2010 ...
- linux下多路复用模型之Select模型
Linux关于并发网络分为Apache模型(Process per Connection (进程连接) ) 和TPC , 还有select模型,以及poll模型(一般是Epoll模型) Select模 ...
- Select模型及tcp select模型
参考:http://m.blog.csdn.net/article/details?id=51420015 一.套接字模式 套接字模式简单的决定了操作套接字时,Winsock函数是如何运转的.Wins ...
随机推荐
- NewWord
identification: 鉴定,识别; 验明; 身份证明; 认同; peer:PEER-TO-PEER:同等延迟机制.根据网络中共享资源方式的不同,局域网有两种组织形式 filters: n. ...
- 44.JS--hover事件防重复效果
遇到一种情况,就是运用hover事件的时候,会出现对象(主要是图片)闪动现象.主要是由于hover事件重复触发导致这一现象. html: <p class="smallImg" ...
- node.js初识05
小项目,需求,通过url来查询学生学号和老师的编号 05.js var http = require("http"); var server = http.createServer ...
- 不同版本Hibernate.获取SessionFactory的方式
不同版本Hibernate.获取SessionFactory的方式 Hibernate 版本说明: 我当前使用的是 Hibernate 5.x ,(hibernate-release-5.3.6.Fi ...
- LeetCode110.平衡二叉树
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1. 示例 1: 给定二叉树 [3,9,20,null,null,15,7] 3 / \ 9 20 / \ 15 7 返回 true . 示例 ...
- jdbc连接oracle时使用的字符串格式
两种方式: SID的方式: jdbc:oracle:thin:@[HOST][:PORT]:SID SERVICE_NAME的方式: jdbc:oracle:thin:@//[HOST][:PORT] ...
- Lua之table
Lua table(表) 参考:http://www.runoob.com/lua/lua-tables.html table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数字.字典 ...
- python 将word另存为txt
import os import os.path from win32com import client as wc c=[] rootdir=["d:/77"] #以该路径为 ...
- 擠出線寬(Extrusion width),要怎麼設定?
擠出線寬(Extrusion width),要怎麼設定? Slic3r的作者,把這邊的%設定,跟"層高"做連結.我個人認為擠出線寬,要以噴頭孔徑當做設定參考才好.層高應該只要設定成 ...
- 大数据处理框架之Strom: Storm----helloword
大数据处理框架之Strom: Storm----helloword Storm按照设计好的拓扑流程运转,所以写代码之前要先设计好拓扑图.这里写一个简单的拓扑: 第一步:创建一个拓扑类含有main方法的 ...