`
zxxapple
  • 浏览: 79073 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

套接字select异步模型

 
阅读更多

Select 模型原理

利用 select 函数,判断套接字上是否存在数据,或者能否向一个套接字写入数据。目的是防止应用程序在套接字处于锁定模式时,调用 recv (或 send )从没有数据的套接字上接收数据,被迫进入阻塞状态。

 

select 参数和返回值意义如下:

int  select   (

  IN  int   nfds,                              //0, 无意义

  IN OUT  fd_set*   readfds,       // 检查可读性

  IN OUT  fd_set*   writefds,       // 检查可写性

  IN OUT fd_set*  exceptfds,   // 例外数据

  IN const  struct timeval*   timeout);      // 函数的返回时间

 

struct   timeval   {

         long     tv_sec;         //

         long     tv_usec;      // 毫秒

};

select 返回 fd_set 中可用的套接字个数。

 

fd_set 是一个 SOCKET 队列 ,以下宏可以对该队列进行操作:

FD_CLR( s, *set)  从队列 set 删除句柄 s;

FD_ISSET( s, *set)   检查句柄 s 是否存在与队列 set ;

FD_SET( s, *set ) 把句柄 s 添加到队列 set ;

FD_ZERO( *set )   set 队列初始化成空队列 .

 

Select 工作流程

1 :用 FD_ZERO 宏来初始化我们感兴趣的 fd_set

也就是 select 函数的第二三四个参数。

2 :用 FD_SET 宏来将套接字句柄分配给相应的 fd_set

如果想要检查一个套接字是否有数据需要接收,可以用 FD_SET 宏把套接接字句柄加入可读性检查队列中

3 :调用 select 函数。

如果该套接字没有数据需要接收, select 函数会把该套接字从可读性检查队列中删除掉,

4 :用 FD_ISSET 对套接字句柄进行检查

如果我们所关注的那个套接字句柄仍然在开始分配的那个 fd_set 里,那么说明马上可以进行相应的 IO   作。比如一个分配给 select 第一个参数的套接字句柄在 select 返回后仍然在 select 第一个参数的 fd_set 里,那么说明当前数据已经来了,   马上可以读取成功而不会被阻塞。

 

 

/*************************************************************************************/

#include "stdafx.h"  

#include <winsock.h>  

#include <stdio.h>  

#define PORT   5150  

#define MSGSIZE   1024  

#pragma comment(lib, "ws2_32.lib")  

int g_iTotalConn = 0;  

SOCKET g_CliSocketArr[FD_SETSIZE];  

DWORD WINAPI WorkerThread(LPVOID lpParam);  

int main(int argc, char* argv[])  

{  

     WSADATA wsaData;  

     SOCKET sListen, sClient;  

     SOCKADDR_IN local, client;  

     int iAddrSize = sizeof(SOCKADDR_IN);  

     DWORD dwThreadId;  

      // Initialize windows socket library  

     WSAStartup(0x0202, &wsaData);  

     // Create listening socket  

     sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  

      // Bind  

     local.sin_family = AF_INET;  

     local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);  

     local.sin_port = htons(PORT);  

     bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));  

     // Listen  

     listen(sListen, 3);  

     // Create worker thread  

     CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);   

     while (TRUE)   

     {  

          // Accept a connection  

         sClient = accept(sListen, (sockaddr*)&client, &iAddrSize);  

         printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));  

          // Add socket to g_CliSocketArr  

         g_CliSocketArr[g_iTotalConn++] = sClient;   

     }  

     return 0;  

}  

DWORD WINAPI WorkerThread(LPVOID lpParam)  

{  

     int i;  

     fd_set fdread;  

     int ret;  

     struct timeval tv = {1, 0};  

     char szMessage[MSGSIZE];  

     while (TRUE)   

     {  

         FD_ZERO(&fdread);      //1 清空队列

         for (i = 0; i < g_iTotalConn; i++)   

         {  

             FD_SET(g_CliSocketArr[i], &fdread);      //2 将要检查的套接口加入队列

         }  

          // We only care read event  

          ret = select(0, &fdread, NULL, NULL, &tv);     //3 查询满足要求的套接字,不满足要求,出队

         if (ret == 0)   

         {  

              // Time expired  

             continue;  

         }  

         for (i = 0; i < g_iTotalConn; i++)   

         {  

             if (FD_ISSET(g_CliSocketArr[i], &fdread))       //4. 是否依然在队列

             {  

                  // A read event happened on g_CliSocketArr  

                 ret = recv(g_CliSocketArr[i], szMessage, MSGSIZE, 0);  

                 if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))   

                 {  

                      // Client socket closed  

                     printf("Client socket %d closed.\n", g_CliSocketArr[i]);  

                     closesocket(g_CliSocketArr[i]);  

                     if (i < g_iTotalConn-1)   

                     {  

                         g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn];  

                     }  

                 }   

                 else   

                 {  

                      // We reveived a message from client  

                     szMessage[ret] = '\0';  

                     send(g_CliSocketArr[i], szMessage, strlen(szMessage), 0);  

                 }  

             }  

         }  

     }  

} 

服务器的几个主要动作如下:

1. 创建监听套接字,绑定,监听;

2. 创建工作者线程;

3. 创建一个套接字数组,用来存放当前所有活动的客户端套接字,每 accept 一个连接就更新一次数组;

4. 接受客户端的连接。

 

这里有一点需要注意的,就是我没有重新定义 FD_SETSIZE 宏,所以服务器最多支持的并发连接数为 64 。而且,这里决不能无条件的 accept, 服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。一种比较好的实现方案就是采用 WSAAccept 函数,而且让 WSAAccept 回调自己实现的 Condition Function 。如下所示:

 

int CALLBACK ConditionFunc(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)

{

if ( 当前连接数   < FD_SETSIZE)

   return CF_ACCEPT;

else

   return CF_REJECT;

}

 

工作者线程里面是一个死循环,一次循环完成的动作是:

1. 将当前所有的客户端套接字加入到读集 fdread 中;

2. 调用 select 函数;

3. 查看某个套接字是否仍然处于读集中,如果是,则接收数据。如果接收的数据长度为 0 ,或者发生 WSAECONNRESET 错误,则表示客户端套接字主动关闭,这时需要将服务器中对应的套接字所绑定的资源释放掉,然后调整我们的套接字数组(将数组中最后一个套接字挪到当前的位置上)

 

除了需要有条件接受客户端的连接外,还需要在连接数为 0 的情形下做特殊处理,因为如果读集中没有任何套接字, select 函数会立刻返回,这将导致工作者线程成为一个毫无停顿的死循环, CPU 的占用率马上达到 100%

 

关系到套接字列表的操作都需要使用循环 , 在轮询的时候 , 需要遍历一次 , 再新的一轮开始时 , 将列表加入队列又需要遍历一次 . 也就是说 ,Select 在工作一次时 , 需要至少遍历 2 次列表 , 这是它效率较低的原因之一 . 在大规模的网络连接方面 , 还是推荐使用 IOCP EPOLL 模型 . 但是 Select 模型可以使用在诸如对战类游戏上 , 比如类似星际这种 , 因为它小巧易于实现 , 而且对战类游戏的网络连接量并不大 .

 

对于 Select 模型想要突破 Windows 64 个限制的话 , 可以采取分段轮询 , 一次轮询 64 . 例如套接字列表为 128 , 在第一次轮询时 , 将前 64 个放入队列中用 Select 进行状态查询 , 待本次操作全部结束后 . 将后 64 个再加入轮询队列中进行轮询处理 . 这样处理需要在非阻塞式下工作 . 以此类推 ,Select 也能支持无限多个 .

分享到:
评论

相关推荐

    异步套接字服务器与客户端

    在Linux或其他类Unix系统中,可以使用Epoll、Poll或Select等I/O多路复用技术来实现异步套接字。这些方法允许应用程序同时监控多个套接字,一旦有事件发生(如数据到达或连接请求),系统会通知程序进行相应的处理。 ...

    线程同步与异步套接字编程

    例如,`select`和`poll`函数是I/O复用的一种实现,它们允许一个线程监控多个套接字,当有就绪事件时,线程会被唤醒。Epoll是Linux系统提供的一种高效I/O复用机制,适用于大量并发连接。另一方面,Windows系统提供了`...

    select选择模型 windows套接字I/O模型

    Windows套接字API提供了一种称为“select”选择模型的机制,用于管理多个网络连接并有效地处理I/O事件。本文将深入探讨select选择模型以及其在Windows套接字I/O中的应用。 首先,我们需要理解什么是I/O模型。I/O...

    异步套接字客户端

    3. 异步模型:异步套接字客户端通常基于事件驱动或IO多路复用模型。例如,Windows上的I/O完成端口(IOCP)、Linux上的epoll、Unix的select/poll等。这些机制使得客户端可以在多个套接字上同时监听事件,无需为每个套...

    局域网聊天程序(xml保存)(异步套接字技术)(只要一分,两个都一样).rar

    2. 异步模型:了解IO多路复用(如select、poll、epoll)、事件驱动(如Windows的IOCP、Linux的epoll)以及回调函数等异步编程模型,选择适合应用场景的技术。 3. 异步处理:设计并实现异步套接字通信流程,包括异步...

    tcp套接字例子windos

    `select()`是多路复用I/O模型的关键函数,用于监控多个套接字的状态。它可以同时检测一组套接字,看哪些准备好了进行读、写或错误处理。`select()`返回一个描述符集,告诉开发者哪些套接字可以进行下一步操作,从而...

    UNIX网络编程 卷1:套接字联网API源代码

    6. **多路复用I/O**:select()和poll()函数用于监控多个套接字的就绪状态,实现非阻塞I/O。epoll()是Linux下更为高效的选择。 7. **并发服务器**:讨论了如何处理多个并发客户端连接,包括多线程、多进程以及异步I/...

    unix网络编程卷1:套接字联网api(第3版)

    2. **套接字API**:书中深入讲解了套接字API的各种函数,如socket()用于创建套接字,bind()将套接字与特定地址绑定,listen()使服务器进入监听状态,connect()和accept()分别用于客户端连接和服务器接受连接,send()...

    异步IO模型编程实例(纯C语言)

    其中,nfds 是传入参数,readfds 是检查可读性的套接字集合,writefds 是检查可写性的套接字集合,exceptfds 是例外数据的套接字集合,timeout 是本次 select 调用最长的等待时间。 Select 函数的返回值是处于就绪...

    网络套接字实例20个

    这些实例将帮助你理解网络套接字编程的细节,包括地址结构(如`sockaddr_in`),网络字节序转换(`htonl`, `htons`, `ntohl`, `ntohs`),以及异步I/O模型(如select、poll、epoll)等。通过分析和运行这些示例,你...

    UNIX网络编程卷1:套接字联网API(第3版)源代码

    4. **非阻塞I/O和异步I/O**:探讨如何处理套接字的阻塞模式,以及如何利用异步I/O模型实现高效的网络程序。 5. **套接字选项和控制消息**:介绍setsockopt()和getsockopt()函数,以及套接字选项如何影响网络通信。...

    Windows套接字IO模型.pdf

    选择模型是最基本且最广泛使用的I/O模型之一,其核心在于使用`select()`函数来监控多个套接字的状态变化。这种模型非常适合于那些希望避免在套接字调用过程中被“锁定”的应用程序,并允许应用程序同时管理多个套接...

    使用套接字接口创建客户端和服务器进程

    在IT领域,网络通信是核心部分之一,而套接字(Sockets)是实现网络通信的基础接口。本文将深入探讨如何使用C++编程语言通过套接字接口来创建客户端和服务器进程,使得它们能在互联网上相互通信。 首先,我们要理解...

    select模型

    `select`模型是一种常见的多路复用I/O模型,用于处理多个套接字(sockets)的读写事件。本篇文章将深入探讨`select`模型的工作原理、使用场景以及如何在实际编程中应用。 `select`函数是POSIX标准的一部分,它允许...

    异步TCP通信模型

    在TCP异步通信中,`select`通常被用来监控多个套接字(sockets)的状态,以便于及时处理来自不同连接的数据。当有新的连接请求或者数据可读写时,`select`会返回就绪的文件描述符集合,这样程序就可以无须阻塞地处理...

    一个服务器和一个客户端,用来展示 网络套接字和select等函数的使用,双方可以通信.zip

    总结来说,这个项目为学习C#网络编程提供了很好的实践平台,涵盖了套接字通信、TCP连接、`select`函数的使用以及异步编程等多个重要概念。通过深入理解并实践这些知识,开发者能够构建高效、可靠的网络应用程序。

    网络游戏套接字IO模型.rar_Vc_网络游戏

    本资料“网络游戏套接字IO模型.rar”是一个专门针对VC++(Visual C++)环境下的网络游戏开发的资源包,包含了一个名为“CSocketModel”的源代码示例,用于演示如何构建和实现网络通信模型。以下将详细解释这个主题中...

    套接字编程

    在TCP/IP网络中,套接字编程遵循客户机/服务器模型,这是一种非对等的异步通信方式。下面我们将详细探讨套接字编程的各个关键知识点。 1. **客户机/服务器模式**: - 客户机(Client)是发起通信的一方,它主动...

    socket套接字聊天程序

    11. **网络编程模型**:除了基本的同步IO模型,还可以使用异步IO(如select、poll、epoll等)、事件驱动(如Reactor模式、Proactor模式)或者非阻塞IO来提高服务器的并发处理能力。 12. **调试与日志**:在开发过程...

Global site tag (gtag.js) - Google Analytics