对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 */