对epoll相关知识做简单整理:
最近一直在做网络通信相关的开发,对epoll的使用有了一定的认知,就有想法整理一下:
相关内容来自网络,仅供个人笔记
1:什么时候使用epoll
1:通常网络开发(tcp/udp)的时候,使用epoll(IO多路复用)对连接,可读,可写进行监听。
2:epoll事件中可以存储一个指针,这个特性可以用来实现一些复杂的网络处理。
3:用epoll事件中可以存储指针的特性,可以基于此基础上实现一个reactor模型。
2:epoll使用的数据结构和函数
在熟练使用epoll后,终于意识到要从对应的源码试图了解epoll。
这里,获取到对应的头文件<sys/epoll.h>,对epoll做一些认识。
1:标识符 __BEGIN_DECLS和__END_DECLS
#ifdef __cplusplus # define __BEGIN_DECLS extern "C" { # define __END_DECLS } #else # define __BEGIN_DECLS # define __END_DECLS #endif
从源码中可以发现:
__BEGIN_DECLS和__END_DECLS 宏定义其实时 extern “C” { }的重新命名。
这里的作用:
在c和c++混合使用时,C++调用c编译出的相关接口,可以使用这两个宏。
==》简化了extern “C” {} 而已
2:了解相关的结构体,应用层我们所时使用的。
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 */ } __EPOLL_PACKED;
注意:
struct epoll_event 是函数参数类型,在整个流程中,通过该结构对已知事件进行获取和处理.
注意epoll_event 结构体中epoll_data_t的定义:
epoll_data_t 是一个union公用体类型(使用其中的一种类型)
通过这里的事件结构,我们分析我们可以使用到的:
通常:
1:我们使用epoll_data_t中的int类型保存服务端或者客户端连接的fd。
2:使用void* 保存我们需要的一些自定义指针(如结构体指针),可以实现一些复杂业务,或者使用这个实现reactor网络事件触发机制。
3:了解对应的一些函数。
1:epoll_create 创建epoll对象
extern int epoll_create (int __size) __THROW;
作用: 创建epoll,返回epoll对象的文件描述符,注意在使用结束要关闭
参数 :__size 在linux 内核版本大于2.6.8 后,这个size 参数就被弃用了,但是传入的值必须大于0。
==》早期版本,该参数告诉内核epoll需要的fd文件描述符的个数,使用该大小去申请内存。
返回值:
成功时返回epoll对象的文件描述符。
失败时返回-1,并将errno作为错误指示。
简单了解了一下返回-1时,errno的一些设置:
EINVAL : 无效的标志 EMFILE : 用户打开的文件超过了限制 ENFILE : 系统打开的文件超过了限制 ENOMEM : 没有足够的内存完成当前操作
2:epoll_create1 类似epoll_create()
extern int epoll_create1 (int __flags) __THROW;
可以处理一些fork进程时,文件描述符相关问题,通过设置flag:
可以将flags设置为O_CLOEXEC,参考open函数的O_CLOEXEC标记。
O_CLOEXEC标记将FD_CLOEXEC常量设置为文件描述符标志,在进程fork时,可以关闭子进程无用文件描述符。(参考)
3:epoll_ctl 往epoll对象中操作事件
实现对事件的加入,修改,删除
extern int epoll_ctl (int __epfd, int __op, int __fd,struct epoll_event *__event) __THROW;
相关函数参数:
__epfd:epoll_create返回值,即epoll对象对应的描述符
__op:要执行的动作
EPOLL_CTL_ADD:向多路复用实例加入一个连接socket的文件描述符 EPOLL_CTL_MOD:改变多路复用实例中的一个socket的文件描述符的触发事件 EPOLL_CTL_DEL:移除多路复用实例中的一个socket的文件描述符
__fd: 要操作的文件描述符,如监听服务端fd, 连接进来的客户端fd
__event:上文中的epoll_event 结构,其中该结构中的events指定了我们关心的事件。
可读,可写,错误,设置触发模式边沿触发还是水平触发 EPOLLIN:关注文件描述符的可读 ==》即有内容可以读出 EPOLLOUT:关注文件描述符的可写 ==》即有空间写入内容
返回值:
成功时返回0,失败时返回-1,相关的errno标志如下:
EBADF : epfd或者fd不是一个有效的文件描述符 EEXIST : op为EPOLL_CTL_ADD,但fd已经被监控 EINVAL : epfd是无效的epoll文件描述符 ENOENT : op为EPOLL_CTL_MOD或者EPOLL_CTL_DEL,并且fd未被监控 ENOMEM : 没有足够的内存完成当前操作 ENOSPC : epoll实例超过了/proc/sys/fs/epoll/max_user_watches中限制的监听数量
源码中关于参数的定义:
/* 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. */ enum EPOLL_EVENTS { EPOLLIN = 0x001, //可读 #define EPOLLIN EPOLLIN EPOLLPRI = 0x002, //发生异常情况,比如所tcp连接中收到了带外消息 #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, // read/write时出错,对方可能关闭,这个事件是一直监控的 #define EPOLLERR EPOLLERR EPOLLHUP = 0x010, //文件被挂断。这个事件是一直监控的,即使没有明确指定 //通常表示本端被挂断,一般于RST联系在一起 #define EPOLLHUP EPOLLHUP EPOLLRDHUP = 0x2000, //对端关闭连接或者shutdown写入半连接 #define EPOLLRDHUP EPOLLRDHUP EPOLLWAKEUP = 1u << 29, // 如果EPOLLONESHOT和EPOLLET清除了,并且进程拥有CAP_BLOCK_SUSPEND权限,那么这个标志能够保证事件在挂起或者处理的时候,系统不会挂起或休眠 #define EPOLLWAKEUP EPOLLWAKEUP EPOLLONESHOT = 1u << 30, //一个事件发生并读取后,文件自动不再监控 #define EPOLLONESHOT EPOLLONESHOT EPOLLET = 1u << 31 //开启边缘触发,默认的是水平触发,所以我们并未看到EPOLLLT #define EPOLLET EPOLLET };
4:epoll_wait 对epoll对象中已触发事件的处理
等待epoll实例上已经触发的事件,返回触发事件的数量
extern int epoll_wait (int __epfd, struct epoll_event *__events, int __maxevents, int __timeout);
相关参数:
__epfd: epoll_create函数返回的epoll对象的文件描述符
__events: 缓冲区,将存储触发的事件
__maxevents: 要处理的最大事件个数
__timeout: 超时时间,单位毫秒, -1表示无限等待,0为立即返回
返回值:
返回就绪事件的数量。
返回0,表示超时。
返回-1,根据erron错误码的设置,进行分析:
EBADF : epfd不是一个有效的文件描述符 EFAULT : events指向的内存无权访问 EINTR : 在请求事件发生或者过期之前,调用被信号打断 EINVAL : epfd是无效的epoll文件描述符
注意:
除了关注本身的可读,可写,以及相关错误,异常情况相关事件,还有一种就是设置触发模式: 水平触发,和边沿触发。
水平触发和边沿触发时epoll中的另一个知识点,我的另一个博客已经整理~
水平触发:对于可读,如果缓冲区中有数据,则会一直触发。
对于可写,如果缓冲区中有空余位置,则会一直触发。
边沿触发:对于可读,当缓冲区来数据,缓冲区大小发生变化时,会触发一次。
对于可写,缓冲区大小变化时,会触发一次。 (描述如果不准确,请指正)
4:epoll的实现原理
1:每个epoll对象维持一个独立的eventpoll结构体,管理独立的内存。
2:eventpoll用红黑树存储相关事件,通过epoll_ctl函数实现事件的修改。 ==》高效查找识别
3:加入到的epoll事件会与设备(如网卡)建立回调关系,执行对应的回调函数。
4:回调函数会把触发到的事件加入到eventpoll 中管理的一个双向链表中。
5:每次epoll_wait其实是对这里的双向链表进行检测。
注意:这里的红黑树和链表是公用一个节点的。
5:errno的了解
1:了解到errno的定义:<errno.h>
#ifndef errno extern int errno; #endif
使用:
errno会捕获操作系统最后的错误。
个人理解:
errno其实是一个全局变量,由操作系统控制,存储操作系统就近发生错误的错误码。
errno其实就是一个int类型的数字,每个数字对应着相关的错误描述,我们可以用测试的方式打印相关的错误描述:
2:errno的输出:
perror函数:
perror函数errno对应的错误消息的字符串打印到标准错误输出上,即stderr或2上:
#include <stdio.h> void perror(const char *msg);
该函数会先打印参数中的字符串,再打印errno对应的错误描述。
strerrorr函数:
#include <string.h> char * strerror(int errno); printf("errno is %d [%s] \n", errno, strerror(errno));
传入操作系统的错误描述码,返回该错误码对应的错误描述字符串。
测试:可以试着打印strerror的返回值,传入数字。
相关errno错误描述:
#define EPERM 1 /* Operation not permitted */ #define ENOENT 2 /* No such file or directory */ #define ESRCH 3 /* No such process */ #define EINTR 4 /* Interrupted system call */ #define EIO 5 /* I/O error */ #define ENXIO 6 /* No such device or address */ #define E2BIG 7 /* Argument list too long */ #define ENOEXEC 8 /* Exec format error */ #define EBADF 9 /* Bad file number */ #define ECHILD 10 /* No child processes */ #define EAGAIN 11 /* Try again */ #define ENOMEM 12 /* Out of memory */ #define EACCES 13 /* Permission denied */ #define EFAULT 14 /* Bad address */ #define ENOTBLK 15 /* Block device required */ #define EBUSY 16 /* Device or resource busy */ #define EEXIST 17 /* File exists */ #define EXDEV 18 /* Cross-device link */ #define ENODEV 19 /* No such device */ #define ENOTDIR 20 /* Not a directory */ #define EISDIR 21 /* Is a directory */ #define EINVAL 22 /* Invalid argument */ #define ENFILE 23 /* File table overflow */ #define EMFILE 24 /* Too many open files */ #define ENOTTY 25 /* Not a typewriter */ #define ETXTBSY 26 /* Text file busy */ #define EFBIG 27 /* File too large */ #define ENOSPC 28 /* No space left on device */ #define ESPIPE 29 /* Illegal seek */ #define EROFS 30 /* Read-only file system */ #define EMLINK 31 /* Too many links */ #define EPIPE 32 /* Broken pipe */ #define EDOM 33 /* Math argument out of domain of func */ #define ERANGE 34 /* Math result not representable */ #define ENOSYS 38 /* Invalid system call number */ #define ENOTEMPTY 39 /* Directory not empty */ #define ELOOP 40 /* Too many symbolic links encountered */ #define EWOULDBLOCK EAGAIN /* Operation would block */ #define ENOMSG 42 /* No message of desired type */ #define EIDRM 43 /* Identifier removed */ #define ECHRNG 44 /* Channel number out of range */ #define EL2NSYNC 45 /* Level 2 not synchronized */ #define EL3HLT 46 /* Level 3 halted */ #define EL3RST 47 /* Level 3 reset */ #define ELNRNG 48 /* Link number out of range */ #define EUNATCH 49 /* Protocol driver not attached */ #define ENOCSI 50 /* No CSI structure available */ #define EL2HLT 51 /* Level 2 halted */ #define EBADE 52 /* Invalid exchange */ #define EBADR 53 /* Invalid request descriptor */ #define EXFULL 54 /* Exchange full */ #define ENOANO 55 /* No anode */ #define EBADRQC 56 /* Invalid request code */ #define EBADSLT 57 /* Invalid slot */ #define EDEADLOCK EDEADLK #define EBFONT 59 /* Bad font file format */ #define ENOSTR 60 /* Device not a stream */ #define ENODATA 61 /* No data available */ #define ETIME 62 /* Timer expired */ #define ENOSR 63 /* Out of streams resources */ #define ENONET 64 /* Machine is not on the network */ #define ENOPKG 65 /* Package not installed */ #define EREMOTE 66 /* Object is remote */ #define ENOLINK 67 /* Link has been severed */ #define EADV 68 /* Advertise error */ #define ESRMNT 69 /* Srmount error */ #define ECOMM 70 /* Communication error on send */ #define EPROTO 71 /* Protocol error */ #define EMULTIHOP 72 /* Multihop attempted */ #define EDOTDOT 73 /* RFS specific error */ #define EBADMSG 74 /* Not a data message */ #define EOVERFLOW 75 /* Value too large for defined data type */ #define ENOTUNIQ 76 /* Name not unique on network */ #define EBADFD 77 /* File descriptor in bad state */ #define EREMCHG 78 /* Remote address changed */ #define ELIBACC 79 /* Can not access a needed shared library */ #define ELIBBAD 80 /* Accessing a corrupted shared library */ #define ELIBSCN 81 /* .lib section in a.out corrupted */ #define ELIBMAX 82 /* Attempting to link in too many shared libraries */ #define ELIBEXEC 83 /* Cannot exec a shared library directly */ #define EILSEQ 84 /* Illegal byte sequence */ #define ERESTART 85 /* Interrupted system call should be restarted */ #define ESTRPIPE 86 /* Streams pipe error */ #define EUSERS 87 /* Too many users */ #define ENOTSOCK 88 /* Socket operation on non-socket */ #define EDESTADDRREQ 89 /* Destination address required */ #define EMSGSIZE 90 /* Message too long */ #define EPROTOTYPE 91 /* Protocol wrong type for socket */ #define ENOPROTOOPT 92 /* Protocol not available */ #define EPROTONOSUPPORT 93 /* Protocol not supported */ #define ESOCKTNOSUPPORT 94 /* Socket type not supported */ #define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ #define EPFNOSUPPORT 96 /* Protocol family not supported */ #define EAFNOSUPPORT 97 /* Address family not supported by protocol */ #define EADDRINUSE 98 /* Address already in use */ #define EADDRNOTAVAIL 99 /* Cannot assign requested address */ #define ENETDOWN 100 /* Network is down */ #define ENETUNREACH 101 /* Network is unreachable */ #define ENETRESET 102 /* Network dropped connection because of reset */ #define ECONNABORTED 103 /* Software caused connection abort */ #define ECONNRESET 104 /* Connection reset by peer */ #define ENOBUFS 105 /* No buffer space available */ #define EISCONN 106 /* Transport endpoint is already connected */ #define ENOTCONN 107 /* Transport endpoint is not connected */ #define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ #define ETOOMANYREFS 109 /* Too many references: cannot splice */ #define ETIMEDOUT 110 /* Connection timed out */ #define ECONNREFUSED 111 /* Connection refused */ #define EHOSTDOWN 112 /* Host is down */ #define EHOSTUNREACH 113 /* No route to host */ #define EALREADY 114 /* Operation already in progress */ #define EINPROGRESS 115 /* Operation now in progress */ #define ESTALE 116 /* Stale file handle */ #define EUCLEAN 117 /* Structure needs cleaning */ #define ENOTNAM 118 /* Not a XENIX named type file */ #define ENAVAIL 119 /* No XENIX semaphores available */ #define EISNAM 120 /* Is a named type file */ #define EREMOTEIO 121 /* Remote I/O error */ #define EDQUOT 122 /* Quota exceeded */ #define ENOMEDIUM 123 /* No medium found */ #define EMEDIUMTYPE 124 /* Wrong medium type */ #define ECANCELED 125 /* Operation Canceled */ #define ENOKEY 126 /* Required key not available */ #define EKEYEXPIRED 127 /* Key has expired */ #define EKEYREVOKED 128 /* Key has been revoked */ #define EKEYREJECTED 129 /* Key was rejected by service */ /* for robust mutexes */ #define EOWNERDEAD 130 /* Owner died */ #define ENOTRECOVERABLE 131 /* State not recoverable */ #define ERFKILL 132 /* Operation not possible due to RF-kill */ #define EHWPOISON 133 /* Memory page has hardware error */