前段时间一直想学习网络编程的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模型的更多相关文章

  1. windows socket编程select模型使用

    int select(         int nfds,            //忽略         fd_ser* readfds,    //指向一个套接字集合,用来检测其可读性       ...

  2. socket select模型

    由于socket recv()方法是堵塞式的,当多个客户端连接服务器时,其中一个socket的recv调用时,会产生堵塞,使其他连接不能继续. 如果想改变这种一直等下去的焦急状态,可以多线程来实现(不 ...

  3. socket编程的select模型

    在掌握了socket相关的一些函数后,套接字编程还是比较简单的,日常工作中碰到很多的问题就是客户端/服务器模型中,如何让服务端在同一时间高效的处理多个客户端的连接,我们的处理办法可能会是在服务端不停的 ...

  4. socket select()模型

    转载:http://www.cnblogs.com/xiangshancuizhu/archive/2012/10/05/2711882.html 由于socket recv()方法是阻塞式的,当有多 ...

  5. Windows socket I/O模型 之 select(2)

    在Windows socket I/O模型 之  select(1)中.我们仅仅是在console中简单的模拟了select的处理方法. 还有非常多特性不能改动.比方仅仅能写,不能读. 没使用线程.也 ...

  6. Socket I/O模型之select模型

    socket网络编程中有多种常见的I/O模型: 1.blocking阻塞 2.nonblocking非阻塞 3.I/O multiplexing复用 4.signal driven 5.asynchr ...

  7. 关于 Poco::TCPServer框架 (windows 下使用的是 select模型) 学习笔记.

    说明 为何要写这篇文章 ,之前看过阿二的梦想船的<Poco::TCPServer框架解析> http://www.cppblog.com/richbirdandy/archive/2010 ...

  8. linux下多路复用模型之Select模型

    Linux关于并发网络分为Apache模型(Process per Connection (进程连接) ) 和TPC , 还有select模型,以及poll模型(一般是Epoll模型) Select模 ...

  9. Select模型及tcp select模型

    参考:http://m.blog.csdn.net/article/details?id=51420015 一.套接字模式 套接字模式简单的决定了操作套接字时,Winsock函数是如何运转的.Wins ...

随机推荐

  1. C++ 退出双层for循环,解决 break、return、continue无法实现问题

    遇到一个情景,采用双层for循环 遍历图像的像素,当找到某一个像素点满足条件时,退出双层for 循环 . 首先了解一下 continue.break.return 各自功能用法: 1.continue ...

  2. 连接mysql && ERROR 2003 (HY000): Can't connect to MySQL server on 'localhost' (10061)

    上一篇:mysql服务正在启动 mysql服务无法启动 && mysql启动脚本 mysql关闭脚本 此篇目编写一个核心目的: 1.mysql连接 先抛出一个问题 这是因为mysql服 ...

  3. VB脚本错误,系统找不到制定的文件 。代码:80070002

    希望得到网友的答案:非常感谢!

  4. MyBatis基础入门《九》ResultMap自动匹配

    MyBatis基础入门<九>ResultMap自动匹配 描述: Mybatis执行select查询后,使用ResultMap接收查询的数据结果. 实体类:TblClient.java 接口 ...

  5. windows Server2012 IIS8.0配置安装完整教程

    IIS8.0是windows Server2012自带的服务器管理系统,和以往不同,IIS8.0安装和操作都比较简单,界面很简洁,安装也很迅速.今天我们重点完整的演示下Internet Informa ...

  6. JDBC连接自定义sqlserver数据库实例名(多个实例)

    java语言中,通过jdbc访问sqlserver2005(2008)数据库默认实例可以按常用的写法来写url连接.代码如下: <span style="font-size:12px; ...

  7. Js闭包学习笔记

    好多内容摘抄了大神的博客内容,只为分享记录.如有冒犯,请见谅 参考文章 http://www.cnblogs.com/libin-1/p/5962269.html http://www.cnblogs ...

  8. Java IO留存查看

    IO也可以写作为 "i/O" ,也可以理解为In和Out,即输入与输出.所以,IO体系的基本功能就是: 读和写. IO流作用:读写设备上的东西,硬盘文件.内存.键盘.网络... 根 ...

  9. Spring Boot中Service用@Transactional 注解

    一般来说function2和function1用的是同一个Transaction. 这个取决于@Transactional 的 propagation设置(事务的传播性) 默认的是 1 @Transa ...

  10. Linq To SQL LEFT OUTER JOIN (Left Join)

    SQL: SELECT [t0].[ProductName], [t1].[TotalPrice] AS [TotalPrice] FROM [Product] AS [t0] LEFT OUTER ...