旅游网站建设答辩ppt模板,上海十大公关公司排名,安卓开发基础教程,网站制作价格与售后视频目录一、高级IO相关1.1 同步通信和异步通信1.2 阻塞与非阻塞1.3 fcntl 函数二、五种IO模型2.1 阻塞式IO模型2.2 非阻塞式IO模型2.3 多路复用IO模型2.4 信号驱动式IO模型2.5 异步IO模型三、认识IO多路复用四、select4.1 认识select函数4.2 select函数原型4.3 select网络编程4.4 …
目录一、高级IO相关1.1 同步通信和异步通信1.2 阻塞与非阻塞1.3 fcntl 函数二、五种IO模型2.1 阻塞式IO模型2.2 非阻塞式IO模型2.3 多路复用IO模型2.4 信号驱动式IO模型2.5 异步IO模型三、认识IO多路复用四、select4.1 认识select函数4.2 select函数原型4.3 select网络编程4.4 setsockopt函数补充4.5 select的特点4.6 select的缺点五、poll5.1 认识poll函数5.2 poll网络编程5.3 poll函数的优点5.4 poll函数的缺点六、epoll6.1 认识epoll函数6.2 epoll工作原理6.3 epoll工作模式LT模式ET模式6.4 epoll网络编程6.5 epoll的优点一、高级IO相关
1.1 同步通信和异步通信
同步通信和异步通信是两种不同的通信方式二者的概念如下 同步通信是指通信双方需要在某时刻达成一致才进行数据交换。在同步通信中发送方会在发送数据时等待接收方的响应直到接收到响应后才会继续执行后续任务。同步通信可以保证数据传输的可靠性和一致性但是可能造成系统的阻塞和资源浪费。 异步通信是指通信双方可以独立的进行数据交换不需要在某一时刻达成一致。在异步通信中发送方会在发送数据后立即返回而接收方会在接收到数据后立即进行处理。异步通信可以提高系统的并发性和效率但是也可能会带来一些数据的不一致问题。
总的来说同步通信适用于对数据传输的可靠性和一致性要求较高的场景例如数据库的读写操作。而异步通信适用于对系统的并发性和效率要求较高的场景如网络通信。具体选择哪种通信方式需要根据实际情况来考虑。
1.2 阻塞与非阻塞
阻塞和非阻塞是两种不同的操作方式二者的概念如下
阻塞是指在进行某种操作时如果当前操作无法完成那么程序就会一直等待直到操作完成或者出现错误时才返回结果。阻塞操作会阻塞当前线程或进程的执行直到操作完成因此会占用CPU资源并且可能造成系统的阻塞。非阻塞是指在进行某种操作时如果当前操作无法完成则程序会立马返回并且告诉调用者当前操作无法完成。非阻塞操作不会阻塞当前线程或进程的执行因此不会占用CPU资源并且可以让程序执行其他任务。
总的来说阻塞和非阻塞是对于操作的执行方式的描述阻塞操作会等待操作的完成而非阻塞操作会立即返回。选择使用阻塞或非阻塞操作取决于应用程序的需求和实际情况。如果需要快速响应和处理多个并发请求通常会使用非阻塞操作。而如果需要保证数据传输的可靠性和一致性则可能需要使用阻塞操作。
1.3 fcntl 函数
fcntl是一个Unix/Linux系统编程中的函数用于控制文件描述符的一些属性和操作。其定义如下
#include unistd.h
#include fcntl.hint fcntl(int fildes, int cmd, ...);参数fd是被参数cmd操作(如下面的描述)的文件描述符传入的cmd的值不同fcntl后面追加的参数也不相同。
fcntl函数有5种功能 复制一个现有的文件描述符 (cmdF_DUPFD)获得/设置文件描述符标记 (cmdF_GETFD或F_SETFD)获得/设置文件状态标记 (cmdF_GETFL或F_SETFL)获得/设置异步I/O所有权 (cmdF_GETOWN或F_SETOWN)获得/设置记录锁 (cmdF_GETLK, F_SETLK或F_SETLKW) 此处只是用第三种功能获取/设置文件状态标记就可以将一个文件描述符设置为非阻塞如下面的例子
基于fcntl我们实现一个SetNoBlock函数将文件描述符设置为非阻塞。
bool SetNoBlock(int sock)
{int flag fcntl(sock, F_GETFL);if(flag -1)return false;int n fcntl(sock, F_SETFL, flag | O_NONBLOCK);if(n -1)return false;return true;
}先使用F_GETFL将当前的文件描述符的属性取出来(这是一个位图)然后再使用F_SETFL将文件描述符设置回去设置回去的同时加上一个O_NONBLOCK参数将这个文件描述符设置为非阻塞状态。
下面以轮询的方式读取标准输入
#include iostream
#include vector
#include cstring
#include functional
#include unistd.h
#include fcntl.husing namespace std;using func_t std::functionvoid();void func1()
{cout func1 endl;
}void func2()
{cout func2 endl;
}void func3()
{cout func3 endl;
}bool SetNoBlock(int sock)
{int flag fcntl(sock, F_GETFL);if(flag -1)return false;int n fcntl(sock, F_SETFL, flag | O_NONBLOCK);if(n -1)return false;return true;
}int main()
{std::vectorfunc_t funcs;funcs.push_back(func1);funcs.push_back(func2);funcs.push_back(func3);SetNoBlock(0);char buff[1024];while(true){memset(buff, 0, sizeof buff);ssize_t read_size read(0, buff, sizeof(buff) - 1);if(read_size 0){cerr errno: errno desc: strerror(errno) endl;for(const auto f : funcs){f();}}else {cout buff: buff read_size: read_size endl;}sleep(1);}return 0;}可以发现此时不输入时就不会阻塞而直接处理另外一个逻辑。
如果此时取消调用SetNoBlock函数 就按照阻塞的方式进行读取了。
二、五种IO模型
2.1 阻塞式IO模型
在阻塞式IO模型中当应用程序发起一个IO请求时。程序会一直阻塞等待直到数据传输完成才继续执行其他任务。这种模型的缺点是会造成CPU资源的浪费降低系统的响应速度。但是阻塞式IO是最常见的IO模型所有的套接字默认都是阻塞方式。 2.2 非阻塞式IO模型
在非阻塞式IO模型中当应用程序发起一个IO请求时即使数据没有准备好也会立即返回并且通过轮询的方式不断地查询IO操作的状态直到数据准备完成。这种模型可以减少CPU资源的浪费但也会增加系统的负担降低IO操作的效率。
2.3 多路复用IO模型
在多路复用IO模型中应用程序可以将多个IO操作绑定到同一个事件轮询器中然后等待IO操作完成的通知。这种模型可以有效地提高系统的并发性能但是实现较为复杂。
2.4 信号驱动式IO模型
在信号驱动IO模型中应用程序向操作系统注册一个信号当IO操作完成时操作系统会向应用程序发送信号通知。这种模型可以减少轮询带来的系统负担提高系统的效率。
2.5 异步IO模型
在异步IO模型中应用程序可以在发起IO请求后立即返回并在IO操作完成后由操作系统通知应用程序。这种模型可以提高系统的并发性能和效率但是实现较为复杂。 总之不论是何种IO模型都包含了两个步骤第一是等待数据第二是拷贝数据。而且在实际应用场景中等待消耗的时间往往都远高于拷贝数据的时间。让IO变得更高效最核心的办法就是尽量减少等待的时间。
三、认识IO多路复用
IO多路复用是一种高效的I/O处理机制它允许在单个线程中同时监视和处理多个I/O操作以提高程序的性能和可扩展性。
在传统的阻塞I/O模型中每个I/O操作都会阻塞整个进程直到该操作完成。这就意味着如果应用程序需要处理多个并发I/O操作就需要建立多个线程或者进程来处理它们这样就会导致系统的开销过高并且导致可扩展性下降。
使用IO多路复用应用程序可以将多个I/O操作注册到一个事件的循环中然后使用一个线程来监视这些操作的状态。当其中任何一个操作就绪时事件循环就会通知相应的应用程序来执行相应的操作。这种方式允许应用程序同时处理多个I/O操作并且无需创建多个线程或者进程因此提高了系统的性能和可扩展性。
常见的IO多路复用技术包括select、poll、epoll等其中epoll是最常用的技术之一因为它可以更好地处理大量的并发连接。
四、select
4.1 认识select函数
select()函数是一种在 Unix/Linux 系统中实现多路复用 IO 的一种机制它可以等待多个文件描述符socket、文件等中任何一个变为”就绪“状态可读、可写、异常然后立即进行处理而不是阻塞在一个文件描述符上等待数据到来。
4.2 select函数原型
select函数原型
#include sys/select.hint select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数说明
nfds需要监听的最大文件描述符的值加 1即文件描述符集合中最大的文件描述符值加 1readfds指向可读文件描述符集合的指针writefds指向可写文件描述符集合的指针exceptfds指向异常文件描述符集合的指针timeout超时时间若设置为NULL则表示永远等待直到有文件描述符就绪。
函数返回值
若有文件描述符就绪则返回就绪文件描述符的个数若超时或被信号中断则返回 0若出现错误则返回 -1并且设置errno变量。
fd_set 结构体 fd_set是一个用于表示文件描述符的结构体它包含了一组标志位每个标志位的值表示了一个文件描述符是否在集合中。fd_set结构体的定义如下
/* The fd_set member is required to be an array of longs. */
typedef long int __fd_mask;/* fd_set for select and pselect. */
typedef struct{/* XPG4.2 requires this member name. Otherwise avoid the namefrom the global namespace. */
#ifdef __USE_XOPEN__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)-fds_bits)
#else__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)-__fds_bits)
#endif} fd_set;其实这个结构就是一个整数数组更严格的说是一个位图使用位图中对应的位来表示要监视的文件描述符。
为了使得操作fd_set变得更加方便操作系统还专门提供了一组接口
FD_ZERO(fd_set *set)将集合中的所有位清零FD_SET(int fd, fd_set *set)将指定文件描述符加入集合中FD_CLR(int fd, fd_set *set)将指定文件描述符从集合中删除FD_ISSET(int fd, fd_set *set)判断指定的文件描述符是否在集合中。
timeval 结构体
timeval结构体用于描述一段时间长度如果在这段时间内需要监听的文件描述符没有就绪则函数返回返回值为 0。timeval结构体的定义如下
/* A time value that is accurate to the nearestmicrosecond but also has a range of years. */
struct timeval{__time_t tv_sec; /* Seconds. */__suseconds_t tv_usec; /* Microseconds. */};理解socket就绪状态
读就绪 在socket内核中接收缓冲区中的字节数大于等于低水位标记SO_RCVLOWAT此时就可以无阻塞的读取该缓冲区并且读取的返回值大于 0监听的socket上有新的连接请求socket上有未处理的错误socket TCP通信中对端断开连接此时对该socket读则返回0。 写就绪 在socket内核中发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小)大于等于低水位标记SO_SNDLOWAT此时可以无阻塞的写并且返回值大于0。socket的写操作被关闭(close或者shutdown)。对一个写操作被关闭的socket进行写操作会触发SIGPIPE信号socket使用非阻塞connect连接成功或失败之后socket上有未处理的错误。 异常就绪 socket上收到带外数据 关于带外数据和TCP紧急模式相关。 4.3 select网络编程
在TCP服务器中监听socket获取新连接的本质需要先三次握手即客户端向服务端发送SYN连接请求。建立连接的本质其实也是IO操作。
一个建立好的连接我们称之为读事件就绪而listensocket 也只需要关心读事件就绪如果TCP服务器自己直接调用accept函数如果此时客户端发送连接的请求还没有就绪那么该进程就会阻塞式等待连接请求的数据就绪。并且建立连接后每次读写数据还需要等待数据达到缓冲区的最低水位线才进行数据拷贝这样势必也会导致服务器的性能低下。
因此我们可以把listenSock以及读写相关的sock交付给select()函数进行监管以下是一个利用select()函数实现的多路复用TCP服务器
简单封装Sock
#pragma once#include iostream
#include cstring
#include cstdlib
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.hclass Sock
{
public:static const int gbacklog 3;static int Socket(){int listenSock socket(PF_INET, SOCK_STREAM, 0);if(listenSock 0){exit(1);}//运行服务器快速重启int opt 1;setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, opt, sizeof(opt));return listenSock;}static void Bind(int sock, uint16_t port){struct sockaddr_in local;memset(local, 0, sizeof local);local.sin_family PF_INET;local.sin_addr.s_addr INADDR_ANY;local.sin_port htons(port);if(bind(sock, (const sockaddr*)local, sizeof local) 0){exit(2);}}static void Listen(int sock){if(listen(sock, gbacklog) 0){exit(3);}}static int Accept(int sock, std::string* clientIp, uint16_t* clientPort){struct sockaddr_in peer;socklen_t len sizeof(peer);int sock_fd accept(sock, (sockaddr*)peer, len);if(sock_fd 0){exit(4);}if(clientIp)*clientIp inet_ntoa(peer.sin_addr);if(clientPort)*clientPort ntohs(peer.sin_port);return sock_fd;}
};
服务端代码
#include iostream
#include sock.hpp
#include unistd.h
#include sys/select.husing namespace std;#define DEL -1 // 设置默认的文件描述符int fdsArray[sizeof(fd_set) * 8] {0}; // 保存所有合法的fd
int gnum sizeof(fdsArray) / sizeof(fdsArray[0]); // fdsArray中保存最多的fd个数// 打印fdsArray中的文件描述符
static void showArray(int arr[], int n)
{cout 当前合法的 sock list# ;for (int i 0; i n; i){if (arr[i] DEL)continue;elsecout arr[i] ;}cout endl;
}static void HandlerEvent(int listenSock, fd_set readfds)
{// 首先判断fdsArray中的文件描述符是listenSock还是读文件描述符并且过滤没有设置的文件描述符for (int i 0; i gnum; i){if (fdsArray[i] DEL)continue;if (i 0 fdsArray[i] listenSock){// 判断listenSock是否就绪if (FD_ISSET(listenSock, readfds)){cout 已经有一个新的连接请求就绪了需要接收连接请求 endl;string clientIp;uint16_t clientPort 0;int sock Sock::Accept(listenSock, clientIp, clientPort);if (sock 0){// 建立连接失败return;}cout 建立新连接成功 clientIp : clientPort | sock: sock endl;// 把新的sock托管给select设置进fdsArray数组int i 0;for (; i gnum; i){if (fdsArray[i] DEL)break;}if (i gnum){cerr 服务器已经达到上限无法同时保持更多的连接 endl;close(sock);}else{fdsArray[i] sock;showArray(fdsArray, gnum);}}}else // 处理普通的IO事件{if (FD_ISSET(fdsArray[i], readfds)){// 此时一个是一个普通合法的IO请求就绪了char buff[1024];// 存在bug因为此时不会阻塞如果数据量过大会导致读取数据不完整ssize_t s recv(fdsArray[i], buff, sizeof(buff), 0);if (s 0){buff[s] 0;cout clent[ fdsArray[i] ]# buff endl;}else if (s 0){cout client[ fdsArray[i] ] quit, server close! endl;close(fdsArray[i]);fdsArray[i] DEL;showArray(fdsArray, gnum);}else{// 该文件描述符异常cerr client[ fdsArray[i] ] error, server close! endl;close(fdsArray[i]);fdsArray[i] DEL;showArray(fdsArray, gnum);}}}}
}void usage(std::string process)
{cerr \nUsage: process [port]\n endl;
}int main(int argc, char *argv[])
{if (argc ! 2){usage(argv[0]);exit(-1);}int listenSock Sock::Socket();Sock::Bind(listenSock, atoi(argv[1]));Sock::Listen(listenSock);// 初始化fdsArrayfor (int i 0; i gnum; i)fdsArray[i] DEL;fdsArray[0] listenSock; // 默认第一个是listenSockwhile (true){// 每次重新调用select的时候都要重新设定参数int maxFd DEL;fd_set readfds;FD_ZERO(readfds);for (int i 0; i gnum; i){if (fdsArray[i] DEL)continue;FD_SET(fdsArray[i], readfds); // 将合法的fd设置进readfds// 更新最大fdif (fdsArray[i] maxFd)maxFd fdsArray[i];}timeval timeout {3, 0};int n select(maxFd 1, readfds, nullptr, nullptr, timeout);switch (n){case 0:cout time out ... (unsigned long)time(nullptr) endl;break;case -1:cerr errno : strerror(errno) endl;break;default:// 等待成功HandlerEvent(listenSock, readfds);break;}}return 0;
}
运行结果 4.4 setsockopt函数补充
在上面封装Sock的代码中使用到了setsockopt函数以下是对其的补充介绍
setsocketopt函数是一个用于设置套接字选项的系统调用函数。它可以用来设置套接字的各种选项例如超时缓冲区大小等。函数原型如下
#include sys/socket.hint setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);其中参数的含义如下
sockfd表示要设置的套接字描述符level表示要设置的选项所在的协议层。通常为SOL_SOCKET表示设置的是套接字级别的选项或者是某个具体协议的协议和optname表示要设置的选项名1具体的选项名取决于所设置的协议层optval表示要设置的选项的值是一个指向选项值的指针optlen表示要设置的选项的值的长度。
返回值
调用成功返回 0失败则返回 -1并且将错误信息设置进errno变量。
以下是一些常用的选项名和说明
SO_REUSEADDR允许在同一个端口上启动同一服务器的多个实例用于服务器程序重启后快速恢复到正常服务状态SO_REUSEPORT允许多个进程或线程在同一端口上绑定实现端口共享SO_KEEPALIVE开启TCP的keepalive机制检测连接是否仍然存活SO_RCVBUF和SO_SNDBUF设置套接字接收和发送缓冲区的大小TCP_NODELAY禁止Nagle算法即数据发送时不缓存等待其他数据直接发送。
例如下面的代码设置了套接字的超时时间为10秒
#include sys/socket.h
#include netinet/in.h
#include netinet/tcp.h
#include arpa/inet.hint sockfd socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct timeval timeout {10, 0};
setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, timeout, sizeof(timeout));4.5 select的特点
select()函数具有以下特点
可以同时监视多个文件描述符的状态变化实现了一次等待多个IO事件的机制避免了使用多个线程或进程进行IO操作时的复杂性和开销支持设置超时时间可以等待一段时间后返回避免了长时间等待IO事件而导致的程序阻塞可以同时等待多种IO事件的发生包括可读、可写和异常事件适用于各种类型的IO操作在等待IO事件的过程中进程会被阻塞直到有任意一个文件描述符就绪但是在等待的过程中其他进程可以继续执行因此可以提高程序的效率select()函数会修改传入的文件描述符集合所以需要每次调用select()后重新设置集合。
4.6 select的缺点
虽然select()函数是一种多路复用IO机制但是它也存在以下缺点
受限于文件描述符数量select()函数需要将所有需要监听的文件描述符都加入到一个文件描述符集合中并将这个集合传递给函数但在某些系统中文件描述符集合的大小是由限制的比如POSIX标准规定文件描述符集合的大小默认不能超过FD_SETSIZE一般是1024。需要不断轮询在等待IO事件的过程中select()函数需要轮询所有需要监听的文件描述符这样会浪费CPU资源并且导致调用方的延迟。当需要监听的文件描述符量很大时这个缺点尤为明显无法处理大量的连接当需要处理大量的连接时使用select()函数可能会遇到性能问题因为它需要在所有的连接直接进行切换当连接数量非常大时这个缺点尤为明显不方便扩展当需要添加或者删除监听的文件描述符时需要重新设置文件描述符集合并重新调用select()函数这样既会导致调用方的延迟还会浪费CPU资源。同时在某些系统中每次调用select()函数时都需要将文件描述符集合从用户空间复制到内核空间这也会导致一定的性能损失。
综上所述select() 函数虽然是一种多路复用IO机制但是在一些特定的场景下它可能会存在一些性能问题和限制因此需要根据具体的应用场景选择合适的多路复用机制。
五、poll
5.1 认识poll函数
poll函数和select函数一样也是用于实现IO多路复用的系统调用函数可以用于监视一组文件描述符中的任意一个变为可读、可写或者异常状态并返回就绪的文件描述符个数。在Linux系统中它可以替代阻塞式IO或者select函数提高程序的效率和性能。
poll函数原型如下
#include poll.hint poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数说明
fds指向pollfd结构体数组的指针每个pollfd结构体描述了一个待监视的文件描述符及其关注的事件nfdsfds数组中元素的数量timeout超时时间以毫秒为单位。如果设置为 “-1”poll函数将一直阻塞直到至少有一个文件描述符就绪。
函数返回值
返回值大于0就绪文件描述符的数量返回值等于0发生超时返回值小于0发生错误并且将错误信息存储到errno变量中。
pollfd结构体 pollfd结构体定义如下
struct pollfd {int fd; // 文件描述符short events; // 关注的事件short revents; // 实际发生的事件与 events 的取值相同或为其子集。
};
events和revents的取值
事件描述是否可作为输入是否可作为输出POLLIN数据可读包括普通和优先数据是是POLLRDNORM普通数据可读是是POLLRDBAND优先级带数据可读Linux不支持是是POLLOUT数据可写包括普通和优先数据是是POLLWRNORM普通数据可写是是POLLWRBAND优先级带数据可写是是POLLRDHUPTCP连接被对方关闭或者对方关闭了写操作。由GUN引入是是POLLERR错误否是POLLHUP挂起比如管道的写端被关闭后读端描述符上将收到POLLHUP事件否是POLLNAVL文件描述符没有打开否是
如果想要同时设置多个事件可以用按位或操作进行合并。
5.2 poll网络编程
和上文使用select函数编写的服务器代码一样只是将select函数替换成了poll函数。与select函数相比使用poll实现服务器的时候不需要再每次调用poll的时候又重新设置参数并且监视的文件描述符数量没有上限由我们自己决定。
#include iostream
#include sock.hpp
#include unistd.h
#include poll.husing namespace std;#define DEL -1 // 设置默认的文件描述符#define NUM 1024pollfd fdsArray[NUM]; // 保存所有合法的fd// 打印fdsArray中的文件描述符
static void showArray(pollfd arr[], int n)
{cout 当前合法的 sock list# ;for (int i 0; i n; i){if (arr[i].fd DEL)continue;elsecout arr[i].fd ;}cout endl;
}static void HandlerEvent(int listenSock)
{// 首先判断fdsArray中的文件描述符是listenSock还是读文件描述符并且过滤没有设置的文件描述符for (int i 0; i NUM; i){if (fdsArray[i].fd DEL)continue;if (i 0 fdsArray[i].fd listenSock){// 判断listenSock是否读就绪与POLLIN进行按位与运算if (fdsArray[i].revents POLLIN){cout 已经有一个新的连接请求就绪了需要接收连接请求 endl;string clientIp;uint16_t clientPort 0;int sock Sock::Accept(listenSock, clientIp, clientPort);if (sock 0){// 建立连接失败return;}cout 建立新连接成功 clientIp : clientPort | sock: sock endl;// 把新的sock托管给select设置进fdsArray数组int i 0;for (; i NUM; i){if (fdsArray[i].fd DEL)break;}if (i NUM){cerr 服务器已经达到上限无法同时保持更多的连接 endl;close(sock);}else{fdsArray[i].fd sock;fdsArray[i].events POLLIN;fdsArray[i].revents 0;showArray(fdsArray, NUM);}}}else // 处理普通的IO事件{if (fdsArray[i].revents POLLIN){// 此时一个是一个普通合法的IO请求就绪了char buff[1024];// 存在bug因为此时不会阻塞如果数据量过大会导致读取数据不完整ssize_t s recv(fdsArray[i].fd, buff, sizeof(buff), 0);if (s 0){buff[s] 0;cout clent[ fdsArray[i].fd ]# buff endl;}else if (s 0){cout client[ fdsArray[i].fd ] quit, server close! endl;close(fdsArray[i].fd);fdsArray[i].fd DEL;fdsArray[i].events 0;fdsArray[i].revents 0;showArray(fdsArray, NUM);}else{// 该文件描述符异常cerr client[ fdsArray[i].fd ] error, server close! endl;close(fdsArray[i].fd);fdsArray[i].fd DEL;fdsArray[i].events 0;fdsArray[i].revents 0;showArray(fdsArray, NUM);}}}}
}void usage(std::string process)
{cerr \nUsage: process [port]\n endl;
}int main(int argc, char *argv[])
{if (argc ! 2){usage(argv[0]);exit(-1);}int listenSock Sock::Socket();Sock::Bind(listenSock, atoi(argv[1]));Sock::Listen(listenSock);// 初始化fdsArrayfor (int i 0; i NUM; i){fdsArray[i].fd DEL;fdsArray[i].events 0;fdsArray[i].revents 0;}fdsArray[0].fd listenSock;fdsArray[0].events POLLIN; // listenSock只关心读操作// int timeout -1; // 设置为 -1poll函数将一直阻塞直到至少有一个文件描述符就绪int timeout 1000;while (true){// poll函数不需要每次重新设置参数int n poll(fdsArray, NUM, timeout);switch (n){case 0:cout time out ... (unsigned long)time(nullptr) endl;break;case -1:cerr errno : strerror(errno) endl;break;default:// 等待成功HandlerEvent(listenSock);break;}}return 0;
}
运行结果
5.3 poll函数的优点 支持同时监听多个文件描述符可以在一个 poll() 调用中监听多个 I/O 事件避免了多次系统调用。 能够监听复杂的 I/O 事件如对于一个 TCP 连接可以同时监听读和写事件。 poll() 没有最大文件描述符数的限制可以监听任意数量的文件描述符。 在处理大量文件描述符时poll() 的效率比 select() 更高并且代码实现起来更简单。
5.4 poll函数的缺点 调用时需要传入一个数组数组长度取决于需要监听的文件描述符数可能需要使用动态分配内存导致一定的额外开销。 poll() 不支持超时重连即当一个 I/O 事件发生时如果不立即处理下一次 poll() 调用将不会通知你。 poll() 函数是系统调用与内核交互需要额外的开销。 不是所有的操作系统都支持 poll()尤其是旧的操作系统。
六、epoll
6.1 认识epoll函数
epoll是Linux下一种高效的IO多路复用机制可用于管理大量的文件描述符能够处理大规模的并发连接比传统的 select 和 poll 函数更加高效。它几乎具备了前两者的一切优点被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll 可以分为以下三个函数 1、epoll_create() epoll_create()函数用于创建一个新的epoll实例它的参数size是一个整数表示需要监听的文件描述符数量。该函数返回一个整数类型的文件描述符表示新创建的epoll实例。
int epoll_create(int size);2、epoll_ctl() epoll_ctl()函数用于向epoll实例中添加、修改或删除文件描述符。它的参数epollfd是epoll实例的文件描述符op是要执行的操作类型fd是要添加、修改或删除的文件描述符event是要监听的事件类型。
int epoll_ctl(int epollfd, int op, int fd, struct epoll_event *event);其中op的值可以是以下三种之一
EPOLL_CTL_ADD添加文件描述符到epoll实例中。EPOLL_CTL_MOD修改已经添加到 epoll 实例中的文件描述符的监听事件。EPOLL_CTL_ADD从epoll实例中删除文件描述符。
struct epoll_event 结构体类型用于存储需要监听的事件类型和文件描述符它包含以下两个字段
typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64;
} epoll_data_t;struct epoll_event {uint32_t events; /* 监听的事件类型 */epoll_data_t data; /* 用户数据 */
};
其中events的值可以是以下几种
EPOLLIN 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭)。EPOLLOUT表示对应的文件描述符可以写。EPOLLPRI 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来)。EPOLLERR表示对应的文件描述符发生错误。EPOLLHUP表示对应的文件描述符被挂断。EPOLLET将EPOLL设为边缘触发(Edge Triggered)模式这是相对于水平触发(Level Triggered)来说的。EPOLLONESHOT只监听一次事件当监听完这次事件之后如果还需要继续监听这个socket的话需要再次把这个socket加入到EPOLL队列里。
3、epoll_wait() epoll_wait() 函数用于等待文件描述符上的事件发生。它的参数 epollfd 是 epoll 实例的文件描述符events 是一个数组用于存储发生事件的文件描述符。
int epoll_wait(int epollfd, struct epoll_event *events, int maxevents, int timeout);其中maxevents表示events数组的长度timeout表示等待事件的超时时间以毫秒为单位。如果timeout的值为 -1则表示一直等待直到有文件描述符就绪。
epoll_wait() 函数返回一个整数类型的值表示发生事件的文件描述符数量。
6.2 epoll工作原理
epoll之所以会比select和poll有更高的效率和可扩展性其原因在于它采用了以下三个重要的优化技术 1、采用红黑树作为事件的存储的数据结构
当某一进程调用epoll_create时Linux内核会创建一个eventpoll结构体这个结构体中有两个成员与epoll的使用方式密切相关。
struct eventpoll{..../*红黑树的根节点这颗树中存储着所有添加到epoll中的需要监控的事件*/struct rb_root rbr;/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/struct list_head rdlist;
....
};每一个epoll对象都有一个独立的eventpoll结构体用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂载在红黑树中如此重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn其中n为树的高度)。
2、采用事件回调机制 避免了在内核态和用户态之间的频繁切换当某个文件描述符上有事件发生时内核直接回调用户注册的回调函数通知应用程序。这个回调方法在内核中叫ep_poll_callback它会将发生的事件添加到rdlist双链表中。在epoll中对于每一个事件都会建立一个epitem结构体。
struct epitem
{struct rb_node rbn;//红黑树节点struct list_head rdllink;//双向链表节点struct epoll_filefd ffd; //事件句柄信息struct eventpoll *ep; //指向其所属的eventpoll对象struct epoll_event event; //期待发生的事件类型
}当调用epoll_wait检查是否有事件发生时只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空则把发生的事件复制到用户态同时将事件数量返回给用户。这个操作的时间复杂度是O(1)。
3、采用边缘触发模式 只有在文件描述符状态发生变化时才通知应用程序而不是像水平触发模式一样只要文件描述符上有数据就通知应用程序。这样可以避免应用程序在处理事件时漏掉某些数据减少CPU的无效操作。关于边缘触发模式和水平触发模式见下文。
epoll的工作流程可概括如下 应用程序调用epoll_create创建一个epoll实例获得一个文件描述符。 应用程序调用epoll_ctl向epoll实例中添加、修改或删除的文件描述符及其对应的事件。 内核根据添加的文件描述符建立一个红黑树并把文件描述符和其对应的事件节点加入到红黑树中。 应用程序调用epoll_wait阻塞等待事件发生当有文件描述符上的事件发生时epoll_wait返回事件列表。 应用程序处理事件列表处理完后回到第4步继续等待事件的发生。
6.3 epoll工作模式
epoll的工作模式有的两种LTLevel Triggered水平触发模式模式和 ETEdge Triggered边缘触发模式模式它们用于描述内核何时通知应用程序有关文件描述符的事件。
LT模式
在 LT 模式下当文件描述符就绪时epoll_wait 函数会返回并将该文件描述符加入到就绪队列中通知应用程序有数据可读或可写应用程序需要不断读取或写入数据直到文件描述符中没有数据可读或可写。如果应用程序没有对就绪的文件描述符进行操作则 epoll_wait 函数会一直阻塞等待。
ET模式
在 ET 模式下epoll_wait 函数仅在文件描述符状态发生改变时才会返回并将该文件描述符加入到就绪队列中通知应用程序有新的数据可读或可写。应用程序需要立即对就绪的文件描述符进行操作如果应用程序没有对就绪的文件描述符进行操作则 epoll_wait 函数不会再次返回。在 ET 模式下应用程序需要使用非阻塞 I/O 操作以避免因为某个文件描述符的阻塞 I/O 操作而导致阻塞其他文件描述符。
两种模式的对比 LT 模式更加简单易于理解和实现因为它类似于轮询。相比之下ET 模式更为复杂需要程序员具有更高的编程技能和经验。 LT模式适用于需要长时间读取或写入的文件描述符因为应用程序可以反复查询文件描述符是否已经准备好。相比之下ET模式更适用于需要实时响应的场景因为它只会在状态变化时通知应用程序。 在 LT 模式下内核通知应用程序有关文件描述符的事件时应用程序需要循环调用I/O函数而在 ET 模式下应用程序只需要在状态变化时调用一次I/O函数即可。 由于 ET 模式的事件处理方式更加实时因此它在高并发、高性能的场景中表现更好。相比之下LT 模式的轮询方式可能会导致效率降低。
ET模式为什么只支持非阻塞读写?
ET 模式只支持非阻塞 I/O 操作的原因是因为 ET 模式的工作方式是只在文件描述符上发生状态变化时通知应用程序。如果应用程序在 ET 模式下使用阻塞 I/O 操作例如读取或写入数据时阻塞在系统调用中那么即使文件描述符的状态已经发生了变化应用程序也无法感知到这个变化从而无法正确处理事件。这会导致应用程序的错误行为甚至可能导致死锁等问题。
因此在 ET 模式下应用程序必须使用非阻塞 I/O 操作以便在 epoll_wait 函数返回时及时对就绪的文件描述符进行操作。在非阻塞 I/O 操作中如果没有数据可读或可写读取或写入函数会立即返回并返回一个错误码例如 EAGAIN 或 EWOULDBLOCK应用程序需要根据错误码来确定是否继续等待数据可读或可写或者是否进行其他操作。这种方式可以避免应用程序因为某个文件描述符的阻塞 I/O 操作而导致阻塞其他文件描述符从而提高系统的并发处理能力。
需要注意的是在使用 ET 模式时应用程序需要处理 EAGAIN 或 EWOULDBLOCK 错误码这种错误码是非阻塞 I/O 操作的正常情况。如果应用程序没有正确处理这些错误码可能会导致应用程序的异常行为。
6.4 epoll网络编程
这里实现的服务器功能与前面使用select函数和poll实现的服务器功能一样只是对服务器代码进行了简单的封装
#pragma once#include iostream
#include string
#include functional
#include cstdlib
#include sock.hpp
#include unistd.h
#include Log.hpp
#include sys/epoll.hclass EpollServer
{using func_t std::functionint(int); // 回调函数static const int gsize 128; // 最大文件描述符数量static const int num 256; // event数组长度public:EpollServer(uint16_t port, func_t func) : _port(port), _func(func), _listensock(-1), _epfd(-1){Init();}~EpollServer(){if (_listensock ! -1)close(_listensock);if (_epfd ! -1)close(_epfd);}void Init(){_listensock Sock::Socket();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);// 创建epoll实例_epfd epoll_create(gsize);if (_epfd 0){logMsg(FATAL, %d:%s, errno, strerror(errno));exit(3);}logMsg(DEBUG, 创建监听套接字成功: %d, _listensock);logMsg(DEBUG, 创建epoll实例成功: %d, _epfd);}void HandlerEvent(epoll_event revs[], int n){for (int i 0; i n; i){int sock revs[i].data.fd;uint32_t revent revs[i].events;if (revent EPOLLIN) // 读事件就绪{if (sock _listensock){// listensockstd::string clientip;uint16_t clientport 0;// 监听socket就绪获取新连接int sockfd Sock::Accept(_listensock, clientip, clientport);if (sockfd 0){logMsg(FATAL, %d:%s, errno, strerror(errno));continue;}// 托管给epollepoll_event ev;ev.data.fd sockfd;ev.events EPOLLIN;int n epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, ev);assert(n 0);(void)n;}else{// 普通IOint n _func(sock);if (n 0 || n 0){// 先移除再关闭int n epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);assert(n 0);(void)n;logMsg(DEBUG, client quit: %d, sock);close(sock);}}}else{//...}}}void Run(){// 1. 首先添加listensock到epollepoll_event ev;ev.data.fd _listensock;ev.events EPOLLIN;int n epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock, ev);assert(n 0);(void)n;epoll_event revs[num];int timeout 10000;while (true){int n epoll_wait(_epfd, revs, num, timeout);switch (n){case 0:std::cout time out ... (unsigned long)time(nullptr) std::endl;break;case -1:std::cerr errno : strerror(errno) std::endl;break;default:// 等待成功HandlerEvent(revs, n);break;}}}private:int _listensock;int _epfd;int _port;func_t _func;
};运行结果
6.5 epoll的优点
相比于select和pollepoll在性能和功能上有许多优点 高性能epoll使用红黑树作为事件存储的数据结构可以快速地添加、删除和查找事件。而select和poll使用线性列表存储事件每次查找事件都需要遍历整个列表效率低下。 高并发epoll使用事件通知机制只有在事件发生时才会通知应用程序可以避免轮询的开销减少系统调用次数同时支持多个文件描述符的并发操作。 可扩展性epoll支持水平触发和边缘触发两种模式可以根据不同场景灵活选择。而select和poll只支持水平触发模式。 内存占用低epoll通过事件通知机制避免了轮询的开销同时只需要存储活动的文件描述符相比之下select和poll需要存储全部的文件描述符。 更好的可读性epoll使用事件驱动的编程模型可以更清晰地描述应用程序的行为代码更加可读性和易于维护。
总的来说epoll在性能和可扩展性方面具有明显优势适用于高并发、高性能的网络编程场景。而select和poll在简单的网络编程场景下也可以使用但在处理大量的并发连接时效率会明显下降。 文章转载自: http://www.morning.gftnx.cn.gov.cn.gftnx.cn http://www.morning.clybn.cn.gov.cn.clybn.cn http://www.morning.rxlck.cn.gov.cn.rxlck.cn http://www.morning.tndxg.cn.gov.cn.tndxg.cn http://www.morning.yixingshengya.com.gov.cn.yixingshengya.com http://www.morning.pghgq.cn.gov.cn.pghgq.cn http://www.morning.stfdh.cn.gov.cn.stfdh.cn http://www.morning.jgnst.cn.gov.cn.jgnst.cn http://www.morning.bangaw.cn.gov.cn.bangaw.cn http://www.morning.flhnd.cn.gov.cn.flhnd.cn http://www.morning.ssglh.cn.gov.cn.ssglh.cn http://www.morning.fqpyj.cn.gov.cn.fqpyj.cn http://www.morning.yuanshenglan.com.gov.cn.yuanshenglan.com http://www.morning.hwcln.cn.gov.cn.hwcln.cn http://www.morning.rzcfg.cn.gov.cn.rzcfg.cn http://www.morning.rqknq.cn.gov.cn.rqknq.cn http://www.morning.qdlr.cn.gov.cn.qdlr.cn http://www.morning.zcckq.cn.gov.cn.zcckq.cn http://www.morning.ruifund.com.gov.cn.ruifund.com http://www.morning.cbchz.cn.gov.cn.cbchz.cn http://www.morning.dgsx.cn.gov.cn.dgsx.cn http://www.morning.kjtdy.cn.gov.cn.kjtdy.cn http://www.morning.bfcrp.cn.gov.cn.bfcrp.cn http://www.morning.cwskn.cn.gov.cn.cwskn.cn http://www.morning.qmnhw.cn.gov.cn.qmnhw.cn http://www.morning.dgsr.cn.gov.cn.dgsr.cn http://www.morning.rlbg.cn.gov.cn.rlbg.cn http://www.morning.nkyc.cn.gov.cn.nkyc.cn http://www.morning.ltksw.cn.gov.cn.ltksw.cn http://www.morning.xknmn.cn.gov.cn.xknmn.cn http://www.morning.qxycf.cn.gov.cn.qxycf.cn http://www.morning.rbxsk.cn.gov.cn.rbxsk.cn http://www.morning.rqwwm.cn.gov.cn.rqwwm.cn http://www.morning.htfnz.cn.gov.cn.htfnz.cn http://www.morning.tongweishi.cn.gov.cn.tongweishi.cn http://www.morning.glncb.cn.gov.cn.glncb.cn http://www.morning.thrgp.cn.gov.cn.thrgp.cn http://www.morning.hlxxl.cn.gov.cn.hlxxl.cn http://www.morning.sgwr.cn.gov.cn.sgwr.cn http://www.morning.mqbsm.cn.gov.cn.mqbsm.cn http://www.morning.pbmkh.cn.gov.cn.pbmkh.cn http://www.morning.zwyuan.com.gov.cn.zwyuan.com http://www.morning.fhqsm.cn.gov.cn.fhqsm.cn http://www.morning.knmp.cn.gov.cn.knmp.cn http://www.morning.gbkkt.cn.gov.cn.gbkkt.cn http://www.morning.gtnyq.cn.gov.cn.gtnyq.cn http://www.morning.tqfnf.cn.gov.cn.tqfnf.cn http://www.morning.grpfj.cn.gov.cn.grpfj.cn http://www.morning.lsmnn.cn.gov.cn.lsmnn.cn http://www.morning.nfzzf.cn.gov.cn.nfzzf.cn http://www.morning.gthwr.cn.gov.cn.gthwr.cn http://www.morning.wdhzk.cn.gov.cn.wdhzk.cn http://www.morning.nqmdc.cn.gov.cn.nqmdc.cn http://www.morning.cgdyx.cn.gov.cn.cgdyx.cn http://www.morning.xrlwr.cn.gov.cn.xrlwr.cn http://www.morning.jwgmx.cn.gov.cn.jwgmx.cn http://www.morning.yzxlkj.com.gov.cn.yzxlkj.com http://www.morning.nrjr.cn.gov.cn.nrjr.cn http://www.morning.lzzqz.cn.gov.cn.lzzqz.cn http://www.morning.phjny.cn.gov.cn.phjny.cn http://www.morning.bfjyp.cn.gov.cn.bfjyp.cn http://www.morning.qymqh.cn.gov.cn.qymqh.cn http://www.morning.rshs.cn.gov.cn.rshs.cn http://www.morning.yjdql.cn.gov.cn.yjdql.cn http://www.morning.blfgh.cn.gov.cn.blfgh.cn http://www.morning.yrcxg.cn.gov.cn.yrcxg.cn http://www.morning.kmwbq.cn.gov.cn.kmwbq.cn http://www.morning.mbnhr.cn.gov.cn.mbnhr.cn http://www.morning.jpkk.cn.gov.cn.jpkk.cn http://www.morning.rbffj.cn.gov.cn.rbffj.cn http://www.morning.zwsgl.cn.gov.cn.zwsgl.cn http://www.morning.tturfsoc.com.gov.cn.tturfsoc.com http://www.morning.rgpbk.cn.gov.cn.rgpbk.cn http://www.morning.drbd.cn.gov.cn.drbd.cn http://www.morning.khpgd.cn.gov.cn.khpgd.cn http://www.morning.mpbgy.cn.gov.cn.mpbgy.cn http://www.morning.smpb.cn.gov.cn.smpb.cn http://www.morning.xdlwm.cn.gov.cn.xdlwm.cn http://www.morning.rnqrl.cn.gov.cn.rnqrl.cn http://www.morning.hbnwr.cn.gov.cn.hbnwr.cn