TCP--IO多路转接模型

Posted zhao111222333444

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP--IO多路转接模型相关的知识,希望对你有一定的参考价值。

IO多路转接模型

classtcp.hpp

E>  1 #pragma once                                                                                                                                                                                                                                                                                  
    2 #include <cstdio>
    3 #include <iostream>
    4 #include <string>
    5 #include <unistd.h>
    6 #include <arpa/inet.h>
    7 #include <netinet/in.h>
    8 #include <sys/socket.h>
    9 #include<sys/select.h>
    //非阻塞                                                                                                                   
      #include<fcntl.h> 
      #include<errno.h> 

   10 
   11 #define LISTEN_MAX 5
   12 #define CHECK_RET(q) if((q)==false)return -1;
   13 //tcp的流程大致如下“
   14 //客户端:创建套接字,不推荐绑定地址信息,向服务器发送链接请求,收发数据,关闭套接字
   15 //服务端:创建套接字,绑定地址信息,开始监听,获取新建连接,收发数据,关闭套接字
   16 class TCPSocket
   17   private:
   18     int _sockfd;
   19   public:
   20     TCPSocket():_sockfd(-1)
   21 
   22     //加入select的setfd和getfd
   23     void SetFd(int fd)
   24       _sockfd=fd;
   25     
   26 
   27     int GetFd()
   28       return _sockfd;
   29     
   30 
   31    //地址重用的套接字选项,可以解决time_wait段时间不能重连的问题
   
   32     void SetSocketOpt()
   33       int opt=1;
E> 34       setsockopt(_sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));
   35     
   36 
   37 //设置为非阻塞
          bool SetNoBlock()
E> 42       int flag=fcntl(_sockfd,F_GETFL,0);
E> 43       fcntl(_sockfd,F_GETFL,flag|0_MOMBLOCK);                                                                              
   44     

   38 
   39     //创建套接字
   40     //socket(地址域类型,套接字类型,通信协议)
   41     //封装的接口需要获取操作句柄
   42     bool Socket()
E> 43       _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   44       if(_sockfd<0)
   45         perror("socket error");
   46         return false;
   47       
   48       return true;
   49     
   50 
   51     //绑定地址信息
   52     //接口(句柄,规范接口struct结构体,结构体大小)、
   53     //封装的接口(ip地址,port信息)
   54     bool Bind(const std::string &ip,const uint16_t port)
E> 55       sockaddr_in addr;
E> 56       addr.sin_family = AF_INET;//16位地址类型
E> 57       addr.sin_port = htons(port);
   58 
   59       //这两种都能拿到ip内容(str的首地址)
E> 60       addr.sin_addr.s_addr = inet_addr(&ip[0]);
   61       //addr.sin_addr.s_addr = inet_addr(ip.c_str());
   62 
E> 63       socklen_t len = sizeof(sockaddr_in);
   64       int ret=bind(_sockfd,(struct sockaddr*)&addr,len);
   65      
   66       if (ret < 0) 
   67         perror("bind error");
   68         return false;
   69       
   70       return true;
   71   
   72     
   73     
   74 
   75     //监听接口(操作句柄,最大连接数)
   76     //封装接口(最大连接数)
   77     bool Listen(int big =LISTEN_MAX)
   78       int ret=listen(_sockfd,LISTEN_MAX);
   79       if(ret<0)
   80         return false;
   81       
   82       return true;
   83     
   84 
   85 
   86 
   87     //申请链接(操作句柄,服务端的地址,地址长度)
   88     //封装的接口(服务器ip,服务器port)
   89     bool Connect(const std::string &ip,uint16_t port)
   90       sockaddr_in addr;
   91       addr.sin_family = AF_INET;
   92       addr.sin_port = htons(port);
   93       addr.sin_addr.s_addr = inet_addr(&ip[0]);
   94       socklen_t len = sizeof(sockaddr_in);
   95       int ret = connect(_sockfd, (sockaddr*)&addr, len);
   96       if (ret < 0) 
   97         perror("connect error");
   98         return false;
   99     
  100       return true;
  101     
  102     
  103     //封装的接口:
  104     //注意三个都是输出型参数(因为是处理请求的接口,所以不需要输入什么信息,只需要获得)
  105     //服务器同意链接接口(新的套接字地址,客户端的地址结构指针,地址结构大小指针)
  106     //注意这个的前提是客户端发送请求了,所以后两个数输出结构,用来获取请求的客户端的地址信息,当然可以不获取
  107     
  108     //原始的的接口(监听套接字,获取客户端的ip,客户端的port地址)
  109     //返回值是一个新的套接字,专门用来服务客户端,所以监听套接字相当于门迎~
  110     //将返回值放到封装的接口参数1,原始接口2是输出参数,分开封装到封装接口的23
  111     bool Accept(TCPSocket *sock,std::string *ip=NULL,uint16_t *port=NULL)
  112       //建立一个结构体用来存客户端的地址信息
  113       sockaddr_in addr;
  114       socklen_t len = sizeof(sockaddr_in);
  115 
  116       //注意这个len是输出型参数,所以是取地址!!!!
  117       int newfd=accept(_sockfd,(sockaddr*)&addr,&len);
  118       if (newfd < 0) 
  119         perror("accept error");
  120         return false;
  121                                                                                                                                                                                                                                                                                              
  122 
  123       //将输出型参数1填复制的句柄
  124       sock->_sockfd = newfd;
  125       if (ip != NULL) 
  126            *ip = inet_ntoa(addr.sin_addr);             
  127                                                         
  128       if (port != NULL) 
  129            *port = ntohs(addr.sin_port);             
  130       
  131          return true;
  132     
  133   
  134 
  135   
  136     //接收数据(操作句柄,接收缓冲区,接收数据长度,0阻塞)
  137     //封装的接口(接收缓冲区)
  138     //注意返回值小于0错误
  139     //等于0连接断开
  140     //大于0,实际大小
  141     bool Recv(std::string *buf)
  142       char tmp[1024]=0;
  143       int ret=recv(_sockfd,tmp,1024,0);
  144       if (ret < 0) 
  145         perror("recv error");
  146         return false;
  147       
  148       else if (ret == 0) 
  149 
  150         printf("peer shutdown");
  151         return false;
  152       
  153       //把缓冲区的内容放入真正的缓冲区
  154       //因为原始接口需要指定一次接受的数据大小
  155       //而直接用缓冲区接收不能确定大小
  156       buf->assign(tmp, ret);
  157       return true;
  158     


//边缘触发使用的循环读取recv
 172     //循环读取--边缘触发搭配循环读取非阻塞
  173     bool Recv(std::string *buf)
  174       char tmp[1024]=0;
  175       int len=1024;
  176       int total =0;
  177       while(total<len)
  178       int ret=recv(_sockfd,tmp+total,5,0);
  179       if (ret < 0) 
  180         if(errno==EAGAIN)
  181 
  182           break;
  183         
  184         perror("recv error");
  185         return false;
  186       
  187       else if (ret == 0) 
  188 
  189         printf("peer shutdown");
  190         return false;
  191       
  192       total+=ret;                                                                                                                                                                                  
  193       
  194       buf->assign(tmp, total);
  195       return true;
  196     




  159 
  160     //发送数据(描述符,数据,长度,标志位0阻塞)
  161     //封装的接口(要发送数据)
  162     //返回实际的长度
  163     //这里需要考虑一次发送不完全部数据该怎么办
  164     bool Send(const std::string &data)
  165     int total;
  166     while(total < data.size())
  167 
  168       int ret = send(_sockfd, &data[0] + total,data.size() - total, 0);
  169       if(ret<0)
  170 
  171         perror("send error");
  172         return false;
  173       
  174       total+=ret;
  175     
  176     return true;
  177     
  178 
  179     bool Close()
  180       if (_sockfd != -1) 
  181         close(_sockfd);
  182       
  183     return true;
  184     
  185 
  186 ;                                                                                                                                                                                                                                                                                                                         

select.hpp

    1 #include<iostream>
    2 #include<vector>
    3 #include<time.h>                                                                                                                                                                                                                                                                                                                                                                                                                                
    4 #include<sys/select.h>
    5 #include"classtcp.hpp"
    6 
    7 //使用select多路转接模型,为了方便使
    8 //用可以先封装select相关函数,让Tcp
    9 //客户端仅仅调用接口完成多路转接模型
   10 
   11 class Select
   12 
   13 public:
   		Select():_max_fd(-1)
			FD_ZERO(&_rfds);
		
E> 14   bool Addfd(TCPSocket &sock)
   15     int fd=sock.GetFd();//获取当前tcp描述符
   16     FD_SET(fd,&_rfds);//加入到读的集合,第二个参数是地址
   17     _max_fd=_max_fd > fd ? _max_fd : fd;//更新最大描述符,因为要遍历需要最大范围
   18     return true;
   19   
   20 
   21 
E> 22   bool Delete(TCPSocket &sock)
   23     int fd=sock.GetFd();//获取当前tcp描述符
   24     FD_CLR(fd,&_rfds);//删除当前描述符
   25     //删除了一个描述符就需要更新最大描述符
   26     //从最大到最小遍历加快效率
   27     for(int i=_max_fd;i>=0;--i)
   28       if(FD_ISSET(i,&_rfds))
   29         _max_fd=i;//if i存在就是最大的
   30         break;
   31       
   32     
   33     return true;
   34   
   35 
   36 
   37   //wait接口用于对集合中的所有描述符进行监控
   38   //并返回就绪的描述符,通过输出型参数来获取内容
E> 39   bool Wait(std::vector<TCPSocket> *v)
   40     //int select(最大描述符+1,的三种集合地址(不要则置空),设置超时结构体);
   41     struct timeval tv;
   42     tv.tv_sec= 9;
   43     tv.tv_usec=0;
E> 44     fd_set tmp=_rfds;//每次都要拿当前对象的集合的拷贝(每次会修改这个)
   45     int ret=select(_max_fd+1,&tmp,NULL,NULL,&tv);
   46     if(ret<0)//出错返回-1
E> 47       perror("select error");
   48       return false;
   49 
   50     else if(ret==0)
   51       v->clear();//超时返回0
   52       return true;
   53     
   54     //返回值大于0,返回就绪的描述符个数
   55     //知道个数但不知道是哪几个,所以需要遍历tmp集合中还有谁存在,因为
   56     //没有就绪的的已经自动删除了
   57     for(int i=0;i<=_max_fd;++i)
   58       //拿到了就绪的描述符,此时需要加上TCP套接字来加入到就绪vector
   59       //因为在监控加入的时候只是把TCP套接字的描述符加入了用来监控,
   60       //所以返回出去要加上
   61       if(FD_ISSET(i,&tmp))
E> 62         TCPSocket sock;
   63         sock.SetFd(i);
   64         v->push_back(sock);
   65       
   66     
   67     return true;
   68   
   69 
   70 private:
E> 71   fd_set _rfds;
   72   //需要一个描述符集合来监控,每次都要拿拷贝过去监听,
   73   //因为这个集合每次都会去掉没有就绪的
   74   int _max_fd;//集合中最大描述符,方便select接口使用
   75 
   76 
   77 ;

epoll.hpp

E>  1 #include <iostream>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    2 #include <vector>
    3 #include <cstdlib>
    4 #include <sys/epoll.h>
    5 #include "classtcp.hpp"
    6 
    7 class Epoll
    8     public:
    9       //每当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成
   10       //员与epoll的使用方式密切相关
   11       //一个是红黑树的根节点用来保存需要监控的事件
   12       //一个是双向链表存放着将要通过epoll_wait返回给用户的满足条件的事件
   13         Epoll():_epfd(-1)
E> 14             _epfd = epoll_create(1);//大于0即可,linux2.6后被忽略
   15             if (_epfd < 0) 
E> 16                 perror("epoll_create error");
E> 17                 exit(-1);
   18             
   19         
   20 
   21 
   22         //描述符是以事件结构体形式放入内核监控
   23         //也就是放入红黑树
   24         //epoll_ctl就是事件注册函数
E> 25         bool Addfd(TCPSocket &sock) 
   26             //epoll_ctl(epoll句柄,类型,描述符,

以上是关于TCP--IO多路转接模型的主要内容,如果未能解决你的问题,请参考以下文章

[Linux] 典型IO模型与多路转接IO模型

[Linux] 典型IO模型与多路转接IO模型

TCP客户端--IO多路转接模型

五种高阶IO模型以及多路转接技术(selectpoll和epoll)及其代码验证

五种高阶IO模型以及多路转接技术(selectpoll和epoll)及其代码验证

五种高阶IO模型以及多路转接技术(selectpoll和epoll)及其代码验证