前段时间一直想学习网络编程的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. GCC 用户态&内核态 Makefile

    转了一圈,今天再次回到C 网上一篇博文,个人感觉良心作品,故而拿来重新实现一遍,原作者原文有问题,我这里把他打通了 一.GCC Makefile //hello.c #include <stdi ...

  2. iOS 开发笔记-加载/初始化

    ViewDidLoad 一般我们会在这里做界面上的初始化操作,比如往view中添加一些子视图.从数据库或者网络加载模型数据装配到子视图中 在自定义控制里 initWithFrame:一般用于添加控件, ...

  3. border的特殊用法

    大家很容易在一些网页上看到二级菜单上有一个小的三角形,这个小三角型 除了可以使用图片或者使用iconfont写出来,还可以使用border写出来 这边简单的为大家举一个例子,希望对大家有用吧! css ...

  4. Ireport第一张web项目报表。

    原先项目里面的统计分析报表都是和普通的系统页面一样开发的,SSM架构,从数据库一层一层往前面传数据,最后通过jsp表现出来,这次在领导的建议下使用IReport进行报表开发,果然还是要使用工具啊,社会 ...

  5. Css预处理器---Less(三)

    四.Color函数 1.less提供的颜色运算函数,颜色会被转换成HSL色彩空间,然后再通道级别进行操作,函数如下: lighten(@color, 10%); //return a color wh ...

  6. db2修改表结构,增加列,删除列等

    增加一列:ALTER TABLE tbname ADD COLUMN columnName datatype; 增加多列:ALTER TABLE tbname ADD COLUMN columnNam ...

  7. notepad怎么把空格替换成回车?

    替换时选中“使用正则表达式”查找里输入\s替换里输入\r然后“全部替换”即可

  8. awk命令学习(1)

    awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大.简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各 ...

  9. python django简单的登陆实现

    实现方法: 1,可以先定义一个基础的页面访问路径 例如:http://127.0.0.1:8000/index/  定义index路径 在urls urlpatterns = [ url(r'^ind ...

  10. Memento Mori (二维前缀和 + 枚举剪枝)

    枚举指的是枚举矩阵的上下界,然后根据p0, p1, p2的关系去找出另外的中间2个点.然后需要记忆化一些地方防止重复减少时间复杂度.这应该是最关键的一步优化时间,指的就是代码中to数组.然后就是子矩阵 ...