一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

Linux|Centos|Ubuntu|系統進程|Fedora|注冊表|Bios|Solaris|Windows7|Windows10|Windows11|windows server|

服務器之家 - 服務器系統 - Linux - IO多路復用之epoll全面總結(必看篇)

IO多路復用之epoll全面總結(必看篇)

2021-12-13 20:57Linux教程網 Linux

下面小編就為大家帶來一篇IO多路復用之epoll全面總結(必看篇)。小編覺得挺不錯的?,F在就分享給大家。也給大家做個參考。一起跟隨小編過來看看吧

1、基本知識

epoll是在2.6內核中提出的,是之前的select和poll的增強版本。相對于select和poll來說,epoll更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。

2、epoll接口

epoll操作過程需要三個接口,分別如下:

?
1
2
3
4
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

(1) int epoll_create(int size);

創建一個epoll的句柄,size用來告訴內核這個監聽的數目一共有多大。這個參數不同于select()中的第一個參數,給出最大監聽的fd+1的值。需要注意的是,當創建好epoll句柄后,它就是會占用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調用close()關閉,否則可能導致fd被耗盡。

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

epoll的事件注冊函數,它不同與select()是在監聽事件時告訴內核要監聽什么類型的事件epoll的事件注冊函數,它不同與select()是在監聽事件時告訴內核要監聽什么類型的事件,而是在這里先注冊要監聽的事件類型。第一個參數是epoll_create()的返回值,第二個參數表示動作,用三個宏來表示:

epoll_ctl_add:注冊新的fd到epfd中;
epoll_ctl_mod:修改已經注冊的fd的監聽事件;
epoll_ctl_del:從epfd中刪除一個fd;

第三個參數是需要監聽的fd,第四個參數是告訴內核需要監聽什么事,struct epoll_event結構如下:

?
1
2
3
4
struct epoll_event {
 __uint32_t events; /* epoll events */
 epoll_data_t data; /* user data variable */
};

events可以是以下幾個宏的集合:

epollin :表示對應的文件描述符可以讀(包括對端socket正常關閉);

epollout:表示對應的文件描述符可以寫;

epollpri:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);

epollerr:表示對應的文件描述符發生錯誤;

epollhup:表示對應的文件描述符被掛斷;

epollet: 將epoll設為邊緣觸發(edge triggered)模式,這是相對于水平觸發(level triggered)來說的。

epolloneshot:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到epoll隊列里

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

等待事件的產生,類似于select()調用。參數events用來從內核得到事件的集合,maxevents告之內核這個events有多大,這個maxevents的值不能大于創建epoll_create()時的size,參數timeout是超時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函數返回需要處理的事件數目,如返回0表示已超時。

3、工作模式

epoll對文件描述符的操作有兩種模式:lt(level trigger)和et(edge trigger)。lt模式是默認模式,lt模式與et模式的區別如下:

lt模式:當epoll_wait檢測到描述符事件發生并將此事件通知應用程序,應用程序可以不立即處理該事件。下次調用epoll_wait時,會再次響應應用程序并通知此事件。

et模式:當epoll_wait檢測到描述符事件發生并將此事件通知應用程序,應用程序必須立即處理該事件。如果不處理,下次調用epoll_wait時,不會再次響應應用程序并通知此事件。

et模式在很大程度上減少了epoll事件被重復觸發的次數,因此效率要比lt模式高。epoll工作在et模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。

4、測試程序

編寫一個服務器回射程序echo,練習epoll過程。

服務器代碼如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
 
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <sys/types.h>
 
#define ipaddress  "127.0.0.1"
#define port    8787
#define maxsize   1024
#define listenq   5
#define fdsize   1000
#define epollevents 100
 
//函數聲明
//創建套接字并進行綁定
static int socket_bind(const char* ip,int port);
//io多路復用epoll
static void do_epoll(int listenfd);
//事件處理函數
static void
handle_events(int epollfd,struct epoll_event *events,int num,int listenfd,char *buf);
//處理接收到的連接
static void handle_accpet(int epollfd,int listenfd);
//讀處理
static void do_read(int epollfd,int fd,char *buf);
//寫處理
static void do_write(int epollfd,int fd,char *buf);
//添加事件
static void add_event(int epollfd,int fd,int state);
//修改事件
static void modify_event(int epollfd,int fd,int state);
//刪除事件
static void delete_event(int epollfd,int fd,int state);
 
int main(int argc,char *argv[])
{
  int listenfd;
  listenfd = socket_bind(ipaddress,port);
  listen(listenfd,listenq);
  do_epoll(listenfd);
  return 0;
}
 
static int socket_bind(const char* ip,int port)
{
  int listenfd;
  struct sockaddr_in servaddr;
  listenfd = socket(af_inet,sock_stream,0);
  if (listenfd == -1)
  {
    perror("socket error:");
    exit(1);
  }
  bzero(&servaddr,sizeof(servaddr));
  servaddr.sin_family = af_inet;
  inet_pton(af_inet,ip,&servaddr.sin_addr);
  servaddr.sin_port = htons(port);
  if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1)
  {
    perror("bind error: ");
    exit(1);
  }
  return listenfd;
}
 
static void do_epoll(int listenfd)
{
  int epollfd;
  struct epoll_event events[epollevents];
  int ret;
  char buf[maxsize];
  memset(buf,0,maxsize);
  //創建一個描述符
  epollfd = epoll_create(fdsize);
  //添加監聽描述符事件
  add_event(epollfd,listenfd,epollin);
  for ( ; ; )
  {
    //獲取已經準備好的描述符事件
    ret = epoll_wait(epollfd,events,epollevents,-1);
    handle_events(epollfd,events,ret,listenfd,buf);
  }
  close(epollfd);
}
 
static void
handle_events(int epollfd,struct epoll_event *events,int num,int listenfd,char *buf)
{
  int i;
  int fd;
  //進行選好遍歷
  for (i = 0;i < num;i++)
  {
    fd = events[i].data.fd;
    //根據描述符的類型和事件類型進行處理
    if ((fd == listenfd) &&(events[i].events & epollin))
      handle_accpet(epollfd,listenfd);
    else if (events[i].events & epollin)
      do_read(epollfd,fd,buf);
    else if (events[i].events & epollout)
      do_write(epollfd,fd,buf);
  }
}
static void handle_accpet(int epollfd,int listenfd)
{
  int clifd;
  struct sockaddr_in cliaddr;
  socklen_t cliaddrlen;
  clifd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen);
  if (clifd == -1)
    perror("accpet error:");
  else
  {
    printf("accept a new client: %s:%d\n",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
    //添加一個客戶描述符和事件
    add_event(epollfd,clifd,epollin);
  }
}
 
static void do_read(int epollfd,int fd,char *buf)
{
  int nread;
  nread = read(fd,buf,maxsize);
  if (nread == -1)
  {
    perror("read error:");
    close(fd);
    delete_event(epollfd,fd,epollin);
  }
  else if (nread == 0)
  {
    fprintf(stderr,"client close.\n");
    close(fd);
    delete_event(epollfd,fd,epollin);
  }
  else
  {
    printf("read message is : %s",buf);
    //修改描述符對應的事件,由讀改為寫
    modify_event(epollfd,fd,epollout);
  }
}
 
static void do_write(int epollfd,int fd,char *buf)
{
  int nwrite;
  nwrite = write(fd,buf,strlen(buf));
  if (nwrite == -1)
  {
    perror("write error:");
    close(fd);
    delete_event(epollfd,fd,epollout);
  }
  else
    modify_event(epollfd,fd,epollin);
  memset(buf,0,maxsize);
}
 
static void add_event(int epollfd,int fd,int state)
{
  struct epoll_event ev;
  ev.events = state;
  ev.data.fd = fd;
  epoll_ctl(epollfd,epoll_ctl_add,fd,&ev);
}
 
static void delete_event(int epollfd,int fd,int state)
{
  struct epoll_event ev;
  ev.events = state;
  ev.data.fd = fd;
  epoll_ctl(epollfd,epoll_ctl_del,fd,&ev);
}
 
static void modify_event(int epollfd,int fd,int state)
{
  struct epoll_event ev;
  ev.events = state;
  ev.data.fd = fd;
  epoll_ctl(epollfd,epoll_ctl_mod,fd,&ev);
}

客戶端也用epoll實現,控制stdin_fileno、stdout_fileno、和sockfd三個描述符,程序如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
 
#define maxsize   1024
#define ipaddress  "127.0.0.1"
#define serv_port  8787
#define fdsize    1024
#define epollevents 20
 
static void handle_connection(int sockfd);
static void
handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf);
static void do_read(int epollfd,int fd,int sockfd,char *buf);
static void do_read(int epollfd,int fd,int sockfd,char *buf);
static void do_write(int epollfd,int fd,int sockfd,char *buf);
static void add_event(int epollfd,int fd,int state);
static void delete_event(int epollfd,int fd,int state);
static void modify_event(int epollfd,int fd,int state);
 
int main(int argc,char *argv[])
{
  int         sockfd;
  struct sockaddr_in servaddr;
  sockfd = socket(af_inet,sock_stream,0);
  bzero(&servaddr,sizeof(servaddr));
  servaddr.sin_family = af_inet;
  servaddr.sin_port = htons(serv_port);
  inet_pton(af_inet,ipaddress,&servaddr.sin_addr);
  connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
  //處理連接
  handle_connection(sockfd);
  close(sockfd);
  return 0;
}
 
 
static void handle_connection(int sockfd)
{
  int epollfd;
  struct epoll_event events[epollevents];
  char buf[maxsize];
  int ret;
  epollfd = epoll_create(fdsize);
  add_event(epollfd,stdin_fileno,epollin);
  for ( ; ; )
  {
    ret = epoll_wait(epollfd,events,epollevents,-1);
    handle_events(epollfd,events,ret,sockfd,buf);
  }
  close(epollfd);
}
 
static void
handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf)
{
  int fd;
  int i;
  for (i = 0;i < num;i++)
  {
    fd = events[i].data.fd;
    if (events[i].events & epollin)
      do_read(epollfd,fd,sockfd,buf);
    else if (events[i].events & epollout)
      do_write(epollfd,fd,sockfd,buf);
  }
}
 
static void do_read(int epollfd,int fd,int sockfd,char *buf)
{
  int nread;
  nread = read(fd,buf,maxsize);
    if (nread == -1)
  {
    perror("read error:");
    close(fd);
  }
  else if (nread == 0)
  {
    fprintf(stderr,"server close.\n");
    close(fd);
  }
  else
  {
    if (fd == stdin_fileno)
      add_event(epollfd,sockfd,epollout);
    else
    {
      delete_event(epollfd,sockfd,epollin);
      add_event(epollfd,stdout_fileno,epollout);
    }
  }
}
 
static void do_write(int epollfd,int fd,int sockfd,char *buf)
{
  int nwrite;
  nwrite = write(fd,buf,strlen(buf));
  if (nwrite == -1)
  {
    perror("write error:");
    close(fd);
  }
  else
  {
    if (fd == stdout_fileno)
      delete_event(epollfd,fd,epollout);
    else
      modify_event(epollfd,fd,epollin);
  }
  memset(buf,0,maxsize);
}
 
static void add_event(int epollfd,int fd,int state)
{
  struct epoll_event ev;
  ev.events = state;
  ev.data.fd = fd;
  epoll_ctl(epollfd,epoll_ctl_add,fd,&ev);
}
 
static void delete_event(int epollfd,int fd,int state)
{
  struct epoll_event ev;
  ev.events = state;
  ev.data.fd = fd;
  epoll_ctl(epollfd,epoll_ctl_del,fd,&ev);
}
 
static void modify_event(int epollfd,int fd,int state)
{
  struct epoll_event ev;
  ev.events = state;
  ev.data.fd = fd;
  epoll_ctl(epollfd,epoll_ctl_mod,fd,&ev);
}

5、測試結果

IO多路復用之epoll全面總結(必看篇)

IO多路復用之epoll全面總結(必看篇)

IO多路復用之epoll全面總結(必看篇)

以上就是小編為大家帶來的io多路復用之epoll全面總結(必看篇)全部內容了,希望大家多多支持服務器之家~

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 第一次处破女18分钟 | 免费视频大全 | 四虎论坛 | 亚洲AV久久无码精品九九软件 | 91精品久久一区二区三区 | poverty中国老妇人 | 国产丰满美女做爰 | spank日本网站脱裤子打屁股 | 女主被男主做哭失禁高h | 色综合欧美色综合七久久 | 好女孩韩剧免费观看 | 嫩草影院地址一地址二 | 免费视频片在线观看 | 欧美日韩在线观看精品 | 亚洲经典激情春色另类 | 性做久久久久久久 | 国产欧美日韩精品一区二区三区 | 国产精品久久久久久久牛牛 | 日韩欧美三级视频 | 国产精品亚洲片在线不卡 | 国产午夜精品久久理论片小说 | 国内精品福利丝袜视频_速 国内精品91久久久久 | 97久久天天综合色天天综合色hd | 性做久久久久久 | 日本无卡无吗中文免费 | 国产精品不卡高清在线观看 | 精品免费久久久久久影院 | 厨房里摸着乳丰满在线观看 | 男男羞羞视频网站国产 | 免费视频片在线观看大片 | 天作谜案免费完整版在线观看 | 精品国产一二三区在线影院 | 亚洲av欧美在我 | 东京道一本热大交乱 | 国产精品九九久久一区hh | 日本黄色录像视频 | 四虎在线最新地址公告 | 国产夜趣福利第一视频 | 俄罗斯妈妈235 | 超高清欧美同性videos | 欧美成人精品福利在线视频 |