linux epoll用法

epoll 是 linux 特有的 I/O 复用函数。它是把用户关心的文件描述符事件放在内核的一个事件列表中,故而,无须像select和poll一样每次调用都重复传入文件描述符或事件集。但是, epoll 需要一个额外的文件描述符,来唯一标识内核中的这个事件表。这个文件描述符由 epoll_create 函数来创建:

1
2
3
#include <sys/epoll.h>
 
int epoll_create(int size);

  size 参数现在是被忽略的,但是,为了兼容性,需要传入一个大于0的数。

epoll_ctl 函数来操作epoll的内核事件表:

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

  epfd是epoll_create返回的文件描述符,op指定操作类型,有如下三种:

1
2
3
4
/* Valid opcodes ( "op" parameter ) to issue to epoll_ctl().  */
#define EPOLL_CTL_ADD 1 /* Add a file descriptor to the interface.  */
#define EPOLL_CTL_DEL 2 /* Remove a file descriptor from the interface.  */
#define EPOLL_CTL_MOD 3 /* Change file descriptor epoll_event structure.  */

  event 参数指定事件,它是 epoll_event 结构体指针,定义如下:

1
2
3
4
5
6
7
8
9
10
11
typedef union epoll_data {
    void        *ptr;
    int          fd;
    uint32_t     u32;
    uint64_t     u64;
} epoll_data_t;
 
struct epoll_event {
    uint32_t     events;      /* Epoll events */
    epoll_data_t data;        /* User data variable */
};

  events 是成员描述符事件类型。事件类型也定义在 sys/epoll.h 文件中

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
enum EPOLL_EVENTS
  {
    EPOLLIN = 0x001,
#define EPOLLIN EPOLLIN
    EPOLLPRI = 0x002,
#define EPOLLPRI EPOLLPRI
    EPOLLOUT = 0x004,
#define EPOLLOUT EPOLLOUT
    EPOLLRDNORM = 0x040,
#define EPOLLRDNORM EPOLLRDNORM
    EPOLLRDBAND = 0x080,
#define EPOLLRDBAND EPOLLRDBAND
    EPOLLWRNORM = 0x100,
#define EPOLLWRNORM EPOLLWRNORM
    EPOLLWRBAND = 0x200,
#define EPOLLWRBAND EPOLLWRBAND
    EPOLLMSG = 0x400,
#define EPOLLMSG EPOLLMSG
    EPOLLERR = 0x008,
#define EPOLLERR EPOLLERR
    EPOLLHUP = 0x010,
#define EPOLLHUP EPOLLHUP
    EPOLLRDHUP = 0x2000,
#define EPOLLRDHUP EPOLLRDHUP
    EPOLLEXCLUSIVE = 1u << 28,
#define EPOLLEXCLUSIVE EPOLLEXCLUSIVE
    EPOLLWAKEUP = 1u << 29,
#define EPOLLWAKEUP EPOLLWAKEUP
    EPOLLONESHOT = 1u << 30,
#define EPOLLONESHOT EPOLLONESHOT
    EPOLLET = 1u << 31
#define EPOLLET EPOLLET
  };

  data 是 epoll_data_t 联合体类型。可以用fd 表示文件描述符,或者用ptr指针指向更多的用户数据。

epoll 系列系统调用的主要接口是epoll_wait函数,它在一段超时时间内等待一组文件描述符上的事件,定义:

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

  成功时,返回就绪文件描述符的个数,失败返回-1,并设置errno

其中,timeout指定超时时间,单位毫秒。-1表示永远等待直到有文件描述符就绪。

maxevents 指定最多监听多少个事件,它必须大于0

epoll_wait 函数如果检测到时间,就将事件从内核事件表中复制到第二个参数events指向的数组中。这个数组只输出epoll_wait检测到的就绪事件。

 

epoll 对文件描述符的操作有两种模式:LT(level trigger 电平触发)和 ET(edge trigger 边沿触发)。LT是默认的工作模式。在这种模式下,文件描述符会一直被检测到直到应用程序处理它。ET模式下,文件描述符就绪,被通知给应用程序,之后,就不再通知该同一事件了。ET模式降低了同一个epoll时间被重复触发的次数,因此效率较高。

 

EPOLLONESHOT事件

对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写、异常事件,且只触发一次,除非我们使用 epoll_ctl 函数重置该文件描述符上注册的 EPOLLONESHOT 事件。这样就不会用多并发的问题。

 

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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <signal.h>
 
#include <map>
#include <string>
 
using namespace std;
 
#define CLIENTSIZE 5000
#define BUFSIZE 4000
 
 
int createSocket()
{
    struct sockaddr_in servaddr;
    int listenfd = -1;
 
    if (-1 == (listenfd = socket(PF_INET, SOCK_STREAM, 0)))
    {
        fprintf(stderr, "socket: %d, %s\n", errno, strerror(errno));
        exit(1);
    }
 
    int reuseaddr = 1;
    if (-1 == setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)))
    {
        fprintf(stderr, "setsockopt: %d, %s\n", errno, strerror(errno));
        exit(1);
    }
 
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = PF_INET;
    servaddr.sin_port = htons(8008);
    inet_pton(PF_INET, "0.0.0.0", &servaddr.sin_addr);
 
    if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)))
    {
        fprintf(stderr, "bind: %d, %s\n", errno, strerror(errno));
        exit(1);
    }
 
    if (-1 == listen(listenfd, 5))
    {
        fprintf(stderr, "listen: %d, %s\n", errno, strerror(errno));
        exit(1);
    }
 
    return listenfd;
}
 
int setnoblock(int fd)
{
    int oldopt = fcntl(fd, F_GETFL);
    int newopt = oldopt | O_NONBLOCK;
    fcntl(fd, F_SETFL, newopt);
    return oldopt;
}
 
void ErrExit(const char* reason)
{
    fprintf(stderr, "%s: %d, %s\n", reason, errno, strerror(errno));
    exit(1);
}
 
void addsig(int sig, void (*handler)(int))
{
    int olderrno = errno;
    struct sigaction ac;
 
    memset(&ac, 0, sizeof(ac));
    ac.sa_handler = handler;
    ac.sa_flags |= SA_RESTART;
    sigfillset(&ac.sa_mask);
    if (-1 == sigaction(sig, &ac, NULL))
    {
        ErrExit("sigaction");
    }
    errno = olderrno;
}
 
void addfd(int epfd, int fd)
{
    struct epoll_event ev;
 
    ev.events = EPOLLIN | EPOLLET | EPOLLERR;
    ev.data.fd = fd;
    if (-1 == epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev))
    {
        ErrExit("epoll_ctl");
    }
    setnoblock(fd);
}
 
void delfd(int epfd, int fd)
{
    struct epoll_event ev;
 
    ev.data.fd = fd;
    if (-1 == epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev))
    {
        ErrExit("epoll_ctl");
    }
}
 
int main(int argc, char const *argv[])
{
    int listenfd = createSocket();
    int epfd = -1;
    map<int, string> mapdata;
 
    if (-1 == (epfd = epoll_create(CLIENTSIZE)))
    {
        ErrExit("epoll_create");
    }
 
    struct epoll_event evs[CLIENTSIZE];
    addfd(epfd, listenfd);
 
    while (1)
    {
        int connnum = epoll_wait(epfd, evs, CLIENTSIZE, -1);
        for (int i = 0; i < connnum; ++i)
        {
            if (evs[i].events & EPOLLERR)
            {
                printf("%d exit\n", evs[i].data.fd);
                delfd(epfd, evs[i].data.fd);
                close(evs[i].data.fd);
                mapdata.erase(evs[i].data.fd);
            }
            else if (evs[i].data.fd == listenfd && (evs[i].events & EPOLLIN))
            {
                struct sockaddr_in client;
                socklen_t len = sizeof(client);
                int cfd = accept(listenfd, (struct sockaddr*)&client, &len);
                if (cfd == -1)
                {
                    ErrExit("accept");
                }
                printf("get connection: %d\n", cfd);
                addfd(epfd, cfd);
            }
            else if (evs[i].events & EPOLLIN)
            {
                char buf[BUFSIZE] = {0};
                int len = recv(evs[i].data.fd, buf, BUFSIZE-1, 0);
                if (len > 0)
                {
                    mapdata[evs[i].data.fd] = buf;
                    evs[i].events &= (~EPOLLIN);
                    evs[i].events |= EPOLLOUT;
                    if (-1 == epoll_ctl(epfd, EPOLL_CTL_MOD, evs[i].data.fd, &evs[i]))
                    {
                        ErrExit("epoll_ctl");
                    }
                }
                else if (len == 0)
                {
                    printf("%d exit\n", evs[i].data.fd);
                    delfd(epfd, evs[i].data.fd);
                    close(evs[i].data.fd);
                    mapdata.erase(evs[i].data.fd);
                }
                else
                {
                    ErrExit("recv");
                }
            }
            else if (evs[i].events & EPOLLOUT)
            {
                if (send(evs[i].data.fd, mapdata[evs[i].data.fd].c_str(), mapdata[evs[i].data.fd].size(), 0) < 0)
                {
                    if (errno == 104)
                    {
                        continue;
                    }
                    ErrExit("send");
                }
                evs[i].events &= (~EPOLLOUT);
                evs[i].events |= EPOLLIN;
                if (-1 == epoll_ctl(epfd, EPOLL_CTL_MOD, evs[i].data.fd, &evs[i]))
                {
                    ErrExit("epoll_ctl");
                }
            }
        }
    }
 
    close(listenfd);
    close(epfd);
     
    return 0;
}

  

posted @   二狗啸地  阅读(2317)  评论(0编辑  收藏  举报
编辑推荐:
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
阅读排行:
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· DeepSeek火爆全网,官网宕机?本地部署一个随便玩「LLM探索」
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 上周热点回顾(1.20-1.26)
· 【译】.NET 升级助手现在支持升级到集中式包管理
点击右上角即可分享
微信分享提示