int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
void FD_ZERO(fd_set *set); //清空文件描述符集合
void FD_SET(int fd, fd_set *set); //将待监听的文件描述符添加到监听集合中
void FD_CLR(int fd, fd_set *set); //将文件描述符从监听集合中移除
int FD_ISSET(int fd, fd_set *set); //判断文件描述符是否在监听集合中
int poll(struct pollfd * fds, unsigned int nfds, int timeout);
struct pollfd
{
int fd; /* 待监听文件描述符 */
short events; /* 待监听的事件 */
short revents; /* 实际就绪的事件 */
} ;
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
typedef union epoll_data{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}
struct epoll_event{
uint32_t events;
epoll_data data;
}
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
epoll的两种触发模式分别是ET(edge trigger)边缘触发和LT(level triggered)水平触发。
epoll的默认触发模式是LT,select、poll都是LT触发。
缓冲区只要有数据未读就会导致epoll_wait返回。
上次读数据未读完仍会导致epoll_wait返回。
水平触发模式下阻塞和非阻塞并没有什么区别,因为没有可读时间就绪的话epoll_wait不会返回。
缓冲区出现新未读数据才会导致epoll_wait返回。
上次读数据未读完不会导致epoll_wait返回。
边缘触发模式下事件就绪只会通知一次,为了保证数据成功被读取或写入,在非阻塞模式下,采用循环的方式进行读写,直到完成或出现异常时退出。
如果不采用循环的方式进行读写,就会造成数据读/写不完的情况,因为下一次再调用epoll_wait就不会再通知了,所以职能采用循环的方式进行读写。但是如果尝试采用循环的方式进行读写,则会造成永久阻塞。
造成阻塞的原因只有没有数据可读/可写,在非阻塞模式下出现没有数据可读/可写可以返回相应的错误信息设置errno(EWOULDBLOCK),但是阻塞模式就会进入阻塞状态,而处理的该fd永远也不可能再有可读数据了,所以就被永久阻塞了。
epoll反应堆
采用ET非阻塞轮询,利用epoll_data
联合体中void *ptr
作为回调函数处理accept
、读写事件。
优点 | 缺点 | |
---|---|---|
select | 1、跨平台,win、linux等系统都支持。 2、可以等待多个套接字。 |
1、监听上限受文件描述符个数限制,一般每个进程最多允许同时打开1024个文件,所以单个进程最多可监听1024个套接字描述符。 2、每次调用select都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很⼤大。 3、同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很⼤大。 4、每次需要重新设定监听fd_set集合。 5、select返回后,需要轮询所有fd来获取就绪的描述符。 |
poll | 1、自带数组结构,输入输出事件分离,不用每次重新设置。 2、套接字文件描述符数量没有上限。 |
1、不能跨平台。 2、和select一样,poll返回后,需要轮询pollfd来获取就绪的描述符。 3、每次调用poll都需要大把大量描述符在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。 |
epoll | 1、不需要轮询,获取事件的时间复杂度是O(1)。 2、套接字文件描述符数量没有上限。 3、不需要每次从用户态拷贝到内核态。 |
1、不能跨平台。 |
本机字节序:小端字节序,高位存在高地址,低位存在低地址。
本机字节序:大端字节序,高位存在低地址,低位存在高地址。
htonl:通常用来将本机IP转换为网络字节序。
htons:通常用来将本机PORT转换为网络字节序。
ntohl:通常用来将IP从网络字节序转换为本机字节序。
ntohs:通常用来将PORT从网络字节序转换为本机字节序。
inet_pton:将本地字节序的string IP转化成网络字节序二进制的IP地址。
inet_ntop:将网络字节序二进制的IP地址转化成本地字节序的string IP。
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);
struct sockaddr_in{
sa_family_t sin_family; //AF_INET、AF_INET6
in_port_t sin_port; //网络字节序端口号
struct in_addr sin_addr; //网络字节序IP地址
}
struct in_addr{
uint32_t s_addr;
}
struct sockaddr_in addr;
addr.sin_addr.addr = htonl(INADDR_ANY); //INADDR_ANY宏可取出可用的本机字节序IP
int socket(int af, int type, int protocol);
返回一个套接字描述符,失败返回-1。
af(IP协议):AF_INET、AF_INET6、AF_UNIX。
type(数据传输协议):SOCK_STREAM、SOCK_DGRAM。
protocol(代表协议,比如SOCK_STREAM代表协议TCP,SOCK_DGRAM代表协议UDP):默认填0。
int bind(int sockfd, const struct sockaddr, socklen_t addrlen);
bind(fd, (struct sockaddr*)&addr, sizeof(addr));
给socket绑定一个IP和PORT。成功返回0,失败返回-1。
int listen(int sockfd, int backlog);
设置同时可与服务器建立连接的上限数,即同时进行三次握手
的客户端的数量。
int accept(int sockfd, struct sockaddr * addr, socklen_t * addrlen);
阻塞等待客户端建立连接,成功则返回与客户端成功建立通信的套接字描述符。
addr是建立成功的客户端地址。
addrlen是客户端地址的长度。
int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
阻塞等待客户端与服务器建立连接,成功则返回0,失败返回-1。
addr是建立成功的客户端地址。
addrlen是客户端地址的长度。
如果客户端不适用bind绑定客户端地址结构,则采用隐式绑定,系统自动绑定。
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&opt , sizeof(opt));
设置端口复用,允许端口被重复使用,常见的还有设置读写缓冲区大小等。
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
常用fcntl(fd, F_SETFL, fcntl(_fd, F_GETFL, 0) | O_NONBLOCK)
来设置非阻塞。
ssize_t read(int fd,void * buf ,size_t count);
读fd套接字描述符数据,数据将读到buf 缓冲区,最多读取count个字节的数据。
返回0代表读到文件末尾,对端套接字关闭。
返回大于0代表读到数据的字节数。
返回-1代表出错,此时errno
如果是EINTR
为异常中断需要重新读,errno
如果是EAGIN
或EWOULDBLOCK
为以非阻塞的方式读数据但是没有数据。其它情况都是发生了错误。
ssize_t write (int fd,const void * buf,size_t count);
向fd套接字描述符写数据,数据将从buf 缓冲区读取count个字节的数据写入fd的读缓冲区。