常熟市住房建设局网站,南京建设个人网站,建站行业的发展前景,好看的网站建设目录
poll初识
poll函数
poll服务器
poll的优点
poll的缺点 poll初识
poll也是系统提供的一个多路转接接口。
poll系统调用也可以让我们的程序同时监视多个文件描述符上的事件是否就绪#xff0c;和select的定位是一样的#xff0c;适用场景也是一样的。
poll函数 po…目录
poll初识
poll函数
poll服务器
poll的优点
poll的缺点 poll初识
poll也是系统提供的一个多路转接接口。
poll系统调用也可以让我们的程序同时监视多个文件描述符上的事件是否就绪和select的定位是一样的适用场景也是一样的。
poll函数 poll函数 poll函数的函数原型如下
int poll(struct pollfd *fds, nfds_t nfds, int timeout);参数说明
fds一个poll函数监视的结构列表每一个元素包含三部分内容文件描述符、监视的事件集合、就绪的事件集合。nfds表示fds数组的长度。timeout表示poll函数的超时时间单位是毫秒ms。
参数timeout的取值
-1poll调用后进行阻塞等待直到被监视的某个文件描述符上的某个事件就绪。0poll调用后进行非阻塞等待无论被监视的文件描述符上的事件是否就绪poll检测后都会立即返回。特定的时间值poll调用后在指定的时间内进行阻塞等待如果被监视的文件描述符上一直没有事件就绪则在该时间后poll进行超时返回。
返回值说明
如果函数调用成功则返回有事件就绪的文件描述符个数。如果timeout时间耗尽则返回0。如果函数调用失败则返回-1同时错误码会被设置。
poll调用失败时错误码可能被设置为
EFAULTfds数组不包含在调用程序的地址空间中。EINTR此调用被信号所中断。EINVALnfds值超过RLIMIT_NOFILE值。ENOMEM核心内存不足。 struct pollfd结构 struct pollfd结构当中包含三个成员
fd特定的文件描述符若设置为负值则忽略events字段并且revents字段返回0。events需要监视该文件描述符上的哪些事件。reventspoll函数返回时告知用户该文件描述符上的哪些事件已经就绪。 events和revents的取值 这些取值实际都是以宏的方式进行定义的它们的二进制序列当中有且只有一个比特位是1且为1的比特位是各不相同的。 因此在调用poll函数之前可以通过或运算符将要监视的事件添加到events成员当中。在poll函数返回后可以通过与运算符检测revents成员中是否包含特定事件以得知对应文件描述符的特定事件是否就绪。
poll服务器
poll的工作流程和select是基本类似的这里我们也实现一个简单poll服务器该服务器也只是读取客户端发来的数据并进行打印。 PollServer类 PollServer类当中也只需要包含监听套接字和端口号两个成员变量在poll服务器绑定时直接将IP地址设置为INADDR_ANY尽即可。
在构造PollServer对象时需要指明poll服务器的端口号当然也可以在初始化poll服务器的时候指明。在初始化poll服务器的时候调用Socket类当中的函数依次进行套接字的创建、绑定和监听即可这里的Socket类和之前实现的一模一样。在析构函数中可以选择调用close函数将监听套接字进行关闭但实际也可以不进行该动作因为服务器运行后一般是不退出的。
代码如下
#pragma once#include socket.hpp
#include poll.h#define BACK_LOG 5class PollServer{
private:int _listen_sock; //监听套接字int _port; //端口号
public:PollServer(int port): _port(port){}void InitPollServer(){_listen_sock Socket::SocketCreate();Socket::SocketBind(_listen_sock, _port);Socket::SocketListen(_listen_sock, BACK_LOG);}~PollServer(){if (_listen_sock 0){close(_listen_sock);}}
};运行服务器 服务器初始化完毕后就可以开始运行了而poll服务器要做的就是不断调用poll函数当事件就绪时对应执行某种动作即可。
首先在poll服务器开始死循环调用poll函数之前需要定义一个fds数组该数组当中的每个位置都是一个struct pollfd结构后续调用poll函数时会作为参数进行传入。先将fds数组当中每个位置初始化为无效并将监听套接字添加到fds数组当中表示服务器刚开始运行时只需要监视监听套接字的读事件。此后poll服务器就不断调用poll函数监视读事件是否就绪。如果poll函数的返回值大于0则说明poll函数调用成功此时已经有文件描述符的读事件就绪接下来就应该对就绪事件进行处理。如果poll函数的返回值等于0则说明timeout时间耗尽此时直接准备进行下一次poll调用即可。如果poll函数的返回值为-1则说明poll调用失败此时也让服务器准备进行下一次poll调用但实际应该进一步判断错误码根据错误码来判断是否应该继续调用poll函数。
代码如下
#pragma once#include socket.hpp
#include poll.h#define BACK_LOG 5
#define NUM 1024
#define DFL_FD - 1class PollServer{
private:int _listen_sock; //监听套接字int _port; //端口号
public:void Run(){struct pollfd fds[NUM];ClearPollfds(fds, NUM, DFL_FD); //清空数组中的所有位置SetPollfds(fds, NUM, _listen_sock); //将监听套接字添加到数组中并关心其读事件for (;;){switch (poll(fds, NUM, -1)){case 0:std::cout timeout... std::endl;break;case -1:std::cerr poll error std::endl;break;default://正常的事件处理//std::cout有事件发生...std::endl;HandlerEvent(fds, NUM);break;}}}
private:void ClearPollfds(struct pollfd fds[], int num, int default_fd){for (int i 0; i num; i){fds[i].fd default_fd;fds[i].events 0;fds[i].revents 0;}}bool SetPollfds(struct pollfd fds[], int num, int fd){for (int i 0; i num; i){if (fds[i].fd DFL_FD){ //该位置没有被使用fds[i].fd fd;fds[i].events | POLLIN; //添加读事件到events当中return true;}}return false; //fds数组已满}
};事件处理 当poll检测到有文件描述符的读事件就绪就会在其对应的struct pollfd结构中的revents成员中添加读事件并返回接下来poll服务器就应该对就绪事件进行处理了事件处理过程如下
首先遍历fds数组中的每个struct pollfd结构如果该结构当中的fd有效且revents当中包含读事件则说明该文件描述符的读事件就绪接下来就需要进一步判断该文件描述符是监听套接字还是与客户端建立的套接字。如果是监听套接字的读事件就绪则调用accept函数将底层建立好的连接获取上来并将获取到的套接字添加到fds数组当中表示下一次调用poll函数时需要监视该套接字的读事件。如果是与客户端建立的连接对应的读事件就绪则调用read函数读取客户端发来的数据并将读取到的数据在服务器端进行打印。如果在调用read函数时发现客户端将连接关闭或read函数调用失败则poll服务器也直接关闭对应的连接并将该连接对应的文件描述符从fds数组当中清除表示下一次调用poll函数时无需再监视该套接字的读事件。
代码如下
#pragma once#include socket.hpp
#include poll.h#define BACK_LOG 5
#define NUM 1024
#define DFL_FD - 1class PollServer{
private:int _listen_sock; //监听套接字int _port; //端口号
public:void HandlerEvent(struct pollfd fds[], int num){for (int i 0; i num; i){if (fds[i].fd DFL_FD){ //跳过无效的位置continue;}if (fds[i].fd _listen_sockfds[i].reventsPOLLIN){ //连接事件就绪struct sockaddr_in peer;memset(peer, 0, sizeof(peer));socklen_t len sizeof(peer);int sock accept(_listen_sock, (struct sockaddr*)peer, len);if (sock 0){ //获取连接失败std::cerr accept error std::endl;continue;}std::string peer_ip inet_ntoa(peer.sin_addr);int peer_port ntohs(peer.sin_port);std::cout get a new link[ peer_ip : peer_port ] std::endl;if (!SetPollfds(fds, NUM, sock)){ //将获取到的套接字添加到fds数组中并关心其读事件close(sock);std::cout poll server is full, close fd: sock std::endl;}}else if (fds[i].reventsPOLLIN){ //读事件就绪char buffer[1024];ssize_t size read(fds[i].fd, buffer, sizeof(buffer)-1);if (size 0){ //读取成功buffer[size] \0;std::cout echo# buffer std::endl;}else if (size 0){ //对端连接关闭std::cout client quit std::endl;close(fds[i].fd);UnSetPollfds(fds, i); //将该文件描述符从fds数组中清除}else{std::cerr read error std::endl;close(fds[i].fd);UnSetPollfds(fds, i); //将该文件描述符从fds数组中清除}}}}
private:bool SetPollfds(struct pollfd fds[], int num, int fd){for (int i 0; i num; i){if (fds[i].fd DFL_FD){ //该位置没有被使用fds[i].fd fd;fds[i].events | POLLIN; //添加读事件到events当中return true;}}return false; //fds数组已满}void UnSetPollfds(struct pollfd fds[], int pos){fds[pos].fd DFL_FD;fds[pos].events 0;fds[pos].revents 0;}
};说明一下
因为这里将fds数组的大小是固定设置的因此在将新获取连接对应的文件描述符添加到fds数组时可能会因为fds数组已满而添加失败这时poll服务器只能将刚刚获取上来的连接对应的套接字进行关闭。 poll服务器测试 运行poll服务器时也需要先实例化出一个PollServer对象对poll服务器进行初始化后就可以运行服务器了。
代码如下
#include poll_server.hpp
#include stringstatic void Usage(std::string proc)
{std::cerr Usage: proc port std::endl;
}int main(int argc, char* argv[])
{if (argc ! 2){Usage(argv[0]);exit(1);}int port atoi(argv[1]);PollServer* svr new PollServer(port);svr-InitPollServer();svr-Run();return 0;
}因为我们编写的poll服务器在调用poll函数时将timeout的值设置成了-1因此运行服务器后如果没有客户端发来连接请求那么服务器就会在调用poll函数后进行阻塞等待。 当我们用telnet工具连接poll服务器后poll服务器调用的poll函数在检测到监听套接字的读事件就绪后就会调用accept获取建立好的连接并打印输出客户端的IP和端口号此时客户端发来的数据也能够成功被poll服务器收到并进行打印输出。 此外poll服务器也是一个单进程服务器但是它也可以同时为多个客户端提供服务。 当服务器端检测到客户端退出后也会关闭对应的连接并将对应的套接字从fds数组当中清除。 poll的优点
struct pollfd结构当中包含了events和revents相当于将select的输入输出型参数进行分离因此在每次调用poll之前不需要像select一样重新对参数进行设置。poll可监控的文件描述符数量没有限制。当然poll也可以同时等待多个文件描述符能够提高IO的效率。
说明一下
虽然代码中将fds数组的元素个数定义为1024但fds数组的大小是可以继续增大的poll函数能够帮你监视多少个文件描述符是由传入poll函数的第二个参数决定的。而fd_set类型只有1024个比特位因此select函数最多只能监视1024个文件描述符。
poll的缺点
和select函数一样当poll返回后需要遍历fds数组来获取就绪的文件描述符。每次调用poll都需要把大量的struct pollfd结构从用户态拷贝到内核态这个开销也会随着poll监视的文件描述符数目的增多而增大。同时每次调用poll都需要在内核遍历传递进来的所有fd这个开销在fd很多时也很大。