0


【linux】高级IO

1. 五种IO模型

同步IO:

  1. 阻塞式IO (像 read , write ... 都是阻塞IO)
  2. 非阻塞式IO (非阻塞轮询等待)
  3. 信号驱动式IO (信号来了,才知道数据就绪了)
  4. 多路复用/多路转接 (多个读写端共同非阻塞轮询一起等待)

异步IO:

  1. 异步IO:发起IO,但是交给别人完成,完成的时候通知自己读取数据(由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)

注意:

  1. 任何IO过程中, 都包含两个步骤. 第一是等待, 第二是拷贝
  2. 在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少

2. 高级IO重要概念

(一)同步通信 与 异步通信

同步和异步关注的是消息通信机制

同步:在发出一个调用时,没有得到结果之前,该调用就不返回. 但是一旦调用返回,就得到返回值了,即由调用者主动等待这个调用的结果

异步:调用在发出之后,这个调用就直接返回了,所以没有返回结果,即当一个异步过程调用发出后,调用者不会立刻得到结果,而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用

**(二)阻塞 vs 非阻塞 **

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态

阻塞调用是指调用结果返回之前,当前线程会被挂起. 调用线程只有在得到结果之后才会返回

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程

3. 非阻塞IO

fcntl 函数

#include <unistd.h>

#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

函数参数:

fd : 文件描述符

cmd : 可以设置不同值,有不同作用

cmd 设置:

复制一个现有的描述符(cmd=F_DUPFD)

获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)

获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)

文件状态标记:文件打开时的状态标记,如可读,可写,阻塞等

获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)

获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)

代码(非阻塞IO)

  1. void SetNoBlock(int fd)
  2. {
  3. int fl = fcntl(fd, F_GETFL); //得到 fd 中原来的文件状态标记
  4. if (fl < 0)
  5. {
  6. perror("fcntl");
  7. return;
  8. }
  9. fcntl(fd, F_SETFL, fl | O_NONBLOCK); // 既保留原来的文件状态标志,也增加设置了非阻塞
  10. }

** 注意:**

  1. cmd 设置 O_NONBLOCK 代表非阻塞
  2. cmd 设置一个fd成为非阻塞IO时,当 read读取数据为空时,不会阻塞,而是设置 errno 为 EWOULDBLOCK

4. IO多路转接 -- select

**(一)初识select **

系统提供select函数来实现多路复用输入/输出模型

select系统调用是用来让我们的程序监视多个文件描述符的状态变化的

程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变

(二)select函数

select 函数

#include <sys/select.h>

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

参数:

nfd : 需要监视的最大的文件描述符值+1

readfds , writefds , exceptfds :都是输入输出型参数

timeout : 输入输出型参数,表示等待的最长时间,是一个结构体,如果期间没有接收到有数据就绪,时间一到,函数会自动返回

返回值:返回值 > 0,代表有多少数据就绪 ; 返回值 = 0,在设置的等待最长时间内,没有数据就绪 ; 返回值 = -1,在等待过程中,有 IO出现错误

注意:

  1. readfds , writefds , exceptfds 都是输入输出型参数,实际上传入的类似一张位图,以readfds参数举例,传入的位图代表这张位图,所有设置过的位置(1代表被设置,0代表没被设置),都需要判断是否读就绪(读缓冲区是否有数据),传出的位图代表,只有就绪的位置会被设置,输入输出代表含义不一样如果传入设置成NULL,即代表不关心读缓冲区是否有数据,writefds , exceptfds 的参数设置和前面的类似
  2. timeout 输入等待时间,输出代表剩下的时间,如:设置 5 s ,有一个数据就绪,函数就会返回,等待了 2 s , 输出得到的就是 3 s 。 如果设置成 NULL,即阻塞等待,直到有一个数据就绪

timeval 结构体:

提供了一组操作fd_set的接口, 来比较方便的操作位图:

void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位

int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真

void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位

void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位

(三)select 使用

代码 (对连接和读做了处理)

  1. #include"SelectServer.h"
  2. int main()
  3. {
  4. unique_ptr<select_server> str(new select_server());
  5. str->Init();
  6. str->start();
  7. }
  1. #include"server.h"
  2. #include <sys/select.h>
  3. class select_server
  4. {
  5. public:
  6. select_server()
  7. {
  8. for(int i = 0;i < 1024;i++)
  9. {
  10. blank[i] = -1;
  11. }
  12. }
  13. void Init()
  14. {
  15. _listen.start();
  16. }
  17. void Handler(fd_set& set)
  18. {
  19. if (FD_ISSET(blank[0], &set))
  20. {
  21. int lietent_fd = _listen.ListenFd();
  22. sockaddr_in x;
  23. socklen_t len = sizeof(x);
  24. int fd = accept(lietent_fd, (struct sockaddr *)&x, &len);
  25. if (fd < 0)
  26. {
  27. cerr << "accept fail" << endl;
  28. return;
  29. }
  30. else
  31. {
  32. int i = 1;
  33. for (; i < 1024; i++)
  34. {
  35. if (blank[i] == -1)
  36. {
  37. cout << "blank[i] = fd" << endl;
  38. blank[i] = fd;
  39. break;
  40. }
  41. }
  42. if (i == 1024)
  43. {
  44. cout << "over buff" << endl;
  45. close(fd);
  46. }
  47. }
  48. }
  49. for(int i = 1;i < 1024;i++)
  50. {
  51. if(blank[i] == -1)
  52. {
  53. continue;
  54. }
  55. if(FD_ISSET(blank[i],&set))
  56. {
  57. char buff[1024];
  58. int n = read(blank[i],buff,sizeof(buff) - 1);
  59. if(n > 0)
  60. {
  61. buff[n] = 0;
  62. cout << buff << endl;
  63. }
  64. else if(n == 0)
  65. {
  66. cout << "write close" << endl;
  67. close(blank[i]);
  68. blank[i] = -1;
  69. }
  70. else
  71. {
  72. cerr << " read fail " << endl;
  73. }
  74. }
  75. }
  76. }
  77. void start()
  78. {
  79. int max_fd = -1;
  80. int lietent_fd = _listen.ListenFd();
  81. blank[0] = lietent_fd;
  82. fd_set set;
  83. while (true)
  84. {
  85. FD_ZERO(&set);
  86. for(int i = 0;i < 1024;i++)
  87. {
  88. if(blank[i] != -1)
  89. {
  90. max_fd = max(max_fd,blank[i]);
  91. FD_SET(blank[i], &set);
  92. }
  93. }
  94. timeval time({5, 0});
  95. int n = select(max_fd + 1, &set, nullptr, nullptr, &time);
  96. if (n > 0)
  97. {
  98. cout << " get a link ... " << endl;
  99. Handler(set);
  100. }
  101. else if (n == 0)
  102. {
  103. cout << " wait ... ... " << endl;
  104. }
  105. else
  106. {
  107. cerr << "select fail " << endl;
  108. }
  109. }
  110. }
  111. ~select_server()
  112. {}
  113. private:
  114. int blank[1024];
  115. server _listen;
  116. };
  1. #include<iostream>
  2. #include<sys/types.h>
  3. #include<sys/socket.h>
  4. #include<netinet/in.h>
  5. #include<arpa/inet.h>
  6. #include<unistd.h>
  7. #include<signal.h>
  8. #include<memory>
  9. using namespace std;
  10. class server
  11. {
  12. public:
  13. server(const uint16_t port = 8080,const string& ip = "0.0.0.0")
  14. :server_port(port)
  15. ,server_ip(ip)
  16. {}
  17. void start()
  18. {
  19. //构建套接字
  20. server_fd = socket(AF_INET,SOCK_STREAM,0);
  21. if(server_fd < 0)
  22. {
  23. cout << "server scoket fail" << endl;
  24. exit(1);
  25. }
  26. else
  27. {
  28. cout << "socket success" << endl;
  29. }
  30. //绑定端口号
  31. sockaddr_in server;
  32. server.sin_family = AF_INET;
  33. server.sin_port = htons(server_port);
  34. server.sin_addr.s_addr = inet_addr(server_ip.c_str());
  35. socklen_t len = sizeof(server);
  36. int tmp = bind(server_fd,(struct sockaddr*)&server,len);
  37. if(tmp < 0)
  38. {
  39. cout << "server bind fail" << endl;
  40. exit(2);
  41. }
  42. //开始监听
  43. int n = listen(server_fd,10);
  44. if(n < 0)
  45. {
  46. cout << "server listen fail" << endl;
  47. exit(3);
  48. }
  49. }
  50. void run()
  51. {
  52. signal(SIGCHLD,SIG_IGN);
  53. while(true)
  54. {
  55. // 等待客户端
  56. sockaddr_in client;
  57. socklen_t len = sizeof(client);
  58. int cilent_id = accept(server_fd, (struct sockaddr *)&client, &len);
  59. if(cilent_id < 0)
  60. {
  61. cout << "server : accept fail" << endl;
  62. }
  63. cout << "get a new link ...." << endl;
  64. int id = fork();
  65. if (id == 0)
  66. {
  67. close(server_fd);
  68. while (true)
  69. {
  70. // 读取数据
  71. char buff[1024];
  72. int n = read(cilent_id, buff, sizeof(buff));
  73. if (n < 0)
  74. {
  75. cout << "server read fail" << endl;
  76. break;
  77. }
  78. buff[n] = 0;
  79. cout << buff << endl;
  80. // 写入数据
  81. string s = "server say : ";
  82. s += buff;
  83. write(cilent_id, s.c_str(), s.size());
  84. }
  85. exit(0);
  86. }
  87. close(cilent_id);
  88. }
  89. }
  90. int ListenFd()
  91. {
  92. return server_fd;
  93. }
  94. private:
  95. int server_fd;
  96. uint16_t server_port;
  97. string server_ip;
  98. };

(四)select 缺点

  1. 等待的fd是有上限的(最多只能等待1024个)
  2. 输入输出型参数较多,数据拷贝频繁
  3. 输入输出型参数较多,每次都要对关心的数据进行事件重置(重新设置关心的读数据,写数据,或者出错数据)
  4. 用户层使用第三方数组管理用户fd,需要遍历很多次;内核检测fd是否就绪,也需要遍历很多次

5. IO多路转接 -- poll

(一)poll函数

poll 函数

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数:

fds:一个结构体地址,存放fd信息,关心数据信息,是否关心数据就绪信息

nfds:struct pollfd 结构体个数

timeout : 等待时间 (设置成 0 ,是非阻塞等待;设置成 - 1,是阻塞等待)

返回值:

返回值小于0, 表示出错; 返回值等于0, 表示poll函数等待超时; 返回值大于0, 表示poll由于文件描述符就绪而返回

注意:

timeout 参数代表的数据,单位是毫秒

// pollfd结构

struct pollfd {

int fd; /* file descriptor */

short events; /* requested events */

short revents; /* returned events */

};

struct pollfd

{

int fd; //fd信息

short events; /* requested events */

short revents; /* returned events */

};

注意:

这些取值实际上是宏

(二)poll 使用

代码 (对连接和读做了处理)

  1. #include"PollServer.h"
  2. int main()
  3. {
  4. unique_ptr<poll_server> ptr(new poll_server());
  5. ptr->Init();
  6. ptr->start();
  7. return 0;
  8. }
  1. #include"server.h"
  2. #include"poll.h"
  3. class poll_server
  4. {
  5. public:
  6. poll_server()
  7. {
  8. for(int i = 0;i < 1024;i++)
  9. {
  10. polls[i].fd = -1;
  11. }
  12. }
  13. void Init()
  14. {
  15. sv.Init();
  16. }
  17. void Handler()
  18. {
  19. for(int i = 0;i < 1024;i++)
  20. {
  21. int fd = polls[i].fd;
  22. if(fd == -1)
  23. {
  24. continue;
  25. }
  26. else if(fd == sv.ListenFd())
  27. {
  28. if(polls[i].revents & POLL_IN)
  29. {
  30. int client_fd = sv.Accept();
  31. for(int j = 1;j < 1024;j++)
  32. {
  33. if(polls[j].fd == -1)
  34. {
  35. polls[j].events = POLL_IN;;
  36. polls[j].fd = client_fd;
  37. break;
  38. }
  39. }
  40. }
  41. }
  42. else
  43. {
  44. if(polls[i].revents & POLL_IN)
  45. {
  46. //读数据
  47. char buff[1024];
  48. int n = read(fd,buff,sizeof(buff) - 1);
  49. if(n > 0)
  50. {
  51. buff[n] = 0;
  52. cout << buff << endl;
  53. }
  54. else if(n == 0)
  55. {
  56. close(polls[i].fd);
  57. polls[i].fd = -1;
  58. cout << "client read close" << endl;
  59. }
  60. else
  61. {
  62. close(polls[i].fd);
  63. polls[i].fd = -1;
  64. cout << "read fail" << endl;
  65. }
  66. }
  67. }
  68. }
  69. }
  70. void start()
  71. {
  72. int fd = sv.ListenFd();
  73. polls[0].fd = fd;
  74. polls[0].events = POLL_IN;
  75. while (true)
  76. {
  77. int n = poll(polls, 1024, 3000);
  78. if (n > 0)
  79. {
  80. Handler();
  81. }
  82. else if (n == 0)
  83. {
  84. cout << "wait ..." << endl;
  85. }
  86. else
  87. {
  88. cerr << "poll fail" << endl;
  89. }
  90. }
  91. }
  92. ~poll_server()
  93. {}
  94. private:
  95. server sv;
  96. pollfd polls[1024];
  97. };
  1. #include<iostream>
  2. #include<sys/types.h>
  3. #include<sys/socket.h>
  4. #include<netinet/in.h>
  5. #include<arpa/inet.h>
  6. #include<unistd.h>
  7. #include<signal.h>
  8. #include<memory>
  9. using namespace std;
  10. class server
  11. {
  12. public:
  13. server(const uint16_t port = 8080,const string& ip = "0.0.0.0")
  14. :server_port(port)
  15. ,server_ip(ip)
  16. {}
  17. void Init()
  18. {
  19. //构建套接字
  20. server_fd = socket(AF_INET,SOCK_STREAM,0);
  21. if(server_fd < 0)
  22. {
  23. cout << "server scoket fail" << endl;
  24. exit(1);
  25. }
  26. else
  27. {
  28. cout << "socket success" << endl;
  29. }
  30. //绑定端口号
  31. sockaddr_in server;
  32. server.sin_family = AF_INET;
  33. server.sin_port = htons(server_port);
  34. server.sin_addr.s_addr = inet_addr(server_ip.c_str());
  35. socklen_t len = sizeof(server);
  36. int tmp = bind(server_fd,(struct sockaddr*)&server,len);
  37. if(tmp < 0)
  38. {
  39. cout << "server bind fail" << endl;
  40. exit(2);
  41. }
  42. //开始监听
  43. int n = listen(server_fd,10);
  44. if(n < 0)
  45. {
  46. cout << "server listen fail" << endl;
  47. exit(3);
  48. }
  49. }
  50. int Accept()
  51. {
  52. sockaddr_in client;
  53. socklen_t len = sizeof(client);
  54. int cilent_id = accept(server_fd, (struct sockaddr *)&client, &len);
  55. if (cilent_id < 0)
  56. {
  57. cout << "server : accept fail" << endl;
  58. }
  59. else
  60. {
  61. cout << "get a new link ...." << endl;
  62. }
  63. return cilent_id;
  64. }
  65. void run()
  66. {
  67. signal(SIGCHLD,SIG_IGN);
  68. while(true)
  69. {
  70. int cilent_id = Accept();
  71. // 等待客户端
  72. int id = fork();
  73. if (id == 0)
  74. {
  75. close(server_fd);
  76. while (true)
  77. {
  78. // 读取数据
  79. char buff[1024];
  80. int n = read(cilent_id, buff, sizeof(buff));
  81. if (n < 0)
  82. {
  83. cout << "server read fail" << endl;
  84. break;
  85. }
  86. buff[n] = 0;
  87. cout << buff << endl;
  88. // 写入数据
  89. string s = "server say : ";
  90. s += buff;
  91. write(cilent_id, s.c_str(), s.size());
  92. }
  93. exit(0);
  94. }
  95. close(cilent_id);
  96. }
  97. }
  98. int ListenFd()
  99. {
  100. return server_fd;
  101. }
  102. private:
  103. int server_fd;
  104. uint16_t server_port;
  105. string server_ip;
  106. };

(三)poll的缺点

poll中监听的文件描述符数目增多时,和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符. 因此随着监视的描述符数量的增长, 其效率也会线性下降

6. IO多路转接 -- epoll

**(一)epoll初识 **

是为处理大批量等待fd而作了改进的poll

优化了 select 和 poll 的缺点

**(二)epoll的相关系统调用 **

epoll_create 函数

作用

创建一个epoll的模型

原型

int epoll_create(int size);

参数:

size : 填写的值大于0即可

注意:

用完之后, 必须调用close()关闭

epoll_ctl 函数

作用

epoll的事件注册函数,增减关心的数据

原型

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数:

epfd : epoll_create()的返回值(epoll的句柄).

op :表示动作, 用三个宏来表示

fd : 需要监听的fd

event : 告诉内核需要监听什么事

第二个参数的取值:

EPOLL_CTL_ADD :注册新的fd到epfd中

EPOLL_CTL_MOD :修改已经注册的fd的监听事件

EPOLL_CTL_DEL :从epfd中删除一个fd

struct epoll_event结构如图:

events可以是以下几个宏的集合:

EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭)

EPOLLOUT : 表示对应的文件描述符可以写

EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来)

EPOLLERR : 表示对应的文件描述符发生错误

EPOLLHUP : 表示对应的文件描述符被挂断

EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的

EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要

再次把这个socket加入到EPOLL队列里

epoll_wait 函数

作用

收集在epoll监控的事件中已经发送的事件

原型

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

参数:

epfd :epoll_create() 的返回值

events : 是分配好的epoll_event结构体数组

maxevents : 告诉内核这个events有多大

timeout :超时时间 (毫秒,0会立即返回,-1是永久阻塞)

返回值:

函数调用成功,返回对应IO上已准备好的文件描述符数目;返回0表示已超时;返回小于0表示函数失败

注意:

epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存).

(三)epoll 工作原理

实际上,创建epoll,等于创建一个文件,文件里面储存了上述信息

(四)epoll 使用

代码

  1. #include"EpollServer.h"
  2. int main()
  3. {
  4. unique_ptr<epoll_server> ptr(new epoll_server());
  5. ptr->Init();
  6. ptr->start();
  7. return 0;
  8. }
  1. #include"server.h"
  2. #include <sys/epoll.h>
  3. class epoll_server
  4. {
  5. public:
  6. epoll_server()
  7. {
  8. for(int i = 0;i < 1024;i++)
  9. {
  10. events[i].data.fd = -1;
  11. }
  12. }
  13. void Init()
  14. {
  15. sv.Init();
  16. epoll_fd = epoll_create(2);
  17. if( epoll_fd < 0)
  18. {
  19. cerr << "epoll_create fail" << endl;
  20. exit(1);
  21. }
  22. cout << "epoll_fd :" << epoll_fd << endl;
  23. }
  24. void EpollCtl(int op,int fd,epoll_event* event)
  25. {
  26. if(op == EPOLL_CTL_DEL)
  27. {
  28. //删除最后一个传参是 nullptr,为了更方便显示细节,就不合并了
  29. int n = epoll_ctl(epoll_fd,op,fd,nullptr);
  30. if(n < 0)
  31. {
  32. cerr << "epoll_ctl delete fail" << endl;
  33. }
  34. }
  35. else
  36. {
  37. int n = epoll_ctl(epoll_fd,op,fd,event);
  38. if(n < 0)
  39. {
  40. cerr << "epoll_ctl add fail" << endl;
  41. }
  42. }
  43. }
  44. void Handler(int n)
  45. {
  46. for (int i = 0; i < n; i++)
  47. {
  48. int fd = events[i].data.fd;
  49. cout << "fd: "<< fd << endl;
  50. if (fd == sv.ListenFd() && events[i].events & EPOLLIN)
  51. {
  52. int client_fd = sv.Accept();
  53. epoll_event event;
  54. event.events = EPOLLIN;
  55. event.data.fd = client_fd;
  56. cout << "client_fd:" << client_fd << endl;
  57. EpollCtl(EPOLL_CTL_ADD,client_fd, &event);
  58. }
  59. else
  60. {
  61. if(events[i].events & EPOLLIN)
  62. {
  63. //写端
  64. char buff[1024];
  65. int n = read(fd,buff,sizeof(buff) - 1);
  66. if(n > 0)
  67. {
  68. buff[n] = 0;
  69. cout << buff << endl;
  70. }
  71. else if(n == 0)
  72. {
  73. EpollCtl(EPOLL_CTL_DEL,fd, nullptr);
  74. close(fd);
  75. cerr << "write close" << endl;
  76. }
  77. else
  78. {
  79. EpollCtl(EPOLL_CTL_DEL,fd, nullptr);
  80. close(fd);
  81. cerr << "read fail" << endl;
  82. }
  83. }
  84. }
  85. }
  86. }
  87. void start()
  88. {
  89. int fd = sv.ListenFd();
  90. epoll_event event;
  91. event.events = EPOLLIN;
  92. event.data.fd = fd;
  93. EpollCtl(EPOLL_CTL_ADD,fd,&event);
  94. while (true)
  95. {
  96. int n = epoll_wait(epoll_fd, events, 1024, 3000);
  97. if (n > 0)
  98. {
  99. Handler(n);
  100. }
  101. else if (n == 0)
  102. {
  103. cout << "wait ..." << endl;
  104. }
  105. else
  106. {
  107. cerr << "epoll_wait fail" << endl;
  108. }
  109. }
  110. }
  111. ~epoll_server()
  112. {}
  113. private:
  114. int epoll_fd;
  115. epoll_event events[1024];
  116. server sv;
  117. };
  1. #include<iostream>
  2. #include<sys/types.h>
  3. #include<sys/socket.h>
  4. #include<netinet/in.h>
  5. #include<arpa/inet.h>
  6. #include<unistd.h>
  7. #include<signal.h>
  8. #include<memory>
  9. using namespace std;
  10. class server
  11. {
  12. public:
  13. server(const uint16_t port = 8080,const string& ip = "0.0.0.0")
  14. :server_port(port)
  15. ,server_ip(ip)
  16. {}
  17. void Init()
  18. {
  19. //构建套接字
  20. server_fd = socket(AF_INET,SOCK_STREAM,0);
  21. if(server_fd < 0)
  22. {
  23. cout << "server scoket fail" << endl;
  24. exit(1);
  25. }
  26. else
  27. {
  28. cout << "socket success" << endl;
  29. }
  30. //绑定端口号
  31. sockaddr_in server;
  32. server.sin_family = AF_INET;
  33. server.sin_port = htons(server_port);
  34. server.sin_addr.s_addr = inet_addr(server_ip.c_str());
  35. socklen_t len = sizeof(server);
  36. int tmp = bind(server_fd,(struct sockaddr*)&server,len);
  37. if(tmp < 0)
  38. {
  39. cout << "server bind fail" << endl;
  40. exit(2);
  41. }
  42. //开始监听
  43. int n = listen(server_fd,10);
  44. if(n < 0)
  45. {
  46. cout << "server listen fail" << endl;
  47. exit(3);
  48. }
  49. }
  50. int Accept()
  51. {
  52. sockaddr_in client;
  53. socklen_t len = sizeof(client);
  54. int cilent_id = accept(server_fd, (struct sockaddr *)&client, &len);
  55. if (cilent_id < 0)
  56. {
  57. cout << "server : accept fail" << endl;
  58. }
  59. else
  60. {
  61. cout << "get a new link ...." << endl;
  62. }
  63. return cilent_id;
  64. }
  65. void run()
  66. {
  67. signal(SIGCHLD,SIG_IGN);
  68. while(true)
  69. {
  70. int cilent_id = Accept();
  71. // 等待客户端
  72. int id = fork();
  73. if (id == 0)
  74. {
  75. close(server_fd);
  76. while (true)
  77. {
  78. // 读取数据
  79. char buff[1024];
  80. int n = read(cilent_id, buff, sizeof(buff));
  81. if (n < 0)
  82. {
  83. cout << "server read fail" << endl;
  84. break;
  85. }
  86. buff[n] = 0;
  87. cout << buff << endl;
  88. // 写入数据
  89. string s = "server say : ";
  90. s += buff;
  91. write(cilent_id, s.c_str(), s.size());
  92. }
  93. exit(0);
  94. }
  95. close(cilent_id);
  96. }
  97. }
  98. int ListenFd()
  99. {
  100. return server_fd;
  101. }
  102. private:
  103. int server_fd;
  104. uint16_t server_port;
  105. string server_ip;
  106. };

**(五)epoll的优点(和 select 的缺点对应) **

  1. 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
  2. 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)
  3. 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中,
  4. epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响.
  5. 没有数量限制: 文件描述符数目无上限

7. 水平触发 和 边缘触发

**(一)水平触发Level Triggered 工作模式 **

epoll默认状态下就是LT工作模式

当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分,当发生上面两者情况,下一次调用 epoll_wait 时,事件依然处于就绪中,值得数据全部被处理完,epoll_wait 才不会提醒

如上面的例子, 由于只读了1K数据, 缓冲区中还剩1K数据, 在第二次调用 epoll_wait 时, epoll_wait

举例:

向一个接收缓冲区内发送数据,如果上层没有读,或者没读完,检测 epoll_wait 的时候仍然能检测到,直到这个接收缓冲区内的数据全部读完,就不会接收到提醒

**(二)边缘触发Edge Triggered工作模式 **

如果我们在第1步将socket添加到epoll描述符的时候使用了EPOLLET标志, epoll进入ET工作模式.

当数据发生变化时(即从无到有,从有到多),检测该数据是否会就绪只会提醒一次,如果数据仍然没有读完,那么 epoll_wait 不再提醒,除非下次数据发生变化

举例:

向一个接收缓冲区内发送数据,第一次检测 epoll_wait 是能检测成功的,如果不读,或者只读一部分,后面再次检测 epoll_wait 是收不到提示的,除非再向这个接收缓冲区内发送数据,则下一次 epoll_wait 是能检测成功的

**(三)对比LT和ET **

LT是 epoll 的默认行为

ET看上去效率更高,因为使用 ET 能够减少 epoll 触发的次数. 但是代价是:为了防止数据不完整,必须一次响应就绪,就立刻把所有的数据都处理完

LT 也能提高效率:接收到一次提醒后,也把所有数据都进行处理

要做到上述,就必须使用处理数据使用非阻塞等待,即对 fd 使用 fcntl 函数,设置成O_NONBLOCK

标签: linux 网络

本文转载自: https://blog.csdn.net/2301_79789645/article/details/143880369
版权归原作者 旺旺岁冰冰ovo 所有, 如有侵权,请联系我们删除。

“【linux】高级IO”的评论:

还没有评论