IO多路复用模型Select

一、IO多路复用模型

        IO多路复用模型是一种同步IO模型,其原理在于使用一个线程完成在集合内的多个文件句柄的监视,若在集合中发现句柄,则进行读写操作,反之这一直处于阻塞状态,直至在集合中发现句柄。

二、Select函数

(1) FD_ZERO(fd_set* fdset): 将fd_set变量的所有位初始化为0。
(2) FD_SET(int fd, fd_set* fdset):在参数fd_set指向的变量中注册文件描述符fd的信息。           简单来说就是将socket(fd)加入集合(fdset)中
(3) FD_CLR(int fd, fd_set* fdset):参数fd_set指向的变量中清除文件描述符fd的信息
(4) FD_ISSET(int fd, fd_set* fdset):若参数fd_set指向的变量中包含文件描述符fd的信息,则返回真。

三、过程

        将需要查看的套接字加入一个集合内,将集合交给select在内核查看,一段时间后,校验谁还在集合内,在集合内的socket代表发生了网络事件。

请求IO响应机制
同步:请求IO操作之后,等待结果,当结果返回之后,在处理其他事情。
异步:请求IO操作之后,处理其他事情,如果IO操作发生,会发送通知。

代码流程

1.定义一个集合fd_set;

2.清空集合 FD_ZERO();

3.将需要查看的socket 加入集合内 FD_SET();

4.将集合交给select 查看;

5.校验集合中的socket  FD_ISSET();

四、Select优点

1.单线程处理多个套接字;

2.默认套接字数量限制64位,最多1024;

3.集合的底层数据结构是数组;

4.可跨平台、简单、方便;

5.阻塞采用同步状态;

五、服务端全部代码

1.tcpservice.h

#ifndef TCPSERVICE_H
#define TCPSERVICE_H
#include <iostream>
#include <winsock2.h>
#include <windows.h>
#include <list>
#include <map>

//socket对应的--包大小,缓冲区,偏移量
struct stru_pack{
    int m_nPackSize;
    char *m_pszbuf;
    int m_noffset;
};

using namespace std;

class TCPService
{
public:
    TCPService();
    ~TCPService();
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);
public:
    bool b_flagQuit;//标志
    SOCKET m_socklisten;//套接字
    std::list<HANDLE> m_listthread;//线程链表
    fd_set m_fd_set;//集合
    std::map<DWORD,stru_pack*> m_mapSocketToPack;//映射socket对应的结构体(stru_pack)
};

#endif // TCPSERVICE_H

2.tcpservice.cpp

#include "tcpservice.h"

TCPService::TCPService(){
    m_socklisten = 0;
    b_flagQuit = true;
    FD_ZERO(&m_fd_set);//清空集合
}
TCPService::~TCPService(){}

bool TCPService::initNetWork(const char *szip, short nport)
{
    //1
    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.                                  */
        printf("Could not find a usable version of Winsock.dll\n");
        WSACleanup();
        return false;
    }
    else
        printf("The Winsock 2.2 dll was found okay\n");
    //2
    m_socklisten = socket(AF_INET,SOCK_STREAM,0);
    if(INVALID_SOCKET == m_socklisten){
        unInitNetWork("sock erro\n");
        return false;
    }
    //3
    sockaddr_in service;
    service.sin_addr.S_un.S_addr = inet_addr(szip);
    service.sin_family = AF_INET;
    service.sin_port = htons(nport);

    if (bind(m_socklisten, (sockaddr *) &service, sizeof (service)) == SOCKET_ERROR) {
        unInitNetWork("bind erro\n");
        return false;
    }
    //4
    if (listen(m_socklisten, 10) == SOCKET_ERROR){
        unInitNetWork("listen erro\n");
        return false;
    }
    //1-4都为普通TCP服务端基本流程
    //创建线程
    HANDLE hThread = CreateThread(0,0,&ThreadAccept,this,0,0);
    if(hThread){
        m_listthread.push_back(hThread);
    }
    //利用单线程轮换接收所有客户端
    hThread = CreateThread(0,0,&ThreadRecv,this,0,0);
    if(hThread){
        m_listthread.push_back(hThread);
    }

    return true;
}

DWORD TCPService::ThreadAccept(LPVOID lpvoid)
{
    TCPService* pthis = (TCPService*)lpvoid;
    while(pthis->b_flagQuit){
        /****************接收客户端连接ip、nport信息*************/
        sockaddr_in addrclient;
        int nsize =sizeof(addrclient);
        SOCKET sockWaiter = accept(pthis->m_socklisten,(struct sockaddr*)&addrclient,&nsize);
        cout<<"cilent Ip: "<<inet_ntoa(addrclient.sin_addr)<<" client nport: "<<addrclient.sin_port<<endl;
        
        FD_SET(sockWaiter,&pthis->m_fd_set);//将sockWaiter加入集合中
    }
    return 0;
}

DWORD TCPService::ThreadRecv(LPVOID lpvoid)
{
    TCPService* pthis = (TCPService *)lpvoid;
    while(pthis->b_flagQuit){
        /********************接收客户端的数据******************/
        pthis->recvData();
    }
    return 0;
}

void TCPService::unInitNetWork(const char *szerr)
{
    b_flagQuit = false;
    for(auto &ite:m_listthread){
        if(WAIT_TIMEOUT == WaitForSingleObject(ite,100)){
            TerminateThread(ite,-1);
        }
        if(ite){
            CloseHandle(ite);
            ite =NULL;
        }
        m_listthread.clear();
    }
    printf(szerr);
    if(m_socklisten){
        closesocket(m_socklisten);
        m_socklisten = 0;
    }
    WSACleanup();
}

void TCPService::recvData()
{
    SOCKET sockWaiter;//此时的socket
    int nReadNum;//接收recv的返回值
    int PackSize;//包大小
    fd_set fdst;//集合
    TIMEVAL tv;//检测时间
    tv.tv_sec=0;
    tv.tv_usec=100;
    while(b_flagQuit){
        //接收包大小
        fdst = m_fd_set;
        //将集合交给select
        select(0,&fdst,0,0,&tv);
        //校验是否发生网络事件

        for(int i =0 ;i < m_fd_set.fd_count;i++){
            if(FD_ISSET(m_fd_set.fd_array[i],&fdst)){
                //用sockWaiter变量接一下此时集合中的socket
                sockWaiter = m_fd_set.fd_array[i];
                
                //创建一个空的结构体指针
                //映射socketwaiter对应的一个stru_pack的结构体,在后续接收中使用
                //m_mapSocketToPack[sockWaiter]创建键为socketWaiter的映射
                stru_pack *p = m_mapSocketToPack[sockWaiter];
                
                if(!p){
                    //第一次接收包大小
                    nReadNum = recv(sockWaiter,(char*)&PackSize,sizeof(int),0);
                    //判断读取错误
                    if(nReadNum <= 0){
                        if(GetLastError() == 10054){
                            closesocket(sockWaiter);
                            //移除socketwaiter
                            FD_CLR(sockWaiter,&m_fd_set);
                        }
                        continue;
                    }
                    //对p开辟空间,并赋值
                    p = new stru_pack;
                    p->m_nPackSize=PackSize;
                    p->m_noffset=0;
                    p->m_pszbuf = new char[PackSize];
                    m_mapSocketToPack[sockWaiter] = p;//对键为socketWaiter的值写入
                }
                else{
                //第2-n次接收包内容
                    //nReadNum 为每次接收的大小
                    nReadNum = recv(sockWaiter,p->m_pszbuf+p->m_noffset,p->m_nPackSize,0);
                    if(nReadNum > 0){
                        p->m_noffset+=nReadNum;//将偏移量的值改为累计接收的大小
                        p->m_nPackSize-=nReadNum;//m_nPackSize为此刻接收多少大小
                    }
                    if(p->m_nPackSize == 0){
                        //当包全部传输完成后输出其内容
                        printf("%s\n",p->m_pszbuf);
                        delete p;
                        m_mapSocketToPack[sockWaiter] = NULL;
                    }
                }
            }
        }
    }
}

3.main.cpp

测试时客户端需要先发包大小,再发包内容,该服务端才能接收到数据。

#include <QCoreApplication>
#include "network/tcpservice.h"//引入头文件
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    TCPService tp;
    if(tp.initNetWork()){
        cout<<"  server is running!!!  "<<endl;
    }
    else{
        cout<<"server err"<<endl;
    }
    return a.exec();
}

 

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值