且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

epoll学习

更新时间:2022-09-16 20:30:16

epoll全名event poll,他是poll的加强版本,从linux 2.6开始。

select,poll,epoll的关系:

  • select,IO多路归并,也就是在单一线程中监控多个fd
  • poll:具有select的作用,但是select有个局限,被监听的fd数量有限,poll改进了这一点,并且相比于select而言,接口更方便
  • epoll:具有epoll的作用,但是poll是O(n)操作,即需要线性遍历所有的fd,逐一检测,而epoll进行了改进,通过事件注册,直接触发相关函数,不需要遍历所有注册的fd。

epoll有三个接口:

  • epoll_create:创建epoll对象
  • epoll_ctl:控制epoll对象
  • epoll_wait:等待epoll事件发生

 

这里一遍介绍epoll使用方法的文章 https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/

我将里面的元代码修改了,将一些方法提取出来,使得程序的整体脉络更清晰,记录在这里,以便参考:

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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>
 
#define MAXEVENTS 64
 
/**
 * 将socket设置为非堵塞
 * static 函数防止外部调用,默认都是extern
 */
static int MakeSocketNonBlocking (int nSockFd);
 
/**
 * 接受新的链接,并将其放到epoll中
 * @param nListenSock 监听socket
 * @param nEpoll epoll对象实例
 * @return 0 for OK,-1 for error
 */
static int AcceptConnections(int nListenSock, int nEpoll);
 
/**
 * 处理新链接的客户端
 */
static int ProcessClient(int nClientSock);
 
/**
 * 根据端口创建一个socket,绑定到指定端口并返回此socket。
 * 此方法兼容IPv4和IPv6
 */
static int CreateAndBindSocket (char *szPort);
 
/**
 * 主入口
 */
int main (int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf (stderr, "Usage: %s [port]\n", argv[0]);
        exit (EXIT_FAILURE);
    }
 
    int nListenSock = CreateAndBindSocket(argv[1]);
    if (nListenSock == -1)
    {
        abort();
    }
 
    int iRet = MakeSocketNonBlocking(nListenSock);
    if (iRet == -1)
    {
        abort();
    }
 
    iRet = listen(nListenSock, SOMAXCONN);
    if (iRet == -1)
    {
        perror ("listen");
        abort ();
    }
 
    // 创建epoll对象
    int nEpoll = epoll_create(1024);
    if (nEpoll == -1)
    {
        perror ("epoll_create");
        abort ();
    }
 
    struct epoll_event oEvent;
    oEvent.data.fd = nListenSock; // 此事件fd设置为监听socket
    oEvent.events = EPOLLIN | EPOLLET; // 注册读(in)事件和边界触发事件,默认为level-triggered
    iRet = epoll_ctl (nEpoll, EPOLL_CTL_ADD, nListenSock, &oEvent); // 将此事件添加到epoll对象中
    if (iRet == -1)
    {
        perror ("epoll_ctl");
        abort ();
    }
 
    /* Buffer where events are returned */
    struct epoll_event* pEventList = (epoll_event*)calloc(MAXEVENTS, sizeof oEvent);
 
    /* The event loop */
    while (1)
    {
        // 等待epoll事件发生,n为发生的个数,-1那么wait的事件有内核指定
        int n = epoll_wait(nEpoll, pEventList, MAXEVENTS, -1);
        for (int i = 0; i < n; i++)
        {
            if ((pEventList[i].events & EPOLLERR) ||
                (pEventList[i].events & EPOLLHUP) ||
                (!(pEventList[i].events & EPOLLIN)))
            {
                /* An error has occured on this fd, or the socket is not
                ready for reading (why were we notified then?) */
                fprintf (stderr, "epoll error\n");
                close (pEventList[i].data.fd);
                continue;
            }
            else if (nListenSock == pEventList[i].data.fd) // 监听socket有in事件触发,说明有新的链接,那么添加到epoll中
            {
                int iRet = AcceptConnections(nListenSock, nEpoll);
                if (iRet == -1)
                {
                    abort();
                }
            }
            else // 处理所有的fd
            {
                int iRet = ProcessClient(pEventList[i].data.fd);
                if (iRet != 0)
                {
                    abort();
                }
            } // end of if
        } // end of for
    } // end of while
 
    free (pEventList);
    close (nListenSock);
 
    return EXIT_SUCCESS;
}
 
/**
 * 将socket设置为非堵塞
 * static 函数防止外部调用,默认都是extern
 */
int MakeSocketNonBlocking (int nSockFd)
{
    // 获取当前的flags
    int nFlags = fcntl (nSockFd, F_GETFL, 0);
    if (nFlags == -1)
    {
        perror ("fcntl");
        return -1;
    }
 
    // 添加O_NONBLOCK标记,也就是非堵塞
    nFlags |= O_NONBLOCK;
    int iRet = fcntl (nSockFd, F_SETFL, nFlags);
    if (iRet == -1)
    {
        perror ("fcntl");
        return -1;
    }
 
    return 0;
}
 
/**
 * 根据端口创建一个socket,绑定到指定端口并返回此socket。
 * 此方法兼容IPv4和IPv6
 */
int CreateAndBindSocket (char *szPort)
{
    // 传给函数getaddrinfo的提示数据结构,用于IPv4和IPv6兼容
    struct addrinfo oAddrHints;
    memset (&oAddrHints, 0, sizeof (struct addrinfo));
    oAddrHints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
    oAddrHints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
    oAddrHints.ai_flags = AI_PASSIVE;     /* All interfaces */
 
    // 获取当前host的信息数据
    struct addrinfo *pHostAddrInfo;
    int iRet = getaddrinfo (NULL, szPort, &oAddrHints, &pHostAddrInfo);
    if (iRet != 0)
    {
        fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (iRet));
        return -1;
    }
 
    int nSock = -1;
    struct addrinfo *pCurAddr; // 当前地址,host可以装有多个网卡,需要遍历每一个可用的ip
    for (pCurAddr = pHostAddrInfo; pCurAddr != NULL; pCurAddr = pCurAddr->ai_next)
    {
        nSock = socket (pCurAddr->ai_family, pCurAddr->ai_socktype, pCurAddr->ai_protocol);
        if (nSock == -1)
        {
            continue;
        }
        iRet = bind (nSock, pCurAddr->ai_addr, pCurAddr->ai_addrlen);
        if (iRet == 0)
        {
            /* We managed to bind successfully! */
            break;
        }
 
        close (nSock);
    }
 
    if (pCurAddr == NULL)
    {
        fprintf (stderr, "Could not bind\n");
        return -1;
    }
 
    // 释放内存
    freeaddrinfo (pHostAddrInfo);
 
    // 返回可以用的socket
    return nSock;
}
 
/**
 * 接受新的链接,并将其放到epoll中
 * @param nListenSock 监听socket
 * @param nEpoll epoll对象实例
 * @return 0 for OK,-1 for error
 */
int AcceptConnections(int nListenSock, int nEpoll)
{
    // We have a notification on the listening socket, which means one or more incoming connections.
    while (1)
    {
        struct sockaddr oClientAddr;
        socklen_t nSockLen = sizeof oClientAddr;
        int nConnSock = accept (nListenSock, &oClientAddr, &nSockLen);
        if (nConnSock == -1)
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                // We have processed all incoming connections.
                break;
            }
            else
            {
                perror ("accept");
                return -1;
            }
        }
 
        // 输出新建连接的客户端地址
        char szHostBuf[NI_MAXHOST], szServerBuf[NI_MAXSERV];
        int iRet = getnameinfo( &oClientAddr, nSockLen,
                            szHostBuf, sizeof szHostBuf,
                            szServerBuf, sizeof szServerBuf,
                            NI_NUMERICHOST | NI_NUMERICSERV);
        if (iRet == 0)
        {
            printf("Accepted connection on descriptor %d "
                   "(host=%s, port=%s)\n", nConnSock, szHostBuf, szServerBuf);
        }
 
        /* Make the incoming socket non-blocking and add it to the
         list of fds to monitor. */
        iRet = MakeSocketNonBlocking (nConnSock);
        if (iRet == -1)
        {
            return -1;
        }
        struct epoll_event oEvent;
        oEvent.data.fd = nConnSock;
        oEvent.events = EPOLLIN | EPOLLET;
        iRet = epoll_ctl(nEpoll, EPOLL_CTL_ADD, nConnSock, &oEvent); // 将新的fd添加到epoll中
        if (iRet == -1)
        {
            perror ("epoll_ctl");
            return -1;
        }
    }
 
    return 0;
}
 
/**
 * 处理新链接的客户端
 */
int ProcessClient(int nClientSock)
{
    /* We have data on the fd waiting to be read. Read and
    display it. We must read whatever data is available
    completely, as we are running in edge-triggered mode
    and won't get a notification again for the same
    data. */
    int done = 0;
 
    while (1)
    {
       ssize_t count;
       char buf[512];
 
       count = read(nClientSock, buf, sizeof buf);
       if (count == -1)
       {
            /* If errno == EAGAIN, that means we have read all
            data. So go back to the main loop. */
            if (errno != EAGAIN)
            {
               perror ("read");
               done = 1;
            }
            break;
       }
       else if (count == 0)
       {
           /* End of file. The remote has closed the
            connection. */
           done = 1;
           break;
       }
 
        /* Write the buffer to standard output */
        int iRet = write (1, buf, count);
        if (iRet == -1)
        {
           perror ("write");
           return -1;
        }
    }
 
    if (done)
    {
        printf ("Closed connection on descriptor %d\n", nClientSock);
 
        /* Closing the descriptor will make epoll remove it
        from the set of descriptors which are monitored. */
        close (nClientSock);
    }
 
    return 0;
}

编译好后,使用telnet链接服务器,可以看到效果

声明:如有转载本博文章,请注明出处。您的支持是我的动力!文章部分内容来自互联网,本人不负任何法律责任。
本文转自bourneli博客园博客,原文链接:http://www.cnblogs.com/bourneli/archive/2011/12/30/unp.html,如需转载请自行联系原作者