天天看点

网络IO模型之select基础

思考:为什么线程开销会大

一、IO 有两种操作,同步 IO 和异步 IO 。 

    同步 IO 指的是,必须等待 IO 操作完成后,控制权

才返回给用户进程 。 

    异步 IO 指的是,无须等待 IO 操作完成,就将控制权返回给用户进程。

网络中的 IO ,由于不同的 IO 设备有着不同的特点,网络通信中往往需要等待 。 常见的

有以下 4 种情况 。

    (1)、输入操作

    (2)、输出操作

    (3)、服务器接收连接请求

    (4)、客户端发送连接请求

2、四种网络IO模型

    阻塞是指 IO 操作需要彻底完成后才返回到用户空间;

    而非阻塞是指 IO 操作被调用后立即返回给用户一个状态值,

不需要等到 IO 操作彻底完成

    (1)、阻塞IO模型

        只有系统调用获得结果或者超时出错才返回结果

    (2)、非阻塞IO模型

        从用户进程角度讲,它发起一个 read操作后,并不需要等待,而是马上就得到了

一个结果 。 当用户进程判断结果是一个错误时,它就知道数据还没有准备好,于是它可以

再次发送 read 操作,在非阻塞式 IO 中,用户进程其实需要不断地主动询问kernel 数据是

否准备好。非阻塞的接口相比于阻塞型接口的显著差异在于被调用之后立即返回 。 

使用如下的函数可以将某句柄归设为非阻塞状态 :fcntl( fd , F_SETFL , O_NONBLOCK );

    缺点;循环调用recv()会很消耗CPU

    (3)、多路复用IO

        基本原理:就是有个函数(如select)会不断地轮询所负责的所有 socket,当某个 

    socket 有数据到达了,就通知用户进程,

    (4)、异步IO模型(后面具体来说)

三、select

1、函数原型

#include <sys/select.h>   
    int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);      

   maxfdp 一个整数值,指的是集合中所有文件描述符的范围,即文件描述符数量+1

   readfds,writefds,errorfds 都应该包括文件描述符

   timeout 超时时间

2、重要的宏

void FD_ZERO(int fd, fd_set *fdset); // 关闭描述字集上的所有位  

void FD_SET(int fd, fd_set *fd_set);// 打开描述字集上一个描述符位

void FD_CLR(int fd, fd_set *fdset);  // 关闭描述字集上一个描述符位 

int FD_ISSET(int fd, fd_set *fdset); // 判断描述符位是否在描述字集上

(1) FD_ZERO宏,初始化,将一个 fd_set类型变量的所有位都设为 0

// 下面上代码

// 服务器部分

SelectClient.h

#pragma once
#include <winsock2.h>
#include <iostream>
#include <vector>
#include <thread>
#pragma comment(lib, "ws2_32.lib")  //加载 ws2_32.dll

typedef std::vector<int> VecClientFd; // 保存已经连接上的客户端套接字描述符

/*---------------------------------------------------------------------------
** 类名 : SubServer
**---------------------------------------------------------------------------
** 功能 : 子服务器
**---------------------------------------------------------------------------
** Date     Name
** 2018.02.06 Mark
**---------------------------------------------------------------------------*/
class SelectClient
{
public:

  // 初始化
  SelectClient();

  // 释放
  ~SelectClient();

  // 初始化网络服务
  bool InitNetService();

private:

  // 初始化套接字
  int InitSocket();

  // 响应客户端连接
  static bool th_DealClientConnect(int iServerfd);

  // 处理广播
  static void DealBroadCast(char*  bufferSend);

  // 获取新增客户端套接字
  static int QuerySockClient();

  // 设置新增客户端套接字
  static void SetSocketClient(int iSock_client);
private:

  static SelectClient * m_pSelectClient; // 轮询客户端连接模块

  VecClientFd m_VecClientFd; // 已经连接上的客户端套接字描述符

  std::thread *m_thSelectClient; // 轮询客户端请求线程

  int m_iSock_client;// 客户端连接

};      

SelectClient.cpp

#include "SelectClient.h"

SelectClient * SelectClient::m_pSelectClient = NULL;
/*--------------------------------------------------------------------
** 名称 : SelectClient
**--------------------------------------------------------------------
** 功能 : 初始化
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date:    Name
** 19.02.10   Mark
**-------------------------------------------------------------------*/
SelectClient::SelectClient()
{
  m_pSelectClient = this;
}

/*--------------------------------------------------------------------
** 名称 : ~SubServer
**--------------------------------------------------------------------
** 功能 : 释放
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date:    Name
** 19.02.10   Mark
**-------------------------------------------------------------------*/
SelectClient::~SelectClient()
{
  delete m_thSelectClient;

}

/*--------------------------------------------------------------------
** 名称 : InitNetService
**--------------------------------------------------------------------
** 功能 : 初始化网络服务
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date:    Name
** 19.02.10   Mark
**-------------------------------------------------------------------*/
bool SelectClient::InitNetService()
{
  int iServerfd = InitSocket();

  // 处理客户端连接
  m_thSelectClient = new std::thread(th_DealClientConnect,iServerfd);   
  return true;
}

/*--------------------------------------------------------------------
** 名称 : InitSocket
**--------------------------------------------------------------------
** 功能 : 初始化网络
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : serverfd
**--------------------------------------------------------------------
** Date:    Name
** 19.02.10   Mark
**-------------------------------------------------------------------*/
int SelectClient::InitSocket()
{
  //初始化DLL
  WSADATA wsaData;
  int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
  if(NO_ERROR != iResult)
  {
    return 0;
  }

  //创建套接字
  SOCKET iListenSocket = INVALID_SOCKET;
  iListenSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

  if(INVALID_SOCKET == iListenSocket)
  {
    std::cout << "Create Socket Error!" << std::endl;

    WSACleanup();

    return 0;
  }
  else
  {
    std::cout << "Create Socket Success" << std::endl;
  }

  sockaddr_in service;
  service.sin_family = AF_INET;
  service.sin_addr.s_addr = inet_addr("127.0.0.1");
  service.sin_port = htons(9999);
  //service.sin_zero = 0;
  //----------------------
  // Bind the socket.
  iResult = bind(iListenSocket, (SOCKADDR *)&service, sizeof(service));
  if(INVALID_SOCKET == iResult)
  {
    WSACleanup();

    return 0;
  }
  else
  {
    std::cout << "Bind Socket Success" << std::endl;
  }

  int iRet = listen(iListenSocket, SOMAXCONN);
  if(0 != iRet)
  {
    return 0;
  }
  else
  {
    std::cout << "Listen Socket Success" << std::endl;
  }

  return iListenSocket;
}
/*--------------------------------------------------------------------
** 名称 : th_DealClientConnect
**--------------------------------------------------------------------
** 功能 : 处理网络请求
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : serverfd
**--------------------------------------------------------------------
** Date:    Name
** 19.02.10   Mark
**-------------------------------------------------------------------*/
bool SelectClient::th_DealClientConnect(int iServerfd)
{
  if(0 == iServerfd)
  {
    return false;
  }

  fd_set client_fdset;  /*监控文件描述符集合*/
  struct timeval tv;    /*超时返回时间*/

  tv.tv_sec = 10;
  tv.tv_usec = 0;

  int conn_amount = 0;    //用来记录描述符数量
  SOCKET client_sockfd[5] = {0};   //存放活动的sockfd
  int iMaxsock;            //监控文件描述符中最大的文件号
  iMaxsock = iServerfd;

  char buffer[256];
  memset(buffer, '\0', sizeof(buffer));

  while(1)
  {
    /*初始化文件描述符号到集合*/
    FD_ZERO(&client_fdset);

    /*加入服务器描述符*/
    FD_SET(iServerfd, &client_fdset);

    //把活动的句柄加入到文件描述符中
    for (int i = 0; i < 5; ++i)
    {
      //程序中Listen中参数设为5,故i必须小于5
      if (client_sockfd[i] != 0)
      {
        FD_SET(client_sockfd[i], &client_fdset);
      }
    }
    int ret = select(iMaxsock + 1, &client_fdset, NULL, NULL, &tv);
    if(ret < 0) // 出错 -1
    {
      perror("select error!\n");
      
      int i = WSAGetLastError();
      
      break;
      
    }
    else if(ret == 0) // 超时 0
    {
      //printf("timeout!\n");
  
      continue;
    }


    // 检测是否有网络IO请求
    for (int i = 0; i < conn_amount; i++)
    {
      if (FD_ISSET(client_sockfd[i], &client_fdset))
      {
        int iClientFd = client_sockfd[i];
        ret = recv(iClientFd, buffer, 1024, 0);
        if (ret < 0)
        {
          //return -1;
          FD_CLR(client_sockfd[i], &client_fdset);
          client_sockfd[i] = 0;
        }
        else
        {
          std::cout << "buffer = " << buffer << "strlen(buffer)"<< strlen(buffer) <<std::endl;
          DealBroadCast(buffer);
          memset(buffer, '\0', sizeof(buffer));

        }
        
      }
    }

    // 检测是否有新的连接
    if(FD_ISSET(iServerfd, &client_fdset))
    {
      struct sockaddr_in client_addr;
      size_t size = sizeof(struct sockaddr_in);

      int iSock_client = accept(iServerfd, (struct sockaddr*)(&client_addr), (int*)(&size));
      if(iSock_client < 0)
      {
        perror("accept error!\n");
        continue;
      }

      // 把连接加入到新的文件描述符集合中
      if(conn_amount < 5)
      {
        client_sockfd[conn_amount++] = iSock_client;
        
        std::cout << "sock_client " << iSock_client << "Accept" << std::endl;
        m_pSelectClient->m_VecClientFd.push_back(iSock_client);
      }
    
    }


  }
  return true;
}

/*--------------------------------------------------------------------
** 名称 : DealBroadCast
**--------------------------------------------------------------------
** 功能 : 处理广播
**--------------------------------------------------------------------
** 参数 : sock_client 客户端连接
** 返值 : NULL
**--------------------------------------------------------------------
** Date:    Name
** 19.02.10   Mark
**-------------------------------------------------------------------*/
void SelectClient::DealBroadCast(char*  bufferSend) // 设计成类似群聊
{
  if(0 == strlen(bufferSend))
  {
    return;
  }

  VecClientFd::iterator itClientFd = m_pSelectClient->m_VecClientFd.begin();
  for (itClientFd; itClientFd != m_pSelectClient->m_VecClientFd.end(); itClientFd++)
  {
    int iClientFd = *itClientFd;
    send(iClientFd, bufferSend, strlen(bufferSend), 0);

    std::cout << "BroadCast size =  "<< strlen(bufferSend) <<"Info = " << bufferSend<<std::endl;

  }
  memset(bufferSend, '\0', sizeof(bufferSend));
}
/*--------------------------------------------------------------------
** 名称 : QuerySockClient
**--------------------------------------------------------------------
** 功能 : 查询新增客户端套接字
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : 新增客户端套接字
**--------------------------------------------------------------------
** Date:    Name
** 19.02.18   Mark
**-------------------------------------------------------------------*/
int SelectClient::QuerySockClient()
{
  int iSocket_client = m_pSelectClient->m_iSock_client;

  return iSocket_client;
}
/*--------------------------------------------------------------------
** 名称 : SetSocketClient
**--------------------------------------------------------------------
** 功能 : 设置新增客户端套接字
**--------------------------------------------------------------------
** 参数 : iSock_client 新增客户端套接字
** 返值 : NULL
**--------------------------------------------------------------------
** Date:    Name
** 19.02.18     Mark
**-------------------------------------------------------------------*/
void SelectClient::SetSocketClient(int iSock_client)
{
  if(iSock_client >0)
  {
    m_pSelectClient->m_iSock_client = iSock_client;
  }
  
}      

// 服务器入口

#include <iostream>
//#include "ConnectLoginServer.h"
#include "SelectClient.h"
#include <windows.h>

int main()
{
//  ConnectLoginServer m_SubServer;
  SelectClient m_SelectClient;
// 
//  bool bResult = m_SubServer.StartNetService();

  bool bResult = true;
  if(bResult)
  {
    m_SelectClient.InitNetService();
  }
  

  system("pause");

  return 0;
}      

// 客户端部分

// 头文件

#pragma once
#include <WinSock2.h>
#include <WS2tcpip.h>
#include<iostream>
#include<thread>
#pragma comment(lib, "ws2_32.lib")  //加载 ws2_32.dll

//网络地址
struct STNetAddress
{
  const char* pszIp;
  int iPort;
  int iSocket;
};

// 邮件结构体
struct STMailInfo
{
  //bool bSpecial;
  const char* strTitle; // 邮件标题
  const char* strContent; // 邮件内容
//  STMailInfo()
//  {
//    //bSpecial = false;
//  }

};
/*---------------------------------------------------------------------------
** 类名 : Client
**---------------------------------------------------------------------------
** 功能 : 客户端
**---------------------------------------------------------------------------
** Date     Name
** 2019.02.11 Mark
**---------------------------------------------------------------------------*/
class Client
{
public:

  // 初始化
  Client();

  // 释放
  ~Client();

  // 开启网络服务
  bool StartNetService();

  // 网络测试
  bool StartNetServiceTest();

  // 开始广播
  static void SendBroadCast();
private:

  // 连接功能服务器
  int LinkFunctionServer(const char*pszIP, int iPort);
  
  // 等待广播消息
  static void BroadCast(int iServerFd);

  // 获取服务器id
  static int QueryServerSocket();

private:

  static Client* m_pClient; //客户端指针

  std::thread* th_BroadCast; //广播

  int m_iSubServerSocket; // 服务器套接字

  static STMailInfo m_MailInfo;
  static STNetAddress m_stNetAddr;
};      
#include "Client.h"
#define DEFAULT_BUFLEN 256
#define D_BROADCAST_SEND 64

Client* Client::m_pClient = NULL;

STMailInfo Client::m_MailInfo = {"",""};
STNetAddress Client::m_stNetAddr = { "",0,0 };

/*--------------------------------------------------------------------
** 名称 : Client
**--------------------------------------------------------------------
** 功能 : 初始化
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date:    Name
** 19.02.11   Mark
**-------------------------------------------------------------------*/
Client::Client()
{
  m_pClient = this;
}

/*--------------------------------------------------------------------
** 名称 : ~Client
**--------------------------------------------------------------------
** 功能 : 释放
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date:    Name
** 19.02.11   Mark
**-------------------------------------------------------------------*/
Client::~Client()
{
  delete th_BroadCast;
}

/*--------------------------------------------------------------------
** 名称 : StartNetService
**--------------------------------------------------------------------
** 功能 : 开启网络服务
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date:    Name
** 19.02.11   Mark
**-------------------------------------------------------------------*/
bool Client::StartNetService()
{
  //初始化DLL
  WSADATA wsaData;
  int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
  if (NO_ERROR != iResult)
  {
    return 0;
  }

  //创建套接字
  SOCKET iListenSocket = INVALID_SOCKET;
  iListenSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

  if(INVALID_SOCKET == iListenSocket)
  {
    std::cout << "Create Socket Error!" << std::endl;

    WSACleanup();

    return false;
  }
  else
  {
    std::cout << "Create Socket Success" << std::endl;
  }

  sockaddr_in service;

  service.sin_family = AF_INET;
  service.sin_addr.s_addr = inet_addr("127.0.0.1");
  service.sin_port = htons(6666);

  //----------------------
  // Bind the socket.
  iResult = connect(iListenSocket, (SOCKADDR *)&service, sizeof(service));
  if (0 != iResult)
  {
    std::cout << "ConnectMainServer Failed " << iResult << std::endl;
  }
  else
  {
    std::cout << "ConnectMainServer Success!" << std::endl;
    //const char *buf = "OK";
    //send(iListenSocket, buf, strlen(buf), 0);
    char recvbuf[DEFAULT_BUFLEN];
    int recvbuflen = DEFAULT_BUFLEN;

    iResult = recv(iListenSocket, recvbuf, recvbuflen, 0);
    if(iResult > 0)
    {
      std::cout << "  recvbuf= " << recvbuf << std::endl;
      printf("recvbuf = %s\n", recvbuf);

      m_iSubServerSocket = LinkFunctionServer("127.0.0.1",6666);

      if(m_iSubServerSocket > 0)
      {
        th_BroadCast = new std::thread(BroadCast,static_cast<int>(m_iSubServerSocket)); //收听广播

      }
    }
    else if(0 == iResult)
    {
      std::cout <<"Connection Closed!" << std::endl;
    }
    else
    {
      std::cout << "Received Error"<< WSAGetLastError()<< std::endl;
    }
  }

  return true;
}
/*--------------------------------------------------------------------
** 名称 : StartNetServiceTest
**--------------------------------------------------------------------
** 功能 : 网络测试
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date:    Name
** 19.02.18   Mark
**-------------------------------------------------------------------*/
bool Client::StartNetServiceTest()
{
  m_iSubServerSocket = LinkFunctionServer("127.0.0.1", 9999);

  if(m_iSubServerSocket > 0)
  {
    th_BroadCast = new std::thread(BroadCast, static_cast<int>(m_iSubServerSocket)); //收听广播

  }
  return true;
}

/*--------------------------------------------------------------------
** 名称 : SendBroadCast
**--------------------------------------------------------------------
** 功能 : 开始广播
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date:    Name
** 19.02.18   Mark
**-------------------------------------------------------------------*/
void Client::SendBroadCast()
{
  char chSend[D_BROADCAST_SEND];

  while(true)
  {
    memset(chSend, 0, strlen(chSend));

    gets_s(chSend);
    int iServerSocket = QueryServerSocket();

    send(iServerSocket, chSend, sizeof(chSend), 0);
  }
  
}

/*--------------------------------------------------------------------
** 名称 : LinkFunctionServer
**--------------------------------------------------------------------
** 功能 : 连接功能服务器
**--------------------------------------------------------------------
** 参数 : pszIP IP
** 参数 : iPort 端口
** 参数 : iSocket 套接字
** 返值 : NULL
**--------------------------------------------------------------------
** Date:    Name
** 19.02.15   Mark
**-------------------------------------------------------------------*/
int Client::LinkFunctionServer(const char* pszIP, int iPort)
{
  if(iPort <= 0)
  {
    return 0;
  }

  //初始化DLL
  WSADATA wsaData;
  int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
  if(NO_ERROR != iResult)
  {
    return 0;
  }

  //创建套接字
  SOCKET iListenSocket = INVALID_SOCKET;
  iListenSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

  if(INVALID_SOCKET == iListenSocket)
  {
    std::cout << "Create Socket Error!" << std::endl;

    WSACleanup();

    return 0;
  }
  else
  {
    std::cout << "Create Socket Success" << std::endl;
  }

  sockaddr_in service;

  service.sin_family = AF_INET;
  service.sin_addr.s_addr = inet_addr(pszIP);
  service.sin_port = htons(iPort);

  //----------------------
  // Bind the socket.
  iResult = connect(iListenSocket, (SOCKADDR *)&service, sizeof(service));
  if(0 != iResult)
  {
    std::cout << "ConnectFunctionServer Failed " << iResult << std::endl;

    return 0;
  }
  else
  {
    std::cout << "ConnectFunctionServer Success " << iResult << std::endl;

    char buffer[256] = "OK I Came";
    //strcmp(buffer, "OK I Came");
    //sprintf(buffer, "OK I Came");
    send(iListenSocket, buffer, strlen(buffer), 0);
    std::cout <<"send buffer" << buffer << "strlen(buffer)"<< strlen(buffer) << std::endl;
    memset(buffer, '\0', sizeof(buffer));
  }

  return iListenSocket;
}
/*--------------------------------------------------------------------
** 名称 : BroadCast
**--------------------------------------------------------------------
** 功能 : 广播
**--------------------------------------------------------------------
** 参数 : iServerFd 服务器套接字
** 返值 : NULL
**--------------------------------------------------------------------
** Date:    Name
** 19.02.15   Mark
**-------------------------------------------------------------------*/
void Client::BroadCast(int iServerFd)
{
  char recvbuf[DEFAULT_BUFLEN];
  int recvbuflen = DEFAULT_BUFLEN;

  while(1)
  {
    memset(recvbuf, '\0', sizeof(recvbuf));
    int iResult = recv(iServerFd, recvbuf, 1024, 0);
    if(iResult > 0)
    {
      std::cout << "Reveived BroadCast "<< recvbuf << std::endl;
    }
    else if (0 == iResult)
    {
      std::cout << "Connection Closed!" << std::endl;
    }
    memset(recvbuf, '\0', sizeof(recvbuf));
//    else
//    {
//      std::cout << "Received Error" << WSAGetLastError() << std::endl;
//    }

    //Sleep(5000);
  }
}
/*--------------------------------------------------------------------
** 名称 : QueryServerSocket
**--------------------------------------------------------------------
** 功能 : 获取服务器套接字
**--------------------------------------------------------------------
** 参数 : NULL 
** 返值 : NULL
**--------------------------------------------------------------------
** Date:    Name
** 19.02.18   Mark
**-------------------------------------------------------------------*/
int Client::QueryServerSocket()
{
  int iServerSocket = m_pClient->m_iSubServerSocket;

  return iServerSocket;
}      
#include <iostream>
#include "Client.h"
#include <windows.h>

int main()
{
  Client m_client;
  //m_client.StartNetService();
  m_client.StartNetServiceTest();

  m_client.SendBroadCast();
  system("pause");
  return 0;
}