• libevent-select模型分析


    下面内容为windows下select模型分析,原博客链接

    http://blog.csdn.net/fish_55_66/article/details/50352080

    https://www.cnblogs.com/Mr-Zhong/p/4160988.html

    Select模型的原理和使用步骤

      select(选择)模型是Winsock中最常见的I/O模型。之所以称其为“ select模型”,是由于它的“中心思想”

    便是利用select函数,实现对 I/O的管理!利用select函数,我们判断套接字上是否存在数据,或者能否向一

    个套接字写入数据。之所以要设计这个函数,唯一的目的便是防止应用程序在套接字处于锁定模式中时,在

    一次I/O绑定调用(如send或recv)过程中,被迫进入“锁定”状态;同时防止在套接字处于非锁定模式中时,

    产生WSAEWOULDBLOCK错误。除非满足事先用参数规定的条件,否则select函数会在进行I/O操作时锁定。

    select的函数原型如下:

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

      其中,第一个参数nfds会被忽略。之所以仍然要提供这个参数,只是为了保持与早期的Berkeley套接字应用程

    序的兼容。大家可注意到三个 fd_set参数:一个用于检查可读性(readfds),一个用于检查可写性(writefds),

    另一个用于例外数据( excepfds)。从根本上说,fdset数据类型代表着一系列特定套接字的集合。其中,

    readfds集合包括符合下述任何一个条件的套接字:

      ■ 有数据可以读入。
      ■ 连接已经关闭、重设或中止。
      ■ 假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。
    writefds集合包括符合下述任何一个条件的套接字:
      《windows网络编程技术》第八章内容:
      只有在三种条件下,才会发出F D _ W R I T E通知:
      ■ 使用c o n n e c t或W S A C o n n e c t,一个套接字首次建立了连接。
      ■ 使用a c c e p t或W S A A c c e p t,套接字被接受以后。
      ■ 若s e n d、W S A S e n d、s e n d t o或W S A S e n d To操作失败,返回了W S A E W O U L D B L O C K错
      误,而且缓冲区的空间变得可用
      因此,作为一个应用程序,自收到首条F D _ W R I T E消息开始,便应认为自己必然能在一个套接字上发出数据,直至一个s e n d、W S A S e n d、s e n d t o或      W S A S e n d To返回套接字错误
    W S A E W O U L D B L O C K。经过了这样的失败以后,要再用另一条F D _ W R I T E通知应用程序再次发送数据。
    最后,exceptfds集合包括符合下述任何一个条件的套接字:
      ■ 假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。
      ■ 有带外(out-of-band,OOB)数据可供读取。

      例如,假定我们想测试一个套接字是否“可读”,必须将自己的套接字增添到readfds集合,再等待select函数

    完成。select完成之后,必须判断自己的套接字是否仍为readfds集合的一部分。若答案是肯定的,便表明该套

    接字“可读”,可立即着手从它上面读取数据。在三个参数中(readfds、writedfss和exceptfds),任何两个都

    可以是空值(NULL);但是,至少有一个不能为空值!在任何不为空的集合中,必须包含至少一个套接字句柄;

    否则, select函数便没有任何东西可以等待。最后一个参数timeout对应的是一个指针,它指向一个timeval结构,

    用于决定select最多等待 I / O操作完成多久的时间。如 timeout是一个空指针,那么select调用会无限期地“锁定”

    或停顿下去,直到至少有一个描述符符合指定的条件后结束。对timeval结构的定义如下:

    struct timeval {
    long tv_sec;
    long tv_usec;

    } ;

      若将超时值设置为(0,0),表明select会立即返回,允许应用程序对 select操作进行“轮询”。出于对性能方面

    的考虑,应避免这样的设置。select成功完成后,会在 fd_set结构中,返回刚好有未完成的I/O操作的所有套接字

    句柄的总量。若超过timeval设定的时间,便会返回0。不管由于什么原因,假如select调用失败,都会返回SOCKET_ERROR。

    用select对套接字进行监视之前,在自己的应用程序中,必须将套接字句柄分配给一个集合,设置好一个或全部

    读、写以及例外 fd_set结构。将一个套接字分配给任何一个集合后,再来调用select,便可知道一个套接字上是

    否正在发生上述的I/O活动。Winsock提供了下列宏操作,可用来针对I/O活动,对 fd_set进行处理与检查:

      ■ FD_CLR(s, *set):从s e t中删除套接字 s。
      ■ FD_ISSET(s, *set):注意这里并不是检测s有没有调用FD_SET加入set集合,而是检测set集合中哪一个句柄也就是fd是就绪的,因为set中可能有很多fd
      ■ FD_SET(s, *set):将套接字 s加入集合 s e t。
      ■ F D _ Z E R O ( * s e t ):将s e t初始化成空集合。

      例如,假定我们想知道是否可从一个套接字中安全地读取数据,同时不会陷于无休止的“锁定”状态,便可使用

    FD_SET宏,将自己的套接字分配给fd_set集合,再来调用select。要想检测自己的套接字是否仍属 fd_read集合

    的一部分,可使用FD_ISSET宏。采用下述步骤,便可完成用select操作一个或多个套接字句柄的全过程:

    1) 使用FD_ZERO宏,初始化自己感兴趣的每一个fd_set。
    2) 使用FD_SET宏,将套接字句柄分配给自己感兴趣的每个fd_set。
    3) 调用select函数,然后等待在指定的fd_set集合中,I/O活动设置好一个或多个套接字句柄。
    select完成后,会返回在所有fd_set集合中设置的套接字句柄总数,并对每个集合进行相应的更新。
    4) 根据select的返回值,我们的应用程序便可判断出哪些套接字是就绪的,并对其进行下一步操作
    的I/O操作—具体的方法是使用FD_ISSET宏,对每个fd_set集合进行检查。

    下面一个例子

    服务端

    #include <WS2tcpip.h>  
    #include <WinSock2.H>  
    #include <iostream>  
      
    #pragma comment(lib, "ws2_32.lib")   
      
    int main()  
    {  
        /// 初始化socket  
        WSADATA wsaData;  
        WORD version = MAKEWORD(2,2);  
        int result = 0;  
        result = WSAStartup(version, &wsaData);  
        if (result != 0)  
        {  
            std::cout << "WSAStartup() error." << std::endl;  
            return -1;  
        }  
      
        /// 创建socket   
        SOCKET socketListen;  
        socketListen = socket(AF_INET, SOCK_STREAM, 0);  
        if (socketListen == INVALID_SOCKET)  
        {  
            WSACleanup();  
            std::cout << "socket() error." << std::endl;  
            return -1;  
        }  
      
        /// 服务器地址结构   
        sockaddr_in svrAddress;  
        svrAddress.sin_family = AF_INET;  
        svrAddress.sin_addr.s_addr = INADDR_ANY;  
        svrAddress.sin_port = htons(8000);  
      
        /// 绑定服务器套接字   
        result = bind(socketListen, (sockaddr*)&svrAddress, sizeof(svrAddress));  
        if (result == SOCKET_ERROR)  
        {  
            closesocket(socketListen);  
            WSACleanup();  
            std::cout << "bind() error." << std::endl;  
            return -1;  
        }  
      
        /// 开启监听  
        result = listen(socketListen, 5);  
        if (result == SOCKET_ERROR)  
        {  
            closesocket(socketListen);  
            WSACleanup();  
            std::cout << "listen() error." << std::endl;  
            return -1;  
        }  
        std::cout << "服务器启动成功,监听端口:" << ntohs(svrAddress.sin_port) << std::endl;  
      
        /// select模型   
        fd_set allSockSet;   
        FD_ZERO(&allSockSet);   
      
        FD_SET(socketListen, &allSockSet); // 将socketListen加入套接字集合中   
      
        while (true)  
        {  
            fd_set readSet;  
            FD_ZERO(&readSet);   
            readSet = allSockSet;   
              
            result = select(0, &readSet, NULL, NULL, NULL);  
            if (result == SOCKET_ERROR)  
            {  
                std::cout << "listen() error." << std::endl;  
                break;  
            }  
      
            if (FD_ISSET(socketListen, &readSet))  
            {  
                sockaddr_in clientAddr;  
                int len = sizeof(clientAddr);  
      
                SOCKET clientSocket = accept(socketListen, (sockaddr*)&clientAddr, &len);         
                if (clientSocket == INVALID_SOCKET)  
                {  
                    std::cout << "accept() error." << std::endl;  
                    break;  
                }  
                FD_SET(clientSocket, &allSockSet);   /// 将新创建的套接字加入到集合中   
      
                char ipAddress[16] = { 0 };  
                inet_ntop(AF_INET, &clientAddr, ipAddress, 16);  
                std::cout << "有新的连接[" << ipAddress << ":" << ntohs(clientAddr.sin_port)  
                    << "], 目前客户端的数量为:" << allSockSet.fd_count - 1 << std::endl;  
      
                continue;  
            }  
      
            for (u_int i = 0; i < allSockSet.fd_count; ++i)  
            {  
                SOCKET socket = allSockSet.fd_array[i];  
      
                sockaddr_in clientAddr;  
                int len = sizeof(clientAddr);  
                getpeername(socket, (struct sockaddr *)&clientAddr, &len);  
                char ipAddress[16] = { 0 };  
                inet_ntop(AF_INET, &clientAddr, ipAddress, 16);  
      
                /// 可读性监视,可读性指有连接到来、有数据到来、连接已关闭、重置或终止  
                if (FD_ISSET(socket, &readSet))  
                {  
                    char bufRecv[100];  
                    result = recv(socket, bufRecv, 100, 0);  
                    if (result == SOCKET_ERROR)  
                    {  
                        DWORD err = WSAGetLastError();  
                        if (err == WSAECONNRESET)       /// 客户端的socket没有被正常关闭,即没有调用closesocket  
                        {  
                            std::cout << "客户端[" << ipAddress << ":" << ntohs(clientAddr.sin_port) << "]被强行关闭, ";  
                        }  
                        else  
                        {  
                            std::cout << "recv() error," << std::endl;  
                        }  
              
                        closesocket(socket);  
                        FD_CLR(socket, &allSockSet);  
      
                        std::cout << "目前客户端的数量为:" << allSockSet.fd_count - 1 << std::endl;  
                        break;  
                    }  
                    else if (result == 0)               /// 客户端的socket调用closesocket正常关闭  
                    {  
                        closesocket(socket);  
                        FD_CLR(socket, &allSockSet);  
      
                        std::cout << "客户端[" << ipAddress << ":" << ntohs(clientAddr.sin_port)   
                            << "]已经退出,目前客户端的数量为:" << allSockSet.fd_count - 1 << std::endl;  
                        break;  
                    }  
      
                    bufRecv[result] = '';  
                    std::cout << "来自客户端[" << ipAddress << ":" << ntohs(clientAddr.sin_port)  
                        << "]的消息:" << bufRecv << std::endl;  
                }  
            }  
        }  
      
        for (u_int i = 0; i < allSockSet.fd_count; ++i)  
        {  
            SOCKET socket = allSockSet.fd_array[i];  
            closesocket(socket);  
        }  
      
        WSACleanup();  
        return 0;  
    }  

    客户端

    #include <iostream>  
    #include <WS2tcpip.h>  
    #include <WinSock2.H>  
      
    #pragma comment(lib, "ws2_32.lib")  
      
    #define SERVER_ADDRESS      "127.0.0.1"  
    #define SERVER_PORT         8000  
      
    #define SOCKET_NUM          1       /// 客户端socket的个数,修改该值可以改变连接到服务器的客户端个数  
      
    int main()  
    {  
        WORD wVersionRequested = MAKEWORD(2, 2);  
        WSADATA wsaData;  
        int err = WSAStartup(wVersionRequested, &wsaData);  
        if (err != 0) return 1;  
      
        if (LOBYTE(wsaData.wVersion) != 2 ||  
            HIBYTE(wsaData.wVersion) != 2)   
        {  
            WSACleanup();  
            std::cout << "WSAStartup() error." << std::endl;  
            return -1;  
        }  
      
        SOCKET allSocketClients[SOCKET_NUM];  
        for (int i = 0; i < SOCKET_NUM; i ++)  
        {  
            SOCKET socketClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  
            if (socketClient == INVALID_SOCKET)  
            {  
                WSACleanup();  
                std::cout << "socket() error." << std::endl;  
                return -1;  
            }  
            allSocketClients[i] = socketClient;  
        }  
      
        SOCKADDR_IN server;  
        memset(&server, 0, sizeof(SOCKADDR_IN));  
        server.sin_family = AF_INET;  
        server.sin_port = htons(SERVER_PORT);  
        inet_pton(server.sin_family, SERVER_ADDRESS, &server.sin_addr);  
      
        for (int i = 0; i < SOCKET_NUM; i++)  
        {  
            SOCKET socketClient = allSocketClients[i];  
            err = connect(socketClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));  
            if (err == SOCKET_ERROR)  
            {  
                std::cout << "connect() error." << std::endl;  
                closesocket(socketClient);  
                WSACleanup();  
                return -1;  
            }  
      
            std::cout << "" << i + 1 << " 个客户端连接服务器成功。" << std::endl;  
        }  
      
        for (int i = 0; i < SOCKET_NUM; i++)  
        {  
            SOCKET socketClient = allSocketClients[i];  
            char message[100] = { 0 };  
            sprintf_s(message, "我是第 %d 个客户端 ", i + 1);  
            send(socketClient, message, strlen(message), 0);  
        }  
      
        /// 按 q 退出程序  
        do   
        {  
        } while (getchar() != 'q');  
      
        for (int i = 0; i < SOCKET_NUM; i++)  
        {  
            SOCKET socketClient = allSocketClients[i];  
            closesocket(socketClient);  
        }  
      
        WSACleanup();  
      
        return 0;  
    }  
  • 相关阅读:
    ASP.NET备份还原数据库
    ASP.NET的运行原理与运行机制
    Asp.net WebPages框架运行原理浅析(转)
    不要盲目选择定时器
    C# 如何用计时器Timer控件实现停留几秒再做切换窗体的操作
    使用System.Timers.Timer类实现程序定时执行
    C#启动一个外部程序(1)-WinExec
    几种类型的db,以及最新的db排名,看一下
    SharePoint 2013 讨论板列表"Connect to Outlook" 不可用解决方案
    Java可视化编程,基于布局管理器的UI设计
  • 原文地址:https://www.cnblogs.com/wangshaowei/p/8597996.html
Copyright © 2020-2023  润新知