• 高级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:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. fd_set rfds;
    9. FD_ZERO(&rfds);//初始化集合
    10. while (1)
    11. {
    12. struct timeval tv;
    13. tv.tv_usec = 0;
    14. tv.tv_sec = 3;
    15. FD_SET(0,&rfds);//因为select会修改rfds集合,因此每次要重新添加描述符到集合中
    16. int max_fd = 0;
    17. //描述符的监控会做一件事情:在返回前,将没有就绪的描述符从集合中移除
    18. int ret = select(max_fd + 1,&rfds,NULL,NULL,&tv);
    19. if(ret < 0)
    20. {
    21. perror("select error");
    22. usleep(1000);
    23. continue;
    24. }
    25. else if(ret == 0)
    26. {
    27. printf("wait timeout\n");
    28. usleep(1000);
    29. continue;
    30. }
    31. for(int i = 0;i <= max_fd;i++)//因为没有把所有的描述符保存起来,所以从0到当前最大的描述符逐个判断
    32. {
    33. if(FD_ISSET(i,&rfds))//如果i这个描述符在集合中,就表示就绪了事件
    34. {
    35. char buf[1024] = {0};
    36. int res = read(i,buf,1023);
    37. if(res <= 0)
    38. {
    39. perror("recv error");
    40. FD_CLR(i,&rfds);
    41. return -1;
    42. }
    43. printf("%d 描述符就绪了事件,读取数据:%s\n",i,buf);
    44. }
    45. }
    46. }
    47. return 0;
    48. }
    1. select1:select1.c
    2. gcc $^ -o $@

    结果如下: 

    例2:

     select.hpp:

    1. #include "tcp_socket.hpp"
    2. #include
    3. #include
    4. #include
    5. class Select
    6. {
    7. private:
    8. fd_set _rfds;//可读事件的描述集合的备份,每次监控都是从这个集合复制一份出来进行监控(select会修改集合)
    9. int _max_fd;
    10. public:
    11. Select():_max_fd(-1)
    12. {
    13. //针对成员变量的初始化
    14. FD_ZERO(&_rfds);
    15. }
    16. //将sock中的描述符fd,添加到rfds可读事件的描述符集合中
    17. bool Add(TcpSocket &sock)
    18. {
    19. int fd = sock.GetFd();
    20. FD_SET(fd,&_rfds);//将描述符添加到监控集合中
    21. _max_fd = _max_fd >fd ?_max_fd:fd;//重新设置最大的描述符
    22. return true;
    23. }
    24. //将sock中的描述符fd,从rfds可读事件的描述符集合中移除
    25. bool Del(TcpSocket &sock)
    26. {
    27. int fd = sock.GetFd();
    28. FD_CLR(fd,&_rfds);//从当前的监控集合中移除指定的描述符
    29. //如果当前移除的刚好就是最大的描述符,这时候从最大描述符开始向前判断哪个描述符还在集合中,第一个就是最大的
    30. for(int i = _max_fd;i >= 0;i--)
    31. {
    32. if(FD_ISSET(i,&_rfds))
    33. {
    34. _max_fd = i;
    35. break;
    36. }
    37. }
    38. return 0;
    39. }
    40. //开始监控,返回就绪的描述符的数组
    41. bool Wait(std::vector *arry,int timeout = 3000)
    42. {
    43. struct timeval tv;
    44. tv.tv_sec = timeout / 1000;
    45. tv.tv_usec = (timeout % 1000) * 1000;
    46. fd_set tmp = _rfds;//使用临时集合进行监控,因为select会修改监控的集合
    47. int ret = select(_max_fd +1,&tmp,NULL,NULL,&tv);
    48. if(ret < 0)
    49. {
    50. perror("select error");
    51. return false;
    52. }
    53. else if(ret ==0)
    54. {
    55. printf("select timeout!\n");
    56. return false;
    57. }
    58. for(int i=0;i <= _max_fd;i++)
    59. {
    60. if(FD_ISSET(i,&tmp))
    61. {
    62. TcpSocket sock;
    63. sock.SetFd(i);
    64. arry->push_back(sock);
    65. }
    66. }
    67. return true;
    68. }
    69. };

     tcp_socket.hpp:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #define MAX_LISTEN 5
    9. #define CHECK_RES(q) if((q)==false) { return -1;}
    10. class TcpSocket{
    11. private:
    12. int _sockfd;
    13. public:
    14. TcpSocket():_sockfd(-1){}
    15. int GetFd()
    16. {
    17. return _sockfd;
    18. }
    19. void SetFd(int fd)
    20. {
    21. _sockfd = fd;
    22. }
    23. //创建套接字
    24. bool Socket()
    25. {
    26. _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    27. if(_sockfd < 0)
    28. {
    29. perror("socket error");
    30. return false;
    31. }
    32. return true;
    33. }
    34. //绑定地址信息
    35. bool Bind(const std::string &ip,uint16_t port)
    36. {
    37. struct sockaddr_in addr;//先定义一个ipv4的地址结构
    38. addr.sin_family = AF_INET;
    39. addr.sin_port = htons(port);
    40. addr.sin_addr.s_addr = inet_addr(ip.c_str());
    41. socklen_t len = sizeof(struct sockaddr_in);
    42. int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
    43. if(ret<0)
    44. {
    45. perror("bind error");
    46. return false;
    47. }
    48. return true;
    49. }
    50. //向服务器发起连接
    51. bool Connect(const std::string &ip,uint16_t port)
    52. {
    53. struct sockaddr_in addr;
    54. addr.sin_family = AF_INET;
    55. addr.sin_port = htons(port);
    56. addr.sin_addr.s_addr = inet_addr(ip.c_str());
    57. socklen_t len = sizeof(struct sockaddr_in);
    58. int ret = connect(_sockfd,(struct sockaddr*)&addr,len);
    59. if(ret < 0)
    60. {
    61. perror("connect error");
    62. return false;
    63. }
    64. return true;
    65. }
    66. //服务器开始监听
    67. bool Listen(int backlog = MAX_LISTEN)
    68. {
    69. int ret = listen(_sockfd,backlog);
    70. if(ret < 0)
    71. {
    72. perror("listen error");
    73. return false;
    74. }
    75. return true;
    76. }
    77. //获取新建连接
    78. bool Accept(TcpSocket *sock,std::string *ip=NULL,uint16_t *port=NULL)
    79. {
    80. struct sockaddr_in addr;
    81. socklen_t len = sizeof(struct sockaddr_in);
    82. int newfd = accept(_sockfd,(struct sockaddr*)&addr,&len);
    83. if(newfd<0)
    84. {
    85. perror("accept error");
    86. return false;
    87. }
    88. sock->_sockfd = newfd;
    89. if(ip != NULL)
    90. {
    91. *ip = inet_ntoa(addr.sin_addr);
    92. }
    93. if(port != NULL)
    94. {
    95. *port = ntohs(addr.sin_port);
    96. }
    97. return true;
    98. }
    99. //接受数据
    100. bool Recv(std::string *body)
    101. {
    102. char tmp[4096] = {0};
    103. int ret = recv(_sockfd,tmp,4096,0);
    104. if(ret < 0)
    105. {
    106. perror("recv error");
    107. return false;
    108. }
    109. else if(ret == 0)
    110. {
    111. std::cout<<"peer shutdown!\n";
    112. return false;
    113. }
    114. body->assign(tmp,ret);//从tmp中截取ret长度大小的数据
    115. return true;
    116. }
    117. //发送数据
    118. bool Send(const std::string &body)
    119. {
    120. int ret;
    121. ret = send(_sockfd,body.c_str(),body.size(),0);
    122. if(ret < 0)
    123. {
    124. perror("send error");
    125. return false;
    126. }
    127. return true;
    128. }
    129. //关闭套接字
    130. bool Close()
    131. {
    132. if(_sockfd!= -1)
    133. {
    134. close(_sockfd);
    135. }
    136. return true;
    137. }
    138. };

    tcp_srv.cpp:

    1. //#include "tcp_socket.hpp"
    2. #include "select.hpp"
    3. int main()
    4. {
    5. TcpSocket lst_sock;
    6. //创建套接字
    7. CHECK_RES(lst_sock.Socket());
    8. //绑定地址信息
    9. CHECK_RES(lst_sock.Bind("0.0.0.0",20000));
    10. //开始监听
    11. CHECK_RES(lst_sock.Listen());
    12. Select s;
    13. s.Add(lst_sock);//将监听套接字添加到集合中
    14. while(1)
    15. {
    16. std::vector arry;
    17. bool ret = s.Wait(&arry);
    18. if(ret == false)
    19. {
    20. usleep(1000);
    21. continue;
    22. }
    23. for(TcpSocket &a:arry)
    24. {
    25. //array里边就是就绪的描述符,一开始只有一个监听套接字,就绪的肯定就是监听套接字代表有新连接
    26. if(a.GetFd()==lst_sock.GetFd())//如果就绪的套接字描述符与监听套接字描述符一样,就表示有新建连接
    27. {
    28. TcpSocket conn_sock;
    29. std::string cliip;
    30. uint16_t cliport;
    31. bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);
    32. if(ret < 0)
    33. {
    34. continue;
    35. }
    36. std::cout<<"new connect:"<":"<
    37. s.Add(conn_sock);//将新建的套接字也添加监控
    38. }
    39. else
    40. {
    41. std::string buf;
    42. ret = a.Recv(&buf);
    43. if(ret == false)
    44. {
    45. s.Del(a);//出错了则移除监控
    46. a.Close();
    47. continue;
    48. }
    49. std::cout<<"client say:"<
    50. std::cout<<"server say:";
    51. fflush(stdout);
    52. std::cin>>buf;
    53. ret = a.Send(buf);
    54. if(ret == false)
    55. {
    56. s.Del(a);
    57. a.Close();
    58. continue;
    59. }
    60. }
    61. }
    62. }
    63. //关闭套接字
    64. lst_sock.Close();
    65. return 0;
    66. }

    tcp_cli.cpp:

    1. #include "tcp_socket.hpp"
    2. int main(int argc,char* argv[])
    3. {
    4. if(argc!= 3)
    5. {
    6. std::cout<<"please input server address!\n";
    7. std::cout<<"USsage:./tcp_cli 192.168.164.128 20000\n";
    8. return -1;
    9. }
    10. std::string srv_ip = argv[1];
    11. uint16_t srv_port = std::stoi(argv[2]);
    12. TcpSocket cli_sock;
    13. //创建套接字
    14. CHECK_RES(cli_sock.Socket());
    15. //绑定地址信息(客户端不推荐)
    16. //向服务器发起连接
    17. CHECK_RES(cli_sock.Connect(srv_ip,srv_port));
    18. while(1)
    19. {
    20. //与服务器通信
    21. std::string buf;
    22. std::cout<<"client say:";
    23. fflush(stdout);
    24. std::cin>>buf;
    25. bool ret = cli_sock.Send(buf);
    26. if(ret == false)
    27. {
    28. cli_sock.Close();
    29. return -1;
    30. }
    31. buf.clear();
    32. ret = cli_sock.Recv(&buf);
    33. if(ret == false)
    34. {
    35. cli_sock.Close();
    36. return -1;
    37. }
    38. std::cout<<"server say:"<
    39. }
    40. //关闭套接字
    41. cli_sock.Close();
    42. return 0;
    43. }

     makefile:

    1. all:tcp_cli tcp_srv select1
    2. tcp_cli:tcp_cli.cpp
    3. g++ -std=c++11 $^ -o $@
    4. tcp_srv:tcp_srv.cpp
    5. g++ -std=c++11 $^ -o $@
    6. select1:select1.c
    7. gcc $^ -o $@

     测试结果:

    服务器端:

     客户端1:

    客户端2:

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

     3.poll

    poll.c:

    1. #include
    2. #include
    3. #include
    4. #include
    5. int main()
    6. {
    7. struct pollfd pfds[10];
    8. int poll_count = 0;
    9. pfds[poll_count].fd = 0;//要监控的描述符是标准输入
    10. pfds[poll_count].events = POLLIN;//针对标准输入要监控的是可读事件
    11. poll_count++;
    12. while(1)
    13. {
    14. int ret = poll(pfds,poll_count,3000);
    15. if(ret < 0)
    16. {
    17. perror("poll error");
    18. usleep(1000);
    19. continue;
    20. }
    21. else if(ret == 0)
    22. {
    23. printf("poll timeout!\n");
    24. usleep(1000);
    25. continue;
    26. }
    27. for(int i=0;i < poll_count;i++)
    28. {
    29. if(pfds[i].revents & POLLIN)
    30. {
    31. //pfds[i].fd就绪了可读事件
    32. char buf[1024] = {0};
    33. read(pfds[i].fd,buf,1023);
    34. printf("%d 描述符就绪,读取数据:%s\n",pfds[i].fd,buf);
    35. }
    36. else if(pfds[i].revents & POLLOUT)
    37. {
    38. //就绪的是可写事件
    39. }
    40. }
    41. }
    42. return 0;
    43. }

    makefile:

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

    结果:

     4.epoll

    tcp_socket.hpp:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #define MAX_LISTEN 5
    10. #define CHECK_RES(q) if((q)==false) { return -1;}
    11. class TcpSocket{
    12. private:
    13. int _sockfd;
    14. public:
    15. TcpSocket():_sockfd(-1){}
    16. int GetFd()
    17. {
    18. return _sockfd;
    19. }
    20. void SetFd(int fd)
    21. {
    22. _sockfd = fd;
    23. }
    24. void SetNonBlock()
    25. {
    26. int flag = fcntl(_sockfd,F_GETFL,0);
    27. fcntl(_sockfd,F_SETFL,flag | O_NONBLOCK);//在原有属性基础上新增非阻塞属性
    28. }
    29. //创建套接字
    30. bool Socket()
    31. {
    32. _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    33. if(_sockfd < 0)
    34. {
    35. perror("socket error");
    36. return false;
    37. }
    38. return true;
    39. }
    40. //绑定地址信息
    41. bool Bind(const std::string &ip,uint16_t port)
    42. {
    43. struct sockaddr_in addr;//先定义一个ipv4的地址结构
    44. addr.sin_family = AF_INET;
    45. addr.sin_port = htons(port);
    46. addr.sin_addr.s_addr = inet_addr(ip.c_str());
    47. socklen_t len = sizeof(struct sockaddr_in);
    48. int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
    49. if(ret<0)
    50. {
    51. perror("bind error");
    52. return false;
    53. }
    54. return true;
    55. }
    56. //向服务器发起连接
    57. bool Connect(const std::string &ip,uint16_t port)
    58. {
    59. struct sockaddr_in addr;
    60. addr.sin_family = AF_INET;
    61. addr.sin_port = htons(port);
    62. addr.sin_addr.s_addr = inet_addr(ip.c_str());
    63. socklen_t len = sizeof(struct sockaddr_in);
    64. int ret = connect(_sockfd,(struct sockaddr*)&addr,len);
    65. if(ret < 0)
    66. {
    67. perror("connect error");
    68. return false;
    69. }
    70. return true;
    71. }
    72. //服务器开始监听
    73. bool Listen(int backlog = MAX_LISTEN)
    74. {
    75. int ret = listen(_sockfd,backlog);
    76. if(ret < 0)
    77. {
    78. perror("listen error");
    79. return false;
    80. }
    81. return true;
    82. }
    83. //获取新建连接
    84. bool Accept(TcpSocket *sock,std::string *ip=NULL,uint16_t *port=NULL)
    85. {
    86. struct sockaddr_in addr;
    87. socklen_t len = sizeof(struct sockaddr_in);
    88. int newfd = accept(_sockfd,(struct sockaddr*)&addr,&len);
    89. if(newfd<0)
    90. {
    91. perror("accept error");
    92. return false;
    93. }
    94. sock->_sockfd = newfd;
    95. if(ip != NULL)
    96. {
    97. *ip = inet_ntoa(addr.sin_addr);
    98. }
    99. if(port != NULL)
    100. {
    101. *port = ntohs(addr.sin_port);
    102. }
    103. return true;
    104. }
    105. //接收数据
    106. bool Recv(std::string *body)
    107. {
    108. char tmp[4096] = {0};
    109. int ret = recv(_sockfd,tmp,4096,0);
    110. if(ret < 0)
    111. {
    112. perror("recv error");
    113. return false;
    114. }
    115. else if(ret == 0)
    116. {
    117. std::cout<<"peer shutdown!\n";
    118. return false;
    119. }
    120. body->assign(tmp,ret);//从tmp中截取ret长度大小的数据
    121. return true;
    122. }
    123. //接受数据
    124. /*
    125. bool Recv(std::string *body)
    126. {
    127. while(1)
    128. {
    129. char tmp[6] = {0};
    130. int ret = recv(_sockfd,tmp,5,0);
    131. if(ret < 0)
    132. {
    133. if(errno == EAGAIN)//EAGAIN这个错误表示当前缓冲区没有数据了,资源还没有准备好
    134. {
    135. return true;
    136. }
    137. perror("recv error");
    138. return false;
    139. }
    140. else if(ret == 0)
    141. {
    142. std::cout<<"peer shutdown!\n";
    143. return false;
    144. }
    145. *body += tmp;
    146. }
    147. return true;
    148. }
    149. */
    150. //发送数据
    151. bool Send(const std::string &body)
    152. {
    153. int ret;
    154. ret = send(_sockfd,body.c_str(),body.size(),0);
    155. if(ret < 0)
    156. {
    157. perror("send error");
    158. return false;
    159. }
    160. return true;
    161. }
    162. //关闭套接字
    163. bool Close()
    164. {
    165. if(_sockfd!= -1)
    166. {
    167. close(_sockfd);
    168. }
    169. return true;
    170. }
    171. };

    epoll.hpp:

    1. #include "tcp_socket.hpp"
    2. #include
    3. #include
    4. #include
    5. #include
    6. #define EPOLL_MAX 100
    7. class Epoll
    8. {
    9. private:
    10. int _epfd;//epoll的操作句柄
    11. public:
    12. Epoll():_epfd(-1)
    13. {
    14. //针对成员变量的初始化
    15. _epfd = epoll_create(1);
    16. if(_epfd < 0)
    17. {
    18. perror("epoll error");
    19. exit(0);
    20. }
    21. }
    22. bool Add(TcpSocket &sock)
    23. {
    24. struct epoll_event ev;
    25. ev.events = EPOLLIN;
    26. ev.data.fd = sock.GetFd();
    27. int ret = epoll_ctl(_epfd,EPOLL_CTL_ADD,sock.GetFd(),&ev);
    28. if(ret < 0)
    29. {
    30. perror("epoll_ctl error");
    31. return false;
    32. }
    33. return true;
    34. }
    35. bool Del(TcpSocket &sock)
    36. {
    37. int ret = epoll_ctl(_epfd,EPOLL_CTL_DEL,sock.GetFd(),NULL);
    38. if(ret < 0)
    39. {
    40. perror("epoll_ctl error");
    41. return false;
    42. }
    43. return true;
    44. }
    45. bool Wait(std::vector *arry,int timeout = 3000)
    46. {
    47. struct epoll_event evs[EPOLL_MAX];
    48. int ret = epoll_wait(_epfd,evs,EPOLL_MAX,timeout);
    49. if(ret < 0)
    50. {
    51. perror("epoll_wait error");
    52. return false;
    53. }
    54. else if(ret == 0)
    55. {
    56. printf("epoll timeout!\n");
    57. return false;
    58. }
    59. for(int i = 0;i < ret;i++)
    60. {
    61. if(evs[i].events & EPOLLIN)//目前只管可读事件
    62. {
    63. TcpSocket sock;
    64. sock.SetFd(evs[i].data.fd);
    65. arry->push_back(sock);
    66. }
    67. }
    68. return true;
    69. }
    70. };

    tcp_srv.cpp:

    1. /#include "tcp_socket.hpp"
    2. //#include "select.hpp"
    3. #include "epoll.hpp"
    4. int main()
    5. {
    6. TcpSocket lst_sock;
    7. //创建套接字
    8. CHECK_RES(lst_sock.Socket());
    9. //lst_sock.SetNonBlock();
    10. //绑定地址信息
    11. CHECK_RES(lst_sock.Bind("0.0.0.0",20000));
    12. //开始监听
    13. CHECK_RES(lst_sock.Listen());
    14. //Select s;
    15. Epoll s;
    16. s.Add(lst_sock);//将监听套接字添加到集合中
    17. while(1)
    18. {
    19. std::vector arry;
    20. bool ret = s.Wait(&arry);
    21. if(ret == false)
    22. {
    23. usleep(1000);
    24. continue;
    25. }
    26. for(TcpSocket &a:arry)
    27. {
    28. //array里边就是就绪的描述符,一开始只有一个监听套接字,就绪的肯定就是监听套接字代表有新连接
    29. if(a.GetFd()==lst_sock.GetFd())//如果就绪的套接字描述符与监听套接字描述符一样,就表示有新建连接
    30. {
    31. TcpSocket conn_sock;
    32. std::string cliip;
    33. uint16_t cliport;
    34. bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);
    35. if(ret < 0)
    36. {
    37. continue;
    38. }
    39. std::cout<<"new connect:"<":"<
    40. //conn_sock.SetNonBlock();
    41. s.Add(conn_sock);//将新建的套接字也添加监控
    42. }
    43. else
    44. {
    45. std::string buf;
    46. ret = a.Recv(&buf);
    47. if(ret == false)
    48. {
    49. s.Del(a);//出错了则移除监控
    50. a.Close();
    51. continue;
    52. }
    53. std::cout<<"client say:"<
    54. std::cout<<"server say:";
    55. fflush(stdout);
    56. std::cin>>buf;
    57. ret = a.Send(buf);
    58. if(ret == false)
    59. {
    60. s.Del(a);
    61. a.Close();
    62. continue;
    63. }
    64. }
    65. }
    66. }
    67. //关闭套接字
    68. lst_sock.Close();
    69. return 0;
    70. }

     tcp_cli.cpp:

    1. #include "tcp_socket.hpp"
    2. int main(int argc,char* argv[])
    3. {
    4. if(argc!= 3)
    5. {
    6. std::cout<<"please input server address!\n";
    7. std::cout<<"USsage:./tcp_cli 192.168.164.128 20000\n";
    8. return -1;
    9. }
    10. std::string srv_ip = argv[1];
    11. uint16_t srv_port = std::stoi(argv[2]);
    12. TcpSocket cli_sock;
    13. //创建套接字
    14. CHECK_RES(cli_sock.Socket());
    15. //绑定地址信息(客户端不推荐)
    16. //向服务器发起连接
    17. CHECK_RES(cli_sock.Connect(srv_ip,srv_port));
    18. while(1)
    19. {
    20. //与服务器通信
    21. std::string buf;
    22. std::cout<<"client say:";
    23. fflush(stdout);
    24. std::cin>>buf;
    25. bool ret = cli_sock.Send(buf);
    26. if(ret == false)
    27. {
    28. cli_sock.Close();
    29. return -1;
    30. }
    31. buf.clear();
    32. ret = cli_sock.Recv(&buf);
    33. if(ret == false)
    34. {
    35. cli_sock.Close();
    36. return -1;
    37. }
    38. std::cout<<"server say:"<
    39. }
    40. //关闭套接字
    41. cli_sock.Close();
    42. return 0;
    43. }

    makefile:

    1. all:tcp_cli tcp_srv
    2. tcp_cli:tcp_cli.cpp
    3. g++ -std=c++11 $^ -o $@
    4. tcp_srv:tcp_srv.cpp
    5. 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方法、增加头文件

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #define MAX_LISTEN 5
    10. #define CHECK_RES(q) if((q)==false) { return -1;}
    11. class TcpSocket{
    12. private:
    13. int _sockfd;
    14. public:
    15. TcpSocket():_sockfd(-1){}
    16. int GetFd()
    17. {
    18. return _sockfd;
    19. }
    20. void SetFd(int fd)
    21. {
    22. _sockfd = fd;
    23. }
    24. void SetNonBlock()
    25. {
    26. int flag = fcntl(_sockfd,F_GETFL,0);
    27. fcntl(_sockfd,F_SETFL,flag | O_NONBLOCK);//在原有属性基础上新增非阻塞属性
    28. }
    29. //创建套接字
    30. bool Socket()
    31. {
    32. _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    33. if(_sockfd < 0)
    34. {
    35. perror("socket error");
    36. return false;
    37. }
    38. return true;
    39. }
    40. //绑定地址信息
    41. bool Bind(const std::string &ip,uint16_t port)
    42. {
    43. struct sockaddr_in addr;//先定义一个ipv4的地址结构
    44. addr.sin_family = AF_INET;
    45. addr.sin_port = htons(port);
    46. addr.sin_addr.s_addr = inet_addr(ip.c_str());
    47. socklen_t len = sizeof(struct sockaddr_in);
    48. int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
    49. if(ret<0)
    50. {
    51. perror("bind error");
    52. return false;
    53. }
    54. return true;
    55. }
    56. //向服务器发起连接
    57. bool Connect(const std::string &ip,uint16_t port)
    58. {
    59. struct sockaddr_in addr;
    60. addr.sin_family = AF_INET;
    61. addr.sin_port = htons(port);
    62. addr.sin_addr.s_addr = inet_addr(ip.c_str());
    63. socklen_t len = sizeof(struct sockaddr_in);
    64. int ret = connect(_sockfd,(struct sockaddr*)&addr,len);
    65. if(ret < 0)
    66. {
    67. perror("connect error");
    68. return false;
    69. }
    70. return true;
    71. }
    72. //服务器开始监听
    73. bool Listen(int backlog = MAX_LISTEN)
    74. {
    75. int ret = listen(_sockfd,backlog);
    76. if(ret < 0)
    77. {
    78. perror("listen error");
    79. return false;
    80. }
    81. return true;
    82. }
    83. //获取新建连接
    84. bool Accept(TcpSocket *sock,std::string *ip=NULL,uint16_t *port=NULL)
    85. {
    86. struct sockaddr_in addr;
    87. socklen_t len = sizeof(struct sockaddr_in);
    88. int newfd = accept(_sockfd,(struct sockaddr*)&addr,&len);
    89. if(newfd<0)
    90. {
    91. perror("accept error");
    92. return false;
    93. }
    94. sock->_sockfd = newfd;
    95. if(ip != NULL)
    96. {
    97. *ip = inet_ntoa(addr.sin_addr);
    98. }
    99. if(port != NULL)
    100. {
    101. *port = ntohs(addr.sin_port);
    102. }
    103. return true;
    104. }
    105. //接收数据
    106. bool RecvCli(std::string *body)
    107. {
    108. char tmp[4096] = {0};
    109. int ret = recv(_sockfd,tmp,4096,0);
    110. if(ret < 0)
    111. {
    112. perror("recv error");
    113. return false;
    114. }
    115. else if(ret == 0)
    116. {
    117. std::cout<<"peer shutdown!\n";
    118. return false;
    119. }
    120. body->assign(tmp,ret);//从tmp中截取ret长度大小的数据
    121. return true;
    122. }
    123. //接受数据
    124. bool Recv(std::string *body)
    125. {
    126. while(1)
    127. {
    128. char tmp[6] = {0};
    129. int ret = recv(_sockfd,tmp,5,0);
    130. if(ret < 0)
    131. {
    132. if(errno == EAGAIN)//EAGAIN这个错误表示当前缓冲区没有数据了,资源还没有准备好
    133. {
    134. return true;
    135. }
    136. perror("recv error");
    137. return false;
    138. }
    139. else if(ret == 0)
    140. {
    141. std::cout<<"peer shutdown!\n";
    142. return false;
    143. }
    144. *body += tmp;
    145. }
    146. return true;
    147. }
    148. //发送数据
    149. bool Send(const std::string &body)
    150. {
    151. int ret;
    152. ret = send(_sockfd,body.c_str(),body.size(),0);
    153. if(ret < 0)
    154. {
    155. perror("send error");
    156. return false;
    157. }
    158. return true;
    159. }
    160. //关闭套接字
    161. bool Close()
    162. {
    163. if(_sockfd!= -1)
    164. {
    165. close(_sockfd);
    166. }
    167. return true;
    168. }
    169. };

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

    1. //#include "tcp_socket.hpp"
    2. //#include "select.hpp"
    3. #include "epoll.hpp"
    4. int main()
    5. {
    6. TcpSocket lst_sock;
    7. //创建套接字
    8. CHECK_RES(lst_sock.Socket());
    9. lst_sock.SetNonBlock();
    10. //绑定地址信息
    11. CHECK_RES(lst_sock.Bind("0.0.0.0",20000));
    12. //开始监听
    13. CHECK_RES(lst_sock.Listen());
    14. //Select s;
    15. Epoll s;
    16. s.Add(lst_sock);//将监听套接字添加到集合中
    17. while(1)
    18. {
    19. std::vector arry;
    20. bool ret = s.Wait(&arry);
    21. if(ret == false)
    22. {
    23. usleep(1000);
    24. continue;
    25. }
    26. for(TcpSocket &a:arry)
    27. {
    28. //array里边就是就绪的描述符,一开始只有一个监听套接字,就绪的肯定就是监听套接字代表有新连接
    29. if(a.GetFd()==lst_sock.GetFd())//如果就绪的套接字描述符与监听套接字描述符一样,就表示有新建连接
    30. {
    31. TcpSocket conn_sock;
    32. std::string cliip;
    33. uint16_t cliport;
    34. bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);
    35. if(ret < 0)
    36. {
    37. continue;
    38. }
    39. std::cout<<"new connect:"<":"<
    40. conn_sock.SetNonBlock();
    41. s.Add(conn_sock);//将新建的套接字也添加监控
    42. }
    43. else
    44. {
    45. std::string buf;
    46. ret = a.Recv(&buf);
    47. if(ret == false)
    48. {
    49. s.Del(a);//出错了则移除监控
    50. a.Close();
    51. continue;
    52. }
    53. std::cout<<"client say:"<
    54. std::cout<<"server say:";
    55. fflush(stdout);
    56. std::cin>>buf;
    57. ret = a.Send(buf);
    58. if(ret == false)
    59. {
    60. s.Del(a);
    61. a.Close();
    62. continue;
    63. }
    64. }
    65. }
    66. }
    67. //关闭套接字
    68. lst_sock.Close();
    69. return 0;
    70. }

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

    1. #include "tcp_socket.hpp"
    2. int main(int argc,char* argv[])
    3. {
    4. if(argc!= 3)
    5. {
    6. std::cout<<"please input server address!\n";
    7. std::cout<<"USsage:./tcp_cli 192.168.164.128 20000\n";
    8. return -1;
    9. }
    10. std::string srv_ip = argv[1];
    11. uint16_t srv_port = std::stoi(argv[2]);
    12. TcpSocket cli_sock;
    13. //创建套接字
    14. CHECK_RES(cli_sock.Socket());
    15. //绑定地址信息(客户端不推荐)
    16. //向服务器发起连接
    17. CHECK_RES(cli_sock.Connect(srv_ip,srv_port));
    18. cli_sock.SetNonBlock();
    19. while(1)
    20. {
    21. //与服务器通信
    22. std::string buf;
    23. std::cout<<"client say:";
    24. fflush(stdout);
    25. std::cin>>buf;
    26. bool ret = cli_sock.Send(buf);
    27. if(ret == false)
    28. {
    29. cli_sock.Close();
    30. return -1;
    31. }
    32. buf.clear();
    33. ret = cli_sock.RecvCli(&buf);
    34. if(ret == false)
    35. {
    36. cli_sock.Close();
    37. return -1;
    38. }
    39. std::cout<<"server say:"<
    40. }
    41. //关闭套接字
    42. cli_sock.Close();
    43. return 0;
    44. }

    测试结果:

     客户端1:

     客户端2:

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


  • 相关阅读:
    c语言单元测试构建
    P2607 [ZJOI2008] 骑士 (树形dp
    spring boot中shiro使用自定义注解屏蔽接口鉴权
    算法 旋转数组最小数字-(二分查找+反向双指针)
    CCNA-NAT协议 静态NAT 动态NAT(多对多) PAT(多对一) 内网与外网之间的通信 GNS3实验验证
    高斯判别分析(GDA)公式推导
    centos7安装mysql8
    【Nacos篇】Nacos基本操作及配置
    openEuler快速入门(二)-openEuler命令行基础操作
    rust函数
  • 原文地址:https://blog.csdn.net/weixin_46153828/article/details/126576432