文档章节

Winsock IO模型之select模型

r
 ranjiewen
发布于 2016/11/03 23:50
字数 1574
阅读 7
收藏 0

     之所以称其为select模型是因为它主要是使用select函数来管理I/O的。这个模型的设计源于UNIX系统,目的是允许那些想要避免在套接字调用上阻塞的应用程序有能力管理多个套接字。

    int select(

         int nfds,                                                 // 忽略,仅是为了与Berkeley套接字兼容

         fd_set* readfds,                                  // 指向一个套接字集合,用来检查其可读性

         fd_set* writefds,                                 // 指向一个套接字集合,用来检查其可写性

         fd_set* exceptfds,                              // 指向一个套接字集合,用来检查错误

         const struct timeval* timeout           // 指定此函数等待的最长时间,如果为NULL,则最长时间为无限大

    );

 

 Select模型是最常见的I/O模型。

使用 int select( int nfds , fd_set FAR* readfds , fd_set FAR* writefds,fd_set FAR* exceptfds,const struct timeval FAR * timeout ) ;

函数来检查你要调用的Socket套接字是否已经有了需要处理的数据。

select包含三个Socket队列,分别代表: readfds ,检查可读性,writefds,检查可写性,exceptfds,例外数据。 timeout是select函数的返回时间。

例如,我们想要检查一个套接字是否有数据需要接收,我们可以把套接字句柄加入可读性检查队列中,然后调用select,如果,该套接字没有数据需要接收, select函数会把该套接字从可读性检查队列中删除掉,所以我们只要检查该套接字句柄是否还存在于可读性队列中,就可以知道到底有没有数据需要接收了。

timeout参数控制select完成的时间。若timeout参数为空指针,则select将一直阻塞到有一个描述字满足条件,否则的话,timeout指向一个timeval结构,其中指定了select调用在返回前等待多长时间。如果timeval为{0,0},则select立即返回,这可用于探询所选套接口的状态,如果处于这种状态,则select调用可认为是非阻塞的,且一切适用于非阻塞调用的假设都适用于它,举例来说,阻塞钩子函数不应被调用,且WINDOWS套接口实现不应yield。

    函数调用成功,返回发生网络事件的所有套接字数量的总和。如果超过了时间限制返回0,失败则返回SOCKET_ERROR。

    typedef struct fd_set {

                  u_int fd_count;                                       // 下面数组的大小

                  SOCKET fd_array[FD_SETSIZE];       // 套接字句柄数组

     } fd_set;

  • FD_ZERO(*set)                            初始化set为空集合。集合在使用前应该总是清空
  • FD_CLR(s, *set)                          从set移除套接字s
  • FD_ISSET(s, *set)                       检查s是不是set的成员,如果是返回TRUE
  • FD_SET(s, *set)                           添加套接字到集合
 
    typedef struct timeval{
                  long tv_sec;                        // 指示等待多少秒
                  long tv_usec;                      // 指示等待多少毫秒
    } timeval;
 
下面给出使用select模式的例子,运行后在4567端口监听,接受客户端连接请求,打印出接收到的数据。(原来单线程也可以管理多个套接字)

 
//////////////////////////////////////////////////////////  
// initsock.h文件  

#include <winsock2.h>  
#pragma comment(lib, "WS2_32")  // 链接到WS2_32.lib  

class CInitSock
{
public:
    CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
    {
        // 初始化WS2_32.dll  
        WSADATA wsaData;
        WORD sockVersion = MAKEWORD(minorVer, majorVer);
        if (::WSAStartup(sockVersion, &wsaData) != 0)
        {
            exit(0);
        }
    }
    ~CInitSock()
    {
        ::WSACleanup();
    }
};


//////////////////////////////////////////////////////  
// select.cpp文件  


#include "../common/initsock.h"  
#include <stdio.h>  

CInitSock theSock;      // 初始化Winsock库  
int main()
{
    USHORT nPort = 4567;    // 此服务器监听的端口号  

    // 创建监听套节字  
    SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(nPort);
    sin.sin_addr.S_un.S_addr = INADDR_ANY;
    // 绑定套节字到本地机器  
    if (::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
    {
        printf(" Failed bind() \n");
        return -1;
    }
    // 进入监听模式  
    ::listen(sListen, 5);

    // select模型处理过程  
    // 1)初始化一个套节字集合fdSocket,添加监听套节字句柄到这个集合  
    fd_set fdSocket;        // 所有可用套节字集合  
    FD_ZERO(&fdSocket);
    FD_SET(sListen, &fdSocket);
    while (TRUE)
    {
        // 2)将fdSocket集合的一个拷贝fdRead传递给select函数,  
        // 当有事件发生时,select函数移除fdRead集合中没有未决I/O操作的套节字句柄,然后返回。  
        fd_set fdRead = fdSocket;
        int nRet = ::select(0, &fdRead, NULL, NULL, NULL);
        if (nRet > 0)
        {
            // 3)通过将原来fdSocket集合与select处理过的fdRead集合比较,  
            // 确定都有哪些套节字有未决I/O,并进一步处理这些I/O。  
            for (int i = 0; i < (int)fdSocket.fd_count; i++)
            {
                if (FD_ISSET(fdSocket.fd_array[i], &fdRead))
                {
                    if (fdSocket.fd_array[i] == sListen)     // (1)监听套节字接收到新连接  
                    {
                        if (fdSocket.fd_count < FD_SETSIZE)
                        {
                            sockaddr_in addrRemote;
                            int nAddrLen = sizeof(addrRemote);
                            SOCKET sNew = ::accept(sListen, (SOCKADDR*)&addrRemote, &nAddrLen);
                            FD_SET(sNew, &fdSocket);
                            printf("接收到连接(%s)\n", ::inet_ntoa(addrRemote.sin_addr));
                        }
                        else
                        {
                            printf(" Too much connections! \n");
                            continue;
                        }
                    }
                    else
                    {
                        char szText[256];
                        int nRecv = ::recv(fdSocket.fd_array[i], szText, strlen(szText), 0);
                        if (nRecv > 0)                        // (2)可读  
                        {
                            szText[nRecv] = '\0';
                            printf("接收到数据:%s \n", szText);
                        }
                        else                                // (3)连接关闭、重启或者中断  
                        {
                            ::closesocket(fdSocket.fd_array[i]);
                            FD_CLR(fdSocket.fd_array[i], &fdSocket);
                        }
                    }
                }
            }
        }
        else
        {
            printf(" Failed select() \n");
            break;
        }
    }
    return 0;
}

大体流程: 

//创建服务器套接字 
socket 
//绑定本地地址 
bind 
//进入监听模式 
listen 
//select模式 
//构造fd_set集合 
fd_set fdSocket;    
//清空fd_set集合并将服务器套接字加入 
FD_ZERO(&fdSocket);   
FD_SET(sListen, &fdSocket);   
//循环 
//复制一份fd_set集合并放入select中检测 
fd_set fdRead = fdSocket;   
int nRet = ::select(0, &fdRead, NULL, NULL, NULL);   
//根据select结果进行相应处理(accept read ) 


总结:通过select模式,实现了监听socket的accept和客户端的read之间,以及各个客户端之间的read,可以不用一直阻塞在那,而是在有相应事件的时候再进行阻塞处理,把accept和read两个长阻塞转化为select一个长阻塞。

     使用select的好处是程序能够在单个线程内同时处理多个套接字连接,这避免了阻塞模式下的线程膨胀问题。但是,添加到fd_set结构的套接字数量是有限制的,默认情况下,最大值是FD_SETSIZE,它在winsock2.h文件中定义为64。为了增加套接字数量,应用程序可以将FD_SETSIZE定义为更大的值(这个定义必须在包含winsock2.h之前出现)。不过,自定义的值也不能超过Winsock下层提供者的限制(通常是1024)。
    另外,FD_SETSIZE的值太大的话,服务器性能就会受到影响。例如有1000个套接字,那么在调用select之前就不得不设置这1000个套接字,select返回之后,又必须检查这1000个套接字。

本文转载自:http://www.cnblogs.com/ranjiewen/p/5618620.html

上一篇: .hpp文件
r
粉丝 1
博文 203
码字总数 28
作品 0
武汉
程序员
私信 提问
Nginx的Windows移植版本--Ngwsx

Ngwsx是Nginx的一个非官方的Windows移植版本,使用Windows的IOCP,支持高并发。 特性: ) 支持IOCP和Select两种IO模型。 ) 支持以Windows服务的方式运行。 ) 支持单进程和主从进程(主进程/...

无名码农
2011/07/08
4K
0
为何Boost的asio要使用proactor模式实现?

Linux下高性能的网络库中大多使用的Reactor 模式去实现,Boost Asio在Linux下用epoll和select去模拟proactor模式,影响了它的效率和实现复杂度, 看陈硕的自己的Linux下Reactor网络库和ASIO的性...

天下杰论
2014/12/08
286
0
Ngwsx-1.0.5.0 (Nginx for windows)

Ngwsx是Nginx的一个非官方的Windows移植版本,使用Windows的IOCP,支持高并发。 特性: *) 支持IOCP和Select两种IO模型。 *) 支持以Windows服务的方式运行。 *) 支持单进程和主从进程(主进程...

无名码农
2011/08/01
2.7K
2
Ngwsx-1.0.5.0 (Nginx for windows)

Ngwsx是Nginx的一个非官方的Windows移植版本,使用Windows的IOCP,支持高并发。 特性: *) 支持IOCP和Select两种IO模型。 *) 支持以Windows服务的方式运行。 *) 支持单进程和主从进程(主进程...

无名码农
2011/08/01
694
0
Ngwsx-1.0.4.0 (Nginx for windows)

http://blog.csdn.net/ngwsx Ngwsx是Nginx的一个非官方的Windows移植版本,使用Windows的IOCP,支持高并发。 特性: *) 支持IOCP和Select两种IO模型。 *) 支持以Windows服务的方式运行。 *)...

无名码农
2011/07/08
682
2

没有更多内容

加载失败,请刷新页面

加载更多

JMM内存模型(一)&volatile关键字的可见性

在说这个之前,我想先说一下计算机的内存模型: CPU在执行的时候,肯定要有数据,而数据在内存中放着呢,这里的内存就是计算机的物理内存,刚开始还好,但是随着技术的发展,CPU处理的速度越...

走向人生巅峰的大路
30分钟前
78
0
你对AJAX认知有多少(2)?

接着昨日内容,我们几天继续探讨ajax的相关知识点 提到ajax下面几个问题又是必须要了解的啦~~~ 8、在浏览器端如何得到服务器端响应的XML数据。 通过XMLHttpRequest对象的responseXMl属性 9、 ...

理性思考
39分钟前
4
0
正则表达式基础(一)

1.转义 转义的作用: 当某个字符在表达式中具有特殊含义,例如字符串引号中出现了引号,为了可以使用这些字符本身,而不是使用其在表达式中的特殊含义,则需要通过转义符“\”来构建该字符转...

清自以敬
42分钟前
4
0
idea中@Data标签getset不起作用

背景:换电脑以后在idea中有@data注解都不生效 解决办法:idea装个插件 https://blog.csdn.net/seapeak007/article/details/72911529...

栾小糖
48分钟前
5
0
Apache Kudu 不能删除不存在的数据

使用Apache Kudu客户端,对KafkaConnect Sink 进行扩展。 使用的Apache Kudu 的Java 客户端。突然有天发现作业无法提交,一直报错。 后来才发现这是Kudu自身的一种校验机制。为了忽略这种校验...

吐槽的达达仔
58分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部