高级IO---多路转接模型的实现:select模型、poll模型、epoll模型

目录

 1.select、poll、epoll简介

 2.select

 3.poll

 4.epoll


1.select、poll、epoll简介

  • select:进行大量的描述符事件监控,返回描述符就绪了的事件集合。
  • poll:针对大量描述符进行监控,但poll的监控是为每个描述符设置了一个事件结构体。
  • epoll的监控是一个异步阻塞监控,监控过程由系统完成,epoll的事件触发方式包含水平触发和边缘触发。

2.select

例1:

#include<stdio.h>
#include<unistd.h>
#include<time.h>
#include<sys/select.h>
#include<sys/socket.h>

int main()
{
    fd_set rfds;
    FD_ZERO(&rfds);//初始化集合
    while (1)
    {
        struct timeval tv;
        tv.tv_usec = 0;
        tv.tv_sec = 3;
        FD_SET(0,&rfds);//因为select会修改rfds集合,因此每次要重新添加描述符到集合中
        int max_fd = 0;
        //描述符的监控会做一件事情:在返回前,将没有就绪的描述符从集合中移除
        int ret = select(max_fd + 1,&rfds,NULL,NULL,&tv);
        if(ret < 0)
        {
            perror("select error");
            usleep(1000);
            continue;
        }
        else if(ret == 0)
        {
            printf("wait timeout\n");
            usleep(1000);
            continue;
        }
        for(int i = 0;i <= max_fd;i++)//因为没有把所有的描述符保存起来,所以从0到当前最大的描述符逐个判断
        {
            if(FD_ISSET(i,&rfds))//如果i这个描述符在集合中,就表示就绪了事件
            {
                char buf[1024] = {0};
                int res = read(i,buf,1023);
                if(res <= 0)
                {
                    perror("recv error");
                    FD_CLR(i,&rfds);
                    return -1;
                }
                printf("%d 描述符就绪了事件,读取数据:%s\n",i,buf);
            }
        }
            
        
    }
    return 0;
}
select1:select1.c
	gcc $^ -o $@

结果如下: 

例2:

 select.hpp:

#include "tcp_socket.hpp"
#include<time.h>
#include<vector>
#include<sys/select.h>
class Select
{ 
   private:
       fd_set _rfds;//可读事件的描述集合的备份,每次监控都是从这个集合复制一份出来进行监控(select会修改集合)
       int _max_fd;
    public:
       Select():_max_fd(-1)
       {
        //针对成员变量的初始化
        FD_ZERO(&_rfds);
       }

       //将sock中的描述符fd,添加到rfds可读事件的描述符集合中
       bool Add(TcpSocket &sock)
       {
            int fd = sock.GetFd();
            FD_SET(fd,&_rfds);//将描述符添加到监控集合中
            _max_fd = _max_fd >fd ?_max_fd:fd;//重新设置最大的描述符
            return true;
       }

       //将sock中的描述符fd,从rfds可读事件的描述符集合中移除
       bool Del(TcpSocket &sock)
       {
            int fd = sock.GetFd();
            FD_CLR(fd,&_rfds);//从当前的监控集合中移除指定的描述符
            //如果当前移除的刚好就是最大的描述符,这时候从最大描述符开始向前判断哪个描述符还在集合中,第一个就是最大的
        for(int i = _max_fd;i >= 0;i--)
        {
            if(FD_ISSET(i,&_rfds))
            {
                _max_fd = i;
                break;
            }
        }
        return 0;
       }
 
      //开始监控,返回就绪的描述符的数组
       bool Wait(std::vector<TcpSocket> *arry,int timeout = 3000)
       {
          struct timeval tv;
          tv.tv_sec = timeout / 1000;
          tv.tv_usec = (timeout % 1000) * 1000;
          fd_set tmp = _rfds;//使用临时集合进行监控,因为select会修改监控的集合
          int ret = select(_max_fd +1,&tmp,NULL,NULL,&tv);
          if(ret < 0)
          {
            perror("select error");
            return false;
          }
          else if(ret ==0)
          {
            printf("select timeout!\n");
            return false;
          }
          for(int i=0;i <= _max_fd;i++)
          {
            if(FD_ISSET(i,&tmp))
            {
                TcpSocket sock;
                sock.SetFd(i);
                arry->push_back(sock);
            }
           
          }
          return true;
       }
};

 tcp_socket.hpp:

#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>

#define MAX_LISTEN 5
#define CHECK_RES(q) if((q)==false) { return -1;}
class TcpSocket{
   private:
       int _sockfd;
   public:
       TcpSocket():_sockfd(-1){}
       int GetFd()
       {
        return _sockfd;
       }
       void SetFd(int fd)
       {
        _sockfd = fd;
       }
    //创建套接字  
     bool Socket()
       {
         _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
         if(_sockfd < 0)
         {
           perror("socket error");
           return false;
         }
       return true;
       }

   //绑定地址信息
       bool Bind(const std::string &ip,uint16_t port)
       {
         struct sockaddr_in addr;//先定义一个ipv4的地址结构
         addr.sin_family = AF_INET;
         addr.sin_port = htons(port);
         addr.sin_addr.s_addr = inet_addr(ip.c_str());
         socklen_t len = sizeof(struct sockaddr_in);
         int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
         if(ret<0)
         {
            perror("bind error");
            return false;
         }
         return true;
         }

    //向服务器发起连接
      bool Connect(const std::string &ip,uint16_t port)
     {
        struct sockaddr_in addr;
       addr.sin_family = AF_INET;
       addr.sin_port = htons(port);
       addr.sin_addr.s_addr = inet_addr(ip.c_str());
       socklen_t len = sizeof(struct sockaddr_in);
       int ret = connect(_sockfd,(struct sockaddr*)&addr,len);
       if(ret < 0)
         {
           perror("connect error");
           return false;
         }
       return true;
     }
     
   //服务器开始监听
    bool Listen(int backlog = MAX_LISTEN)
     {
      int ret = listen(_sockfd,backlog);
      if(ret < 0)
      {
        perror("listen error");
        return false;
      }
     return true;
    }

  //获取新建连接
    bool Accept(TcpSocket *sock,std::string *ip=NULL,uint16_t *port=NULL)
    {
     struct sockaddr_in addr;
     socklen_t len = sizeof(struct sockaddr_in);
     int newfd = accept(_sockfd,(struct sockaddr*)&addr,&len);
     if(newfd<0)
       {
        perror("accept error");
        return false;
       }
     sock->_sockfd = newfd;
     if(ip != NULL)
      {
        *ip = inet_ntoa(addr.sin_addr);
      }
     if(port != NULL)
      {
        *port = ntohs(addr.sin_port);
      }
     return true;
    }

   //接受数据
  bool Recv(std::string *body)
  {
   char tmp[4096] = {0};
   int ret = recv(_sockfd,tmp,4096,0);
   if(ret < 0)
   {
    perror("recv error");
    return false;
   }
   else if(ret == 0)
   {
    std::cout<<"peer shutdown!\n";
    return false;
   }
   body->assign(tmp,ret);//从tmp中截取ret长度大小的数据
   return true;
 }



   //发送数据
    bool Send(const std::string &body)
    {
       int ret;
       ret = send(_sockfd,body.c_str(),body.size(),0);
       if(ret < 0)
       {
         perror("send error");
         return false;
       }
     return true;
    }

  //关闭套接字
  bool Close()
  {
    if(_sockfd!= -1)
    {
      close(_sockfd);
    }
   return true;
  }
  



};

tcp_srv.cpp:

//#include "tcp_socket.hpp"
#include "select.hpp"
int main()
{
  TcpSocket lst_sock;

  //创建套接字
   CHECK_RES(lst_sock.Socket());
  //绑定地址信息
   CHECK_RES(lst_sock.Bind("0.0.0.0",20000));
  //开始监听
   CHECK_RES(lst_sock.Listen());

   Select s;
   s.Add(lst_sock);//将监听套接字添加到集合中
   while(1)
   {
     std::vector<TcpSocket> arry;
     bool ret = s.Wait(&arry);
     if(ret == false)
     {
      usleep(1000);
      continue;
     }
     for(TcpSocket &a:arry)
     {
        //array里边就是就绪的描述符,一开始只有一个监听套接字,就绪的肯定就是监听套接字代表有新连接
        if(a.GetFd()==lst_sock.GetFd())//如果就绪的套接字描述符与监听套接字描述符一样,就表示有新建连接
        {
          TcpSocket conn_sock;
          std::string cliip;
          uint16_t cliport;
          bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);
          if(ret < 0)
          {
            continue;
          }
          std::cout<<"new connect:"<<cliip<<":"<<cliport<<std::endl;
          s.Add(conn_sock);//将新建的套接字也添加监控
        }
        else
        {
          std::string buf;
          ret = a.Recv(&buf);
          if(ret == false)
          {
            s.Del(a);//出错了则移除监控
            a.Close();
            continue;
          }
          std::cout<<"client say:"<<buf<<std::endl;

          std::cout<<"server say:";
          fflush(stdout);
          std::cin>>buf;
          ret = a.Send(buf);
          if(ret == false)
          {
            s.Del(a);
            a.Close();
            continue;
          }
        }
     }
   }
   //关闭套接字
   lst_sock.Close();
   return 0;
}

    

tcp_cli.cpp:

#include "tcp_socket.hpp"

int main(int argc,char* argv[])
{
  if(argc!= 3)
  {
    std::cout<<"please input server address!\n";
    std::cout<<"USsage:./tcp_cli 192.168.164.128 20000\n";
    return -1;
  }
 std::string srv_ip = argv[1];
 uint16_t srv_port = std::stoi(argv[2]);
 TcpSocket cli_sock;
 //创建套接字
   CHECK_RES(cli_sock.Socket());
 //绑定地址信息(客户端不推荐)
 //向服务器发起连接
   CHECK_RES(cli_sock.Connect(srv_ip,srv_port));
   while(1)
   {
     //与服务器通信
     std::string buf;
     std::cout<<"client say:";
     fflush(stdout);
     std::cin>>buf;
     bool ret = cli_sock.Send(buf);
    if(ret == false)
    {
     cli_sock.Close();
     return -1;
    }
    buf.clear();
    ret = cli_sock.Recv(&buf);
    if(ret == false)
    {
     cli_sock.Close();
     return -1;
    }
    std::cout<<"server say:"<<buf<<std::endl;
  }
 //关闭套接字
  cli_sock.Close();
  return 0;
 }
   
  

 makefile:

all:tcp_cli tcp_srv select1
tcp_cli:tcp_cli.cpp
	g++ -std=c++11 $^ -o $@
tcp_srv:tcp_srv.cpp
	g++ -std=c++11 $^ -o $@
select1:select1.c
	gcc $^ -o $@

 测试结果:

服务器端:

 客户端1:

客户端2:

 服务器处于一直监听的状态,客户端1请求服务器,与服务器连接,可进行多次循环通信,此时客户端2请求建立连接后,也可和服务器进行多次循环通信,注意服务器端绑定的地址以及客户端执行代码输入的地址。

 3.poll

poll.c:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<poll.h>
int main()
{
    struct pollfd pfds[10];
    int poll_count = 0;
    pfds[poll_count].fd = 0;//要监控的描述符是标准输入
    pfds[poll_count].events = POLLIN;//针对标准输入要监控的是可读事件
    poll_count++;
    while(1)
    {
        int ret = poll(pfds,poll_count,3000);
        if(ret < 0)
        {
            perror("poll error");
            usleep(1000);
            continue;
        }
        else if(ret == 0)
        {
            printf("poll timeout!\n");
            usleep(1000);
            continue;
        }
        for(int i=0;i < poll_count;i++)
        {
            if(pfds[i].revents & POLLIN)
            {
                //pfds[i].fd就绪了可读事件
                char buf[1024] = {0};
                read(pfds[i].fd,buf,1023);
                printf("%d 描述符就绪,读取数据:%s\n",pfds[i].fd,buf);
            }
            else if(pfds[i].revents & POLLOUT)
            {
                //就绪的是可写事件
            }
        }

    }
    return 0;
}

makefile:

poll:poll.c
	gcc $^ -o $@

结果:

 4.epoll

tcp_socket.hpp:

#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<fcntl.h>


#define MAX_LISTEN 5
#define CHECK_RES(q) if((q)==false) { return -1;}
class TcpSocket{
   private:
       int _sockfd;
   public:
       TcpSocket():_sockfd(-1){}
       int GetFd()
       {
        return _sockfd;
       }
       void SetFd(int fd)
       {
        _sockfd = fd;
       }
       void SetNonBlock()
       {
        int flag = fcntl(_sockfd,F_GETFL,0);
        fcntl(_sockfd,F_SETFL,flag | O_NONBLOCK);//在原有属性基础上新增非阻塞属性
       }
    //创建套接字  
     bool Socket()
       {
         _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
         if(_sockfd < 0)
         {
           perror("socket error");
           return false;
         }
       return true;
       }

   //绑定地址信息
       bool Bind(const std::string &ip,uint16_t port)
       {
         struct sockaddr_in addr;//先定义一个ipv4的地址结构
         addr.sin_family = AF_INET;
         addr.sin_port = htons(port);
         addr.sin_addr.s_addr = inet_addr(ip.c_str());
         socklen_t len = sizeof(struct sockaddr_in);
         int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
         if(ret<0)
         {
            perror("bind error");
            return false;
         }
         return true;
         }

    //向服务器发起连接
      bool Connect(const std::string &ip,uint16_t port)
     {
        struct sockaddr_in addr;
       addr.sin_family = AF_INET;
       addr.sin_port = htons(port);
       addr.sin_addr.s_addr = inet_addr(ip.c_str());
       socklen_t len = sizeof(struct sockaddr_in);
       int ret = connect(_sockfd,(struct sockaddr*)&addr,len);
       if(ret < 0)
         {
           perror("connect error");
           return false;
         }
       return true;
     }
     
   //服务器开始监听
    bool Listen(int backlog = MAX_LISTEN)
     {
      int ret = listen(_sockfd,backlog);
      if(ret < 0)
      {
        perror("listen error");
        return false;
      }
     return true;
    }

  //获取新建连接
    bool Accept(TcpSocket *sock,std::string *ip=NULL,uint16_t *port=NULL)
    {
     struct sockaddr_in addr;
     socklen_t len = sizeof(struct sockaddr_in);
     int newfd = accept(_sockfd,(struct sockaddr*)&addr,&len);
     if(newfd<0)
       {
        perror("accept error");
        return false;
       }
     sock->_sockfd = newfd;
     if(ip != NULL)
      {
        *ip = inet_ntoa(addr.sin_addr);
      }
     if(port != NULL)
      {
        *port = ntohs(addr.sin_port);
      }
     return true;
    }

  //接收数据
  bool Recv(std::string *body)
  {
   char tmp[4096] = {0};
   int ret = recv(_sockfd,tmp,4096,0);
   if(ret < 0)
   {
    perror("recv error");
    return false;
   }
   else if(ret == 0)
   {
    std::cout<<"peer shutdown!\n";
    return false;
   }
   body->assign(tmp,ret);//从tmp中截取ret长度大小的数据
   return true;
 }

   //接受数据
   /*
  bool Recv(std::string *body)
  {
    while(1)
    {
      char tmp[6] = {0};
      int ret = recv(_sockfd,tmp,5,0);
      if(ret < 0)
      {
         if(errno == EAGAIN)//EAGAIN这个错误表示当前缓冲区没有数据了,资源还没有准备好
         {
          return true;
         }
         perror("recv error");
         return false;
      }
      else if(ret == 0)
      {
        std::cout<<"peer shutdown!\n";
        return false;
      }
      *body += tmp;
    }
    return true;
 }
 */

   //发送数据
    bool Send(const std::string &body)
    {
       int ret;
       ret = send(_sockfd,body.c_str(),body.size(),0);
       if(ret < 0)
       {
         perror("send error");
         return false;
       }
     return true;
    }

  //关闭套接字
  bool Close()
  {
    if(_sockfd!= -1)
    {
      close(_sockfd);
    }
   return true;
  }
  



};

epoll.hpp:

#include "tcp_socket.hpp"
#include<time.h>
#include<cstdlib>
#include<vector>
#include<sys/epoll.h>
#define EPOLL_MAX 100
class Epoll
{ 
   private:
       int _epfd;//epoll的操作句柄
    public:
       Epoll():_epfd(-1)
       {
            //针对成员变量的初始化
            _epfd = epoll_create(1);
            if(_epfd < 0)
            {
                perror("epoll error");
                exit(0);
            }
       }

       
       bool Add(TcpSocket &sock)
       {
            struct epoll_event ev;
            ev.events = EPOLLIN;
            ev.data.fd = sock.GetFd();
            int ret = epoll_ctl(_epfd,EPOLL_CTL_ADD,sock.GetFd(),&ev);
            if(ret < 0)
            {
                perror("epoll_ctl error");
                return false;
            }
            return true;
       }

       
       bool Del(TcpSocket &sock)
       {
            int ret = epoll_ctl(_epfd,EPOLL_CTL_DEL,sock.GetFd(),NULL);
            if(ret < 0)
            {
                perror("epoll_ctl error");
                return false;
            }
            return true;
       }
 
      
       bool Wait(std::vector<TcpSocket> *arry,int timeout = 3000)
       {
          struct epoll_event evs[EPOLL_MAX];
          int ret = epoll_wait(_epfd,evs,EPOLL_MAX,timeout);
          if(ret < 0)
          {
            perror("epoll_wait error");
            return false;
          }
          else if(ret == 0)
          {
            printf("epoll timeout!\n");
            return false;
          }
          for(int i = 0;i < ret;i++)
          {
            if(evs[i].events & EPOLLIN)//目前只管可读事件
            {
                TcpSocket sock;
                sock.SetFd(evs[i].data.fd);
                arry->push_back(sock);
            }
          }
          return true;
       }
};

tcp_srv.cpp:

/#include "tcp_socket.hpp"
//#include "select.hpp"
#include "epoll.hpp"
int main()
{
  TcpSocket lst_sock;

  //创建套接字
   CHECK_RES(lst_sock.Socket());
   //lst_sock.SetNonBlock();
  //绑定地址信息
   CHECK_RES(lst_sock.Bind("0.0.0.0",20000));
  //开始监听
   CHECK_RES(lst_sock.Listen());

   //Select s;
   Epoll s;
   s.Add(lst_sock);//将监听套接字添加到集合中
   while(1)
   {
     std::vector<TcpSocket> arry;
     bool ret = s.Wait(&arry);
     if(ret == false)
     {
      usleep(1000);
      continue;
     }
     for(TcpSocket &a:arry)
     {
        //array里边就是就绪的描述符,一开始只有一个监听套接字,就绪的肯定就是监听套接字代表有新连接
        if(a.GetFd()==lst_sock.GetFd())//如果就绪的套接字描述符与监听套接字描述符一样,就表示有新建连接
        {
          TcpSocket conn_sock;
          std::string cliip;
          uint16_t cliport;
          bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);
          if(ret < 0)
          {
            continue;
          }
          std::cout<<"new connect:"<<cliip<<":"<<cliport<<std::endl;
          //conn_sock.SetNonBlock();
          s.Add(conn_sock);//将新建的套接字也添加监控
        }
        else
        {
          std::string buf;
          ret = a.Recv(&buf);
          if(ret == false)
          {
            s.Del(a);//出错了则移除监控
            a.Close();
            continue;
          }
          std::cout<<"client say:"<<buf<<std::endl;

          std::cout<<"server say:";
          fflush(stdout);
          std::cin>>buf;
          ret = a.Send(buf);
          if(ret == false)
          {
            s.Del(a);
            a.Close();
            continue;
          }
        }
     }
   }
   //关闭套接字
   lst_sock.Close();
   return 0;
}

    

 tcp_cli.cpp:

#include "tcp_socket.hpp"

int main(int argc,char* argv[])
{
  if(argc!= 3)
  {
    std::cout<<"please input server address!\n";
    std::cout<<"USsage:./tcp_cli 192.168.164.128 20000\n";
    return -1;
  }
 std::string srv_ip = argv[1];
 uint16_t srv_port = std::stoi(argv[2]);
 TcpSocket cli_sock;
 //创建套接字
   CHECK_RES(cli_sock.Socket());
 //绑定地址信息(客户端不推荐)
 //向服务器发起连接
   CHECK_RES(cli_sock.Connect(srv_ip,srv_port));
   while(1)
   {
     //与服务器通信
     std::string buf;
     std::cout<<"client say:";
     fflush(stdout);
     std::cin>>buf;
     bool ret = cli_sock.Send(buf);
    if(ret == false)
    {
     cli_sock.Close();
     return -1;
    }
    buf.clear();
    ret = cli_sock.Recv(&buf);
    if(ret == false)
    {
     cli_sock.Close();
     return -1;
    }
    std::cout<<"server say:"<<buf<<std::endl;
  }
 //关闭套接字
  cli_sock.Close();
  return 0;
 }
   
  

makefile:

all:tcp_cli tcp_srv 
tcp_cli:tcp_cli.cpp
	g++ -std=c++11 $^ -o $@
tcp_srv:tcp_srv.cpp
	g++ -std=c++11 $^ -o $@

结果: 

 客户端1:

 客户端2:


 修改tcp_socket.hpp如下:

此时客户端发送helloalaotie,客户端只收到了hello

 以上代码只改动两处:①修改epoll.hpp如下:

② 修改tcp_socket.hpp如下:

 则结果为:

 加了EPOLLET的作用:只有在新数据到来时才会触发事件。

客户端发送nihaoalaotie,服务器只收到nihao,如果不用EPOLLET,则服务器回复客户端后会直接出现缓冲区的剩余数据(eg alaot),如果用了EPOLLET,则只有在客户端发送数据时,才会在缓冲区依次拿数据。---两者都修改了tcp_socket.hpp中的recv函数(如上面所示)。


修改tcp_socket.hpp如下:  (增加SetNonBlock方法以及Recv方法、增加头文件<fcntl.h>)

#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<fcntl.h>


#define MAX_LISTEN 5
#define CHECK_RES(q) if((q)==false) { return -1;}
class TcpSocket{
   private:
       int _sockfd;
   public:
       TcpSocket():_sockfd(-1){}
       int GetFd()
       {
        return _sockfd;
       }
       void SetFd(int fd)
       {
        _sockfd = fd;
       }
       void SetNonBlock()
       {
        int flag = fcntl(_sockfd,F_GETFL,0);
        fcntl(_sockfd,F_SETFL,flag | O_NONBLOCK);//在原有属性基础上新增非阻塞属性
       }
    //创建套接字  
     bool Socket()
       {
         _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
         if(_sockfd < 0)
         {
           perror("socket error");
           return false;
         }
       return true;
       }

   //绑定地址信息
       bool Bind(const std::string &ip,uint16_t port)
       {
         struct sockaddr_in addr;//先定义一个ipv4的地址结构
         addr.sin_family = AF_INET;
         addr.sin_port = htons(port);
         addr.sin_addr.s_addr = inet_addr(ip.c_str());
         socklen_t len = sizeof(struct sockaddr_in);
         int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
         if(ret<0)
         {
            perror("bind error");
            return false;
         }
         return true;
         }

    //向服务器发起连接
      bool Connect(const std::string &ip,uint16_t port)
     {
        struct sockaddr_in addr;
       addr.sin_family = AF_INET;
       addr.sin_port = htons(port);
       addr.sin_addr.s_addr = inet_addr(ip.c_str());
       socklen_t len = sizeof(struct sockaddr_in);
       int ret = connect(_sockfd,(struct sockaddr*)&addr,len);
       if(ret < 0)
         {
           perror("connect error");
           return false;
         }
       return true;
     }
     
   //服务器开始监听
    bool Listen(int backlog = MAX_LISTEN)
     {
      int ret = listen(_sockfd,backlog);
      if(ret < 0)
      {
        perror("listen error");
        return false;
      }
     return true;
    }

  //获取新建连接
    bool Accept(TcpSocket *sock,std::string *ip=NULL,uint16_t *port=NULL)
    {
     struct sockaddr_in addr;
     socklen_t len = sizeof(struct sockaddr_in);
     int newfd = accept(_sockfd,(struct sockaddr*)&addr,&len);
     if(newfd<0)
       {
        perror("accept error");
        return false;
       }
     sock->_sockfd = newfd;
     if(ip != NULL)
      {
        *ip = inet_ntoa(addr.sin_addr);
      }
     if(port != NULL)
      {
        *port = ntohs(addr.sin_port);
      }
     return true;
    }

  //接收数据
  
  bool RecvCli(std::string *body)
  {
    char tmp[4096] = {0};
    int ret = recv(_sockfd,tmp,4096,0);
    if(ret < 0)
    {
      perror("recv error");
      return false;
    }
    else if(ret == 0)
    {
      std::cout<<"peer shutdown!\n";
      return false;
    }
    body->assign(tmp,ret);//从tmp中截取ret长度大小的数据
    return true;
 }
 
 

   //接受数据
   
  bool Recv(std::string *body)
  {
    while(1)
    {
      char tmp[6] = {0};
      int ret = recv(_sockfd,tmp,5,0);
      if(ret < 0)
      {
         if(errno == EAGAIN)//EAGAIN这个错误表示当前缓冲区没有数据了,资源还没有准备好
         {
          return true;
         }
         perror("recv error");
         return false;
      }
      else if(ret == 0)
      {
        std::cout<<"peer shutdown!\n";
        return false;
      }
      *body += tmp;
    }
    return true;
 }
 
 

   //发送数据
    bool Send(const std::string &body)
    {
       int ret;
       ret = send(_sockfd,body.c_str(),body.size(),0);
       if(ret < 0)
       {
         perror("send error");
         return false;
       }
     return true;
    }

  //关闭套接字
  bool Close()
  {
    if(_sockfd!= -1)
    {
      close(_sockfd);
    }
   return true;
  }
  



};

修改tcp_srv.cpp:(创建套接字后设置非阻塞---两处)

//#include "tcp_socket.hpp"
//#include "select.hpp"
#include "epoll.hpp"
int main()
{
  TcpSocket lst_sock;

  //创建套接字
   CHECK_RES(lst_sock.Socket());
   lst_sock.SetNonBlock();
  //绑定地址信息
   CHECK_RES(lst_sock.Bind("0.0.0.0",20000));
  //开始监听
   CHECK_RES(lst_sock.Listen());

   //Select s;
   Epoll s;
   s.Add(lst_sock);//将监听套接字添加到集合中
   while(1)
   {
     std::vector<TcpSocket> arry;
     bool ret = s.Wait(&arry);
     if(ret == false)
     {
      usleep(1000);
      continue;
     }
     for(TcpSocket &a:arry)
     {
        //array里边就是就绪的描述符,一开始只有一个监听套接字,就绪的肯定就是监听套接字代表有新连接
        if(a.GetFd()==lst_sock.GetFd())//如果就绪的套接字描述符与监听套接字描述符一样,就表示有新建连接
        {
          TcpSocket conn_sock;
          std::string cliip;
          uint16_t cliport;
          bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);
          if(ret < 0)
          {
            continue;
          }
          std::cout<<"new connect:"<<cliip<<":"<<cliport<<std::endl;
          conn_sock.SetNonBlock();
          s.Add(conn_sock);//将新建的套接字也添加监控
        }
        else
        {
          std::string buf;
          ret = a.Recv(&buf);
          if(ret == false)
          {
            s.Del(a);//出错了则移除监控
            a.Close();
            continue;
          }
          std::cout<<"client say:"<<buf<<std::endl;

          std::cout<<"server say:";
          fflush(stdout);
          std::cin>>buf;
          ret = a.Send(buf);
          if(ret == false)
          {
            s.Del(a);
            a.Close();
            continue;
          }
        }
     }
   }
   //关闭套接字
   lst_sock.Close();
   return 0;
}

    

修改客户端tcp_cli.cpp---仍用原来的Recv方法,将其重命名为RecvCli:

#include "tcp_socket.hpp"

int main(int argc,char* argv[])
{
  if(argc!= 3)
  {
    std::cout<<"please input server address!\n";
    std::cout<<"USsage:./tcp_cli 192.168.164.128 20000\n";
    return -1;
  }
 std::string srv_ip = argv[1];
 uint16_t srv_port = std::stoi(argv[2]);
 TcpSocket cli_sock;
 //创建套接字
   CHECK_RES(cli_sock.Socket());
   
 //绑定地址信息(客户端不推荐)
 //向服务器发起连接
   CHECK_RES(cli_sock.Connect(srv_ip,srv_port));
    cli_sock.SetNonBlock();
   while(1)
   {
     //与服务器通信
     std::string buf;
     std::cout<<"client say:";
     fflush(stdout);
     std::cin>>buf;
     bool ret = cli_sock.Send(buf);
    if(ret == false)
    {
     cli_sock.Close();
     return -1;
    }
    buf.clear();
    ret = cli_sock.RecvCli(&buf);
    if(ret == false)
    {
     cli_sock.Close();
     return -1;
    }
    std::cout<<"server say:"<<buf<<std::endl;
  }
 //关闭套接字
  cli_sock.Close();
  return 0;
 }

测试结果:

 客户端1:

 客户端2:

 问题:如果客户端在连接后设置非阻塞,用了while循环以及新的Recv方法,则会出现:客户端输入数据会直接显示server say  client say,在服务器端输入数据在客户端是收不到的,原因有待挖掘。结果如下图:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值