目录
poll初识
poll函数
poll服务器
poll的优点
poll的缺点
poll也是系统提供的一个多路转接接口。
poll函数
poll函数的函数原型如下:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数说明:
参数timeout的取值:
返回值说明:
poll调用失败时,错误码可能被设置为:
EFAULT
:fds数组不包含在调用程序的地址空间中。EINTR
:此调用被信号所中断。EINVAL
:nfds值超过RLIMIT_NOFILE值。ENOMEM
:核心内存不足。struct pollfd结构
struct pollfd结构当中包含三个成员:
events和revents的取值:
这些取值实际都是以宏的方式进行定义的,它们的二进制序列当中有且只有一个比特位是1,且为1的比特位是各不相同的。
poll的工作流程和select是基本类似的,这里我们也实现一个简单poll服务器,该服务器也只是读取客户端发来的数据并进行打印。
PollServer类
PollServer类当中也只需要包含监听套接字和端口号两个成员变量,在poll服务器绑定时直接将IP地址设置为INADDR_ANY尽即可。
代码如下:
#pragma once
#include "socket.hpp"
#include
#define BACK_LOG 5
class 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函数,当事件就绪时对应执行某种动作即可。
代码如下:
#pragma once
#include "socket.hpp"
#include
#define BACK_LOG 5
#define NUM 1024
#define DFL_FD - 1
class 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<<"有事件发生..."<
事件处理
当poll检测到有文件描述符的读事件就绪,就会在其对应的struct pollfd结构中的revents成员中添加读事件并返回,接下来poll服务器就应该对就绪事件进行处理了,事件处理过程如下:
代码如下:
#pragma once
#include "socket.hpp"
#include
#define BACK_LOG 5
#define NUM 1024
#define DFL_FD - 1
class 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_sock&&fds[i].revents&POLLIN){ //连接事件就绪
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].revents&POLLIN){ //读事件就绪
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;
}
};
说明一下:
poll服务器测试
运行poll服务器时也需要先实例化出一个PollServer对象,对poll服务器进行初始化后就可以运行服务器了。
代码如下:
#include "poll_server.hpp"
#include
static 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数组当中清除。
说明一下: