文章目录
- 1、认识poll接口
- 2、实现
- 3、特点
1、认识poll接口
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// pollfd结构
struct pollfd
{
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
poll返回值和select一样,都是int,表示就绪的fd数量。timeout是一个输入型参数,单位是毫秒ms,为0表示非阻塞,小于0表示阻塞,大于0poll在这段时间内阻塞等待,如果一直没有事件就绪,那么超过时间就返回0。fds相当于一个数组,events是用户关心的fd上的事件,revents是内核告诉用户,关心的fd中哪些已经有事件就绪。
poll分离了输入参数和输出参数,这样就不需要在调用poll时进行重新设置了。
上图的事件其实都是宏,POLLIN表示读事件就绪,POLLOUT表示写事件就绪。
2、实现
基于上一篇select的代码来实现,代码只用到能够等待多个fd那里。基本代码
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/poll.h>
#include "Sock.hpp"
#include "log.hpp"
#include "err.hpp"
const static int gport = 8888;
typedef int type_t;
class PollServer
{
static const int N = (sizeof(fd_set) * 8);
public:
PollServer(uint16_t port = gport) : port_(port)
{
}
void InitServer()
{
listensock_.Socket();
listensock_.Bind(port_);
listensock_.Listen();
for (int i = 0; i < N; i++)
fdarray_[i] = defaultfd;
}
void Accepter()
{
std::string clientip;
uint16_t clientport;
int sock = listensock_.Accept(&clientip, &clientport);
if (sock < 0)
return;
logMessage(Debug, "[%s:%d], sock: %d", clientip.c_str(), clientport, sock);
int pos = 1;
for (; pos < N; pos++)
{
if (fdarray_[pos] == defaultfd)
break;
}
if (pos >= N)
{
close(sock);
logMessage(Warning, "sockfd array[] full");
}
else
{
fdarray_[pos] = sock;
}
}
void HandlerEvent(fd_set &rfds)
{
for (int i = 0; i < N; i++)
{
if (fdarray_[i] == defaultfd)
continue;
if ((fdarray_[i] == listensock_.Fd()) && FD_ISSET(listensock_.Fd(), &rfds))
{
Accepter();
}
else if ((fdarray_[i] != listensock_.Fd()) && FD_ISSET(fdarray_[i], &rfds))
{
int fd = fdarray_[i];
char buffer[1024];
ssize_t s = recv(fd, buffer, sizeof(buffer) - 1, 0);
if (s > 0)
{
buffer[s-1] = 0;
std::cout << "client# " << buffer << std::endl;
std::string echo = buffer;
echo += " [select server echo]";
send(fd, echo.c_str(), echo.size(), 0);
}
else
{
if (s == 0)
logMessage(Info, "client quit ..., fdarray_[i] -> defaultfd: %d->%d", fd, defaultfd);
else
logMessage(Warning, "recv error, client quit ..., fdarray_[i] -> defaultfd: %d->%d", fd, defaultfd);
close(fdarray_[i]);
fdarray_[i] = defaultfd;
}
}
}
}
void Start()
{
fdarray_[0] = listensock_.Fd();
while (true)
{
fd_set rfds;
FD_ZERO(&rfds);
int maxfd = fdarray_[0];
for (int i = 0; i < N; i++)
{
if (fdarray_[i] == defaultfd)
continue;
// 合法fd
FD_SET(fdarray_[i], &rfds);
if (maxfd < fdarray_[i])
maxfd = fdarray_[i];
}
int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);
switch (n)
{
case 0:
logMessage(Debug, "timeout, %d: %s", errno, strerror(errno));
break;
case -1:
logMessage(Warning, "%d: %s", errno, strerror(errno));
break;
default:
logMessage(Debug, "有一个就绪事件发生了: %d", n);
HandlerEvent(rfds);
DebugPrint();
break;
}
}
}
void DebugPrint()
{
std::cout << "fdarray[]: ";
for (int i = 0; i < N; i++)
{
if (fdarray_[i] == defaultfd)
continue;
std::cout << fdarray_[i] << " ";
}
std::cout << "\n";
}
~PollServer()
{
listensock_.Close();
}
private:
uint16_t port_;
Sock listensock_;
type_t fdarray_[N];
};
改一下数组类型
typedef struct pollfd type_t;
type_t* fdarray_;
在构造函数那里初始化为nullptr,然后在初始化函数初始化
const static int gport = 8888;
const static int N = 4096;
const static short defaultevent = 0;
typedef struct pollfd type_t;
class PollServer
{
public:
PollServer(uint16_t port = gport) : port_(port), fdarray_(nullptr)
{
}
void InitServer()
{
listensock_.Socket();
listensock_.Bind(port_);
listensock_.Listen();
fdarray_ = new type_t[N];
for (int i = 0; i < N; i++)
{
fdarray_[i].fd = defaultfd;
fdarray_[i].events = defaultevent;
fdarray_[i].revents = defaultevent;
}
}
~PollServer()
{
listensock_.Close();
if(fdarray_) delete []fdarray_;//判断不为空再delete
}
start函数里这些不再需要
fd_set rfds;
FD_ZERO(&rfds);
int maxfd = fdarray_[0].fd;
for (int i = 0; i < N; i++)
{
if (fdarray_[i] == defaultfd)
continue;
// 合法fd
FD_SET(fdarray_[i], &rfds);
if (maxfd < fdarray_[i])
maxfd = fdarray_[i];
}
void Start()
{
fdarray_[0].fd = listensock_.Fd();
fdarray_[0].events = POLLIN;
while (true)
{
int timeout = 1000;
int n = poll(fdarray_, N, timeout);
switch (n)
{
case 0:
logMessage(Debug, "timeout, %d: %s", errno, strerror(errno));
break;
case -1:
logMessage(Warning, "%d: %s", errno, strerror(errno));
break;
default:
logMessage(Debug, "有一个就绪事件发生了: %d", n);
HandlerEvent(rfds);
DebugPrint();
break;
}
}
}
poll函数那里,第二个参数可以不传N,把要管理的fd都放到fdarray_最左侧排起来,第二个参数就可以只看这几个fd的个数。
其它函数更改一下
void Accepter()
{
std::string clientip;
uint16_t clientport;
int sock = listensock_.Accept(&clientip, &clientport);
if (sock < 0)
return;
logMessage(Debug, "[%s:%d], sock: %d", clientip.c_str(), clientport, sock);
int pos = 1;
for (; pos < N; pos++)
{
if (fdarray_[pos].fd == defaultfd)
break;
}
if (pos >= N)
{
//可以先动态扩容,扩容失败再close
close(sock);
logMessage(Warning, "sockfd array[] full");
}
else
{
fdarray_[pos].fd = sock;
fdarray_[pos].events = POLLIN;//可以设置成POLLIN / POLLOUT,也就是读写事件都关心
fdarray_[pos].revents = defaultevent;
}
}
void HandlerEvent()
{
for (int i = 0; i < N; i++)
{
int fd = fdarray_[i].fd;
short revent = fdarray_[i].events;
if (fd == defaultfd)
continue;
if ((fd == listensock_.Fd()) && (revent & POLLIN))//fd是需要的fd且读数据就绪
{
Accepter();
}
else if ((fd != listensock_.Fd()) && (revent & POLLIN))//fd不是我们要的,但读数据就绪
{
int fd = fdarray_[i].fd;
char buffer[1024];
ssize_t s = recv(fd, buffer, sizeof(buffer) - 1, 0);
if (s > 0)
{
buffer[s-1] = 0;
std::cout << "client# " << buffer << std::endl;
std::string echo = buffer;
echo += " [select server echo]";
send(fd, echo.c_str(), echo.size(), 0);
}
else
{
if (s == 0)
logMessage(Info, "client quit ..., fdarray_[i] -> defaultfd: %d->%d", fd, defaultfd);
else
logMessage(Warning, "recv error, client quit ..., fdarray_[i] -> defaultfd: %d->%d", fd, defaultfd);
close(fd);
fdarray_[i].fd = defaultfd;
fdarray_[i].events = defaultevent;
fdarray_[i].revents = defaultevent;
}
}
}
}
makefile
pollserver:main.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f pollserver
main.cc
#include "PollServer.hpp"
#include <memory>
int main()
{
//fd_set fd;
//std::cout << sizeof(fd) << std::endl;
std::unique_ptr<PollServer> svr(new PollServer());
svr->InitServer();
svr->Start();
return 0;
}
timeout改成-1就是阻塞了。现在这个服务器支持读,如果要支持写,在HandlerEvent函数里
if (s > 0)
{
buffer[s-1] = 0;
std::cout << "client# " << buffer << std::endl;
fdarray_[i].events |= POLLOUT;//也关心写事件
std::string echo = buffer;
echo += " [select server echo]";
send(fd, echo.c_str(), echo.size(), 0);
}
3、特点
poll虽然相对于select简单了好多,不过poll也是以数组形式传多个fd,让操作系统去遍历所有fd,所以对于底层的消耗和select一样。但poll解决了fd上限少的问题,数组多大是用户决定的,而select自己就决定了一个fd_set,让用户只得用fd_set。当用户定的数组太大时,操作系统去遍历,效率也低。所以poll对于select,虽然看上去简单了一些,但放到实际中,文件逐渐增多,两者都不怎么样,不过poll写起来更简单。
select可以跨平台,poll不行。
本篇gitee
结束。