【网络】IO多路复用模型select

目录

一、基本概念

二、工作原理

三、select函数

四、select Qt示例

五、select模型的优缺点

六、应用场景

七、总结


IO多路复用模型中的Select是一种常用的同步IO模型,它允许单个线程监视多个文件句柄(在网络编程中,这些文件句柄通常是socket),并在一个或多个句柄就绪时进行相应的读写操作。以下是关于Select模型的详细解析:

一、基本概念

  • IO多路复用:一种同步IO模型,通过单个线程监视多个文件句柄(如socket),当某个句柄就绪时,进行读写操作。它减少了系统开销,避免了为每个连接创建独立线程的需要。
  • Select模型:IO多路复用的一种实现方式,通过select系统调用实现。

二、工作原理

  1. 初始化:创建一个文件描述符集合,用于存放需要监视的文件描述符(socket)。
  2. 监视:调用select函数,将文件描述符集合传递给内核,让内核监视这些文件描述符的状态变化(如可读、可写、异常)。
  3. 阻塞等待:select函数会阻塞当前线程,直到一个或多个文件描述符就绪,或者超时发生。
  4. 处理事件:当select函数返回时,检查文件描述符集合,找出哪些文件描述符就绪,并进行相应的读写操作。

三、select函数

select函数的原型如下:

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

 

  • 参数说明
    • nfds:待测试的文件描述符集合中最大文件描述符加1(因为文件描述符是从0开始的)。
    • readfds:指向可读文件描述符集合的指针。
    • writefds:指向可写文件描述符集合的指针。
    • exceptfds:指向异常条件文件描述符集合的指针。
    • timeout:指定等待的最长时间,如果设置为NULL,则永久等待。
  • 返回值:返回就绪的文件描述符数量,如果超时则返回0,如果出错则返回-1。

四、select Qt示例

tcpserver.h

#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <list>
#include <map>
// //sock--包大小,缓冲区,偏移量
struct stru_pack{
    int   m_nPackSize;
    char *m_pszbuf;
    int   m_noffset;
};
class TCPServer
{
public:
    TCPServer();
    ~TCPServer();
public:
    bool initNetWork(const char* szip ="127.0.0.1",short nport = 1234);
    void unInitNetWork(const char *szerr = "");
    bool sendData(SOCKET sock,const char* szbuf,int nlen);
    void recvData();
public:
    static DWORD WINAPI ThreadAccept(LPVOID lpvoid);
    static DWORD WINAPI ThreadRecv(LPVOID lpvoid);
private:
    SOCKET m_socklisten;
    std::list<HANDLE> m_lstThread;
    bool  m_bFlagQuit;
public:

    std::list<SOCKET> m_lstSocket;
    fd_set m_fdsets;
    std::map<SOCKET,stru_pack*> m_mapSocketToPack;
};

#endif // TCPSERVER_H

tcpserver.cpp

 

#include "tcpserver.h"

TCPServer::TCPServer()
{
    m_socklisten = 0;
    m_bFlagQuit = true;
    FD_ZERO(&m_fdsets);
}

TCPServer::~TCPServer()
{

}

bool TCPServer::initNetWork(const char *szip, short nport)
{
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
    wVersionRequested = MAKEWORD(2, 2);

    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) {
        /* Tell the user that we could not find a usable */
        /* Winsock DLL.                                  */
        printf("WSAStartup failed with error: %d\n", err);
        return false;
    }

    /* Confirm that the WinSock DLL supports 2.2.*/
    /* Note that if the DLL supports versions greater    */
    /* than 2.2 in addition to 2.2, it will still return */
    /* 2.2 in wVersion since that is the version we      */
    /* requested.                                        */

    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        /* Tell the user that we could not find a usable */
        /* WinSock DLL.                                  */
        unInitNetWork("Could not find a usable version of Winsock.dll\n");
        return false;
    }
    else
        printf("The Winsock 2.2 dll was found okay\n");
    m_socklisten =  socket(AF_INET,SOCK_STREAM,0);
    if(INVALID_SOCKET == m_socklisten){
        unInitNetWork("socket err\n");
        return false;
    }
    sockaddr_in addrserver;
    addrserver.sin_family = AF_INET;
    addrserver.sin_port = htons(nport);
    addrserver.sin_addr.s_addr = 0;
    if(SOCKET_ERROR == bind(m_socklisten,(const struct sockaddr*)&addrserver,sizeof(addrserver))){
        unInitNetWork("bind err\n");
        return false;
    }
    if(SOCKET_ERROR == listen(m_socklisten,10)){
       unInitNetWork("listen err\n");
        return false;
    }
   //创建线程
    HANDLE hThread = CreateThread(0,0,&ThreadAccept,this,0,0);
    if(hThread)
        m_lstThread.push_back(hThread);
    //单线程轮询接收所有的客户端
    hThread = CreateThread(0,0,&ThreadRecv,this,0,0);
    if(hThread){
       m_lstThread.push_back(hThread);

    }
    return true;
}

DWORD TCPServer::ThreadAccept(LPVOID lpvoid)
{
    TCPServer *pthis = (TCPServer*)lpvoid;
    sockaddr_in addrclient;
    int nsize = sizeof(addrclient);

    u_long iMode = 1;
    while(pthis->m_bFlagQuit){
        SOCKET sockWaiter =  accept(pthis->m_socklisten,(struct sockaddr*)&addrclient,&nsize);
        printf("client ip:%s nport:%d\n",inet_ntoa(addrclient.sin_addr),addrclient.sin_port);

        //加入到集合内
        FD_SET(sockWaiter,&pthis->m_fdsets);
        //将sockWaiter设置为非阻塞
        //ioctlsocket(sockWaiter, FIONBIO, &iMode);
        //将sockWaiter加入链表中
       // pthis->m_lstSocket.push_back(sockWaiter);
    }
    return 0;
}

DWORD TCPServer::ThreadRecv(LPVOID lpvoid)
{
    TCPServer *pthis = (TCPServer*)lpvoid;

    pthis->recvData();
    return 0;
}
void TCPServer::recvData()
{
    int nPackSize;
    int nRecvNum;
    char *pszbuf = NULL;
    SOCKET sockWaiter;
    fd_set fdtemp;
    TIMEVAL  tv;
    tv.tv_sec =0;
    tv.tv_usec = 100;
    while(m_bFlagQuit){
         //接收包大小
        fdtemp = m_fdsets;
        //将集合交给select查看
        select(0,&fdtemp,0,0,&tv);
        //校验是否发生网络事件


        for(int i =0; i < m_fdsets.fd_count;i++){
            if(FD_ISSET(m_fdsets.fd_array[i],&fdtemp)){
                sockWaiter = m_fdsets.fd_array[i];
                //sock--包大小,缓冲区,偏移量
                stru_pack *p =  m_mapSocketToPack[sockWaiter];
                if(!p){
                    //第一次接收 --接收包大小
                     nRecvNum = recv(sockWaiter,(char*)&nPackSize,sizeof(int),0);
                    if(nRecvNum<=0){
                        //客户端下载
                        if(GetLastError() == 10054){
                            closesocket(sockWaiter);
                            //将sockwaiter 从fd_set集合移除
                            FD_CLR(sockWaiter,&m_fdsets);
                        }
                        continue;
                    }
                    p = new stru_pack;
                    p->m_nPackSize = nPackSize;
                    p->m_pszbuf = new char[nPackSize];
                    p->m_noffset = 0;
                    m_mapSocketToPack[sockWaiter] = p;

                }else{
                    //第n次接收的包内容
                    nRecvNum = recv(sockWaiter,p->m_pszbuf+p->m_noffset,p->m_nPackSize,0);
                    p->m_noffset += nRecvNum;
                    p->m_nPackSize-=nRecvNum;
                    if(p->m_nPackSize ==0){
                         printf("%s\n",pszbuf);//处理数据
                        delete p;
                         m_mapSocketToPack[sockWaiter] = NULL;
                    }
                }





            }
        }
       // Sleep(100);
    }
}


void TCPServer::unInitNetWork(const char *szerr )
{
    m_bFlagQuit = false;
    for (auto& ite:m_lstThread) {

        if(WAIT_TIMEOUT == WaitForSingleObject(ite,100))
            TerminateThread(ite,-1);

        if(ite){
            CloseHandle(ite);
            ite = NULL;
        }
    }
    m_lstThread.clear();
    printf(szerr);
    if(m_socklisten){
       closesocket(m_socklisten);
        m_socklisten = 0;
    }
    WSACleanup();
}

bool TCPServer::sendData(SOCKET sock, const char *szbuf, int nlen)
{
    if(!szbuf || nlen <=0)
        return false;
   //粘包
    //发送包大小
    if(send(sock,(char*)&nlen,sizeof(int),0)<=0)
        return false;
    //发送包内容
    if(send(sock,szbuf,nlen,0)<=0)
        return false;
    return true;
}



main.cpp

#include <QCoreApplication>
#include "network/tcpserver.h"
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    TCPServer ts;
    if(ts.initNetWork())
        printf("server is running......\n");
    else
        printf("server err\n");
    return a.exec();
}

五、select模型的优缺点

优点

  1. 单线程处理多个连接:减少了线程创建和切换的开销。
  2. 跨平台:几乎支持所有平台。

缺点

  1. 文件描述符限制:在32位系统上,单个select调用能够监视的文件描述符数量有限(通常是1024个),虽然可以通过修改宏定义来扩展,但扩展后性能会下降。
  2. 性能问题:每次调用select都需要遍历整个文件描述符集合,随着文件描述符数量的增加,性能会下降。
  3. 数据拷贝:在select返回后,需要将就绪的文件描述符从内核空间拷贝到用户空间,增加了开销。

六、应用场景

Select模型适用于连接数不是特别多(不超过文件描述符限制)的场景。对于需要处理大量连接的应用,可能需要考虑使用poll或epoll等更高效的IO多路复用模型。

七、总结

Select是IO多路复用模型的一种实现方式,它通过单个线程监视多个文件描述符的状态变化,并在就绪时进行相应的读写操作。虽然存在文件描述符限制和性能问题,但在连接数不是特别多的情况下,它仍然是一种有效的解决方案。

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱编程的小猴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值