一.简单的Select模型介绍

1.1 在介绍select模型前,先讲讲如何实现非阻塞的socket,因为socket种recv接受数据时处于阻塞模式,当客户端没有断开链接的时候,

recv会一直阻塞,直到有数据接受,要解决这个问题,我们首先想到的时利用多线程为每一个客户端开一个线程用来接受数据,处理数据

和发送数据,如过客户端有成千上百个那么我们就需要开成千上百的线程,这显然不合适,那么如何来减少线程的数量,在这里我们可以

使用select模型

 

1.2 select模型的主要思想:用一个集合保存客户端链接的socket套接字,然后每过一段时间就循环检查集合中套接字是否可读,可写,

如果可读,我们就读取套接字中的数据。

 

1.3使用select模型的一些方法介绍

fd_set :用来存取被检查套接字的结构体:

typedef struct fd_set {
        u_int fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

 

FD_ZERO(fd_set*):初始化fd_set集合的一个宏定义函数:

#define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)

 

FD_SET(SOCKET,fd_se*);将套接字放入fd_set集合中

define FD_SET(fd, set) do { \
    u_int __i; \
    for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \
        if (((fd_set FAR *)(set))->fd_array[__i] == (fd)) { \
            break; \
        } \
    } \
    if (__i == ((fd_set FAR *)(set))->fd_count) { \
        if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) { \
            ((fd_set FAR *)(set))->fd_array[__i] = (fd); \
            ((fd_set FAR *)(set))->fd_count++; \
        } \
    } \
} while(0, 0)

 

FD_ISSET(SOCKET,fd_set*); 检查s套接字是否还在集合中:还在说明此socket满足要求否则不满足要求

#define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set))

 

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

用来检查套接字的读写性如果不可读就出队

第一个参数会被系统忽略  readfds 需要检查可读性的集合,writefds 需要检查可写性的集合,exceptfds需要检查 异常的集合,timeout select函数的等待的时间,

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

 

用来指定时间的结构体

struct timeval {
        long    tv_sec;         /* seconds */
        long    tv_usec;        /* and microseconds */
};


二实现select模型例子:

#include<WinSock2.h>
#include<iostream>
#include<Windows.h>
#include<vector>
#include<algorithm>
using namespace std;
#define PostN   5000
#pragma comment(lib, "Ws2_32.lib")

//用来存储客户端socket
vector<SOCKET> c_vSocks; 

//处理客户端的链接
DWORD WINAPI CThreadPro(LPARAM lparam)
{
    //用来检查socket的集合
    fd_set r_Set;
    fd_set w_Set;
    fd_set e_Set;

    //保存将要被检查的socket
    SOCKET socks[FD_SETSIZE]={0};
    timeval time={1,0};
    int local=0;

    while(true)
    {
        //初始化socket集合
         FD_ZERO(&r_Set);
         FD_ZERO(&w_Set);
         FD_ZERO(&e_Set);
         if(c_vSocks.empty()!=true)
        {
            //获取c_vSocks中已经扫描过的下一个位置
            local=local%c_vSocks.size();
            
            //如果还有大于FD_SETSIZE个没有扫描
            //将FD_SETSIZE个socket填入socks[FD_SETSIZE]中
            //否则将剩下的填入socks[FD_SETSIZE]中
        {
            if((c_vSocks.size()-local)>FD_SETSIZE)
            {
                //获取将要开始扫描的第一个位置
                vector<SOCKET>::iterator it=c_vSocks.begin()+local;
                for(int i=0;i<FD_SETSIZE;i++)
                {
                    socks[i]=*it;
                    it++;
                }
                local+=FD_SETSIZE; 
            }
            else
            {
                vector<SOCKET>::iterator it=c_vSocks.begin()+local;
                for(int i=0;it!=c_vSocks.end();it++)
                {
                    socks[i]=*it;
                    i++;
                    local++;
                }
            
            }
        }
        
            int ret=0;
            //将将要被扫描的填入r_Set队列中
            for each (SOCKET s in socks)
            {
                if(s==0)
                {
                 break;
                }
                FD_SET(s,&r_Set);
            }
            cout<<"开始select"<<endl;

            //将不可读的socket出队
            ret=select(0,&r_Set,NULL,NULL,&time);

              //cout<<ret<<endl;

            char buff[1024];

            //从socks[FD_SETSIZE]中筛选出已经可读的socket
            for(int i=0;i<local;i++)
            {
                memset(buff,0,sizeof(buff));
                //检查socket是否可读,如果可读->读取数据
                if(FD_ISSET(socks[i],&r_Set))
                {
                   int ret=recv(socks[i],buff,sizeof(buff),0);
                   if(ret==0)
                   {
                       closesocket(socks[i]);
                       cout<<"Client Close socket-"<<socks[i]<<endl;
                       vector<SOCKET>::iterator iter=find(c_vSocks.begin(),c_vSocks.end(),socks[i]);
                       if(iter!=c_vSocks.end())
                       {
                       c_vSocks.erase(iter);
                       local--;
                       socks[i]=0;
                       }
                       
                   }
                   if(ret==SOCKET_ERROR)
                   {
                       closesocket(socks[i]);
                       cout<<" socket error close -"<<socks[i]<<endl;
                       vector<SOCKET>::iterator iter=find(c_vSocks.begin(),c_vSocks.end(),socks[i]);
                       if(iter!=c_vSocks.end())
                       {
                       c_vSocks.erase(iter);
                       local--;
                       socks[i]=0;
                       }
                   }
                   cout<<"接受-"<<buff<<endl;
                }
            }

        }

    }

}
//主函数
int main()
{
    //服务端套接字
    SOCKET sockServer;
    WSADATA wsa;
    int ret=0;
    if(WSAStartup(MAKEWORD(2,2),&wsa)!=0)
    {
        cout<<"WSAStartup failed"<<endl;
        return 0;
    }
     sockServer=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
     SOCKADDR_IN addrServer;

     //初始化服务端的网络信息
     memset(&addrServer,0,sizeof(addrServer));
     addrServer.sin_addr.S_un.S_addr=ADDR_ANY;
     addrServer.sin_family=AF_INET;
     addrServer.sin_port=htons(PostN);

     //将信息绑定到套接字上
     ret=bind(sockServer,(sockaddr*)&addrServer,sizeof(addrServer));
     if(ret==SOCKET_ERROR)
     {
         cout<<"bind failed "<<endl;
     }

     //监听信息
     ret=listen(sockServer,10);
     if(ret==SOCKET_ERROR)
     {
         cout<<"listen failed "<<endl;
     }
     HANDLE hThread;
     hThread=CreateThread(0,0,(LPTHREAD_START_ROUTINE)CThreadPro,0,0,0);
     //用来保存客户端的链接信息
     SOCKADDR_IN addrClient;
     int addrlen=sizeof(addrClient);
     while (true)
     {
         cout<<"accept..."<<endl;
         c_vSocks.push_back(accept(sockServer,(sockaddr*)&addrClient,&addrlen));
     }
     CloseHandle(hThread);

     if(!c_vSocks.empty())
     {
         for ( unsigned i = 0; i <c_vSocks.size() ; i++)
         {
             closesocket(c_vSocks[i]);
         }
     }
     c_vSocks.clear();
     WSACleanup();
     return 0;
}