IO多路转接之poll
@[toc]
一、Poll
- 1.函数原型:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数 | 作用 |
---|---|
struct pollfd *fds | fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合 |
nfds_t nfds | nfds表示fds数组的长度 |
timeout | timeout表示poll函数的超时时间, 单位是毫秒(ms) |
返回值
- 返回值小于0, 表示出错;
- 返回值等于0, 表示poll函数等待超时;
- 返回值大于0, 表示poll由于监听的文件描述符就绪而返回
- 2.理解struct pollfd *fds
// pollfd结构
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
其中enent和revent的取值(主要取值),不清楚可以找Man
|取值|描述 |
|--|--|
| POLLIN| 数据可读(普通数据和优先级数据)
|POLLOUT|数据可写(普通数据和优先级数据)
|POLLERR|错误
-
3.socket就绪条件
-
读就绪
-
sochet内核中,接收缓冲区中的字节数,大于等于低水位标记SO_RECVLOWAT,此时可以无阻塞的读该文件描述符,并且返回值大于0
-
socket TCP通信中,对端关闭连接,此时对该socket读,则返回0 监听的socket上有新的连接请求时
-
socket上有未处理的错误时
-
写就绪
-
socket内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小),大于等于低水位标记,SO_SNDLOWAT,此时可以无阻塞的写,并且返回值大于0
-
socket的写操作被关闭(close或者shutdown),对于一个写操作被关闭的socket进行写操作,会触发SIGPIPE信号
-
socket使用非阻塞connect连接成功或失败之后 socket上有未读取的错误
-
异常就绪
-
socket上收到带外数据(关于带外数据,和TCP紧急模式相关,在TCP报头中有一个紧急指针的字段)
-
4.poll的优点及缺点
poll的优点
- 不同与select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现.pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式. 接口使用比 select更方便.
- poll并没有最大数量限制 (但是数量过大后性能也是会下降).
poll的缺点
- poll中监听的文件描述符数目增多时 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符.
- 每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中. 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长, 其效 率也会线性下降
- 5.基于poll实现tcp服务器(多用户)
#pragma once
#include <functional>
#include "tcp_socket.hpp"
#include <poll.h>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <string>
#include <iostream>
class Poll
{
public:
Poll()
:fds_(nullptr)
,fds_size_(15)
,nfds_(0)
,timeout_(1000)
{
fds_=new pollfd[fds_size_];
}
bool Add(TcpSocket& sock)
{
if(nfds_== fds_size_)
{//需要扩容
pollfd* new_fds = new pollfd[2 * fds_size_];
memcpy(new_fds,fds_,fds_size_ * sizeof(pollfd));
fds_size_ *= 2;
delete[] fds_;
fds_ = new_fds;
}
fds_[nfds_].fd = sock.GetFd();
fds_[nfds_].events = POLLIN;
++nfds_;
return true;
}
bool Del(TcpSocket& sock)
{
int del_fd = sock.GetFd();
for(size_t i = 0;i < nfds_;++i)
{//在fds_内才移除
if(fds_[i].fd == del_fd)
{
fds_[i] = fds_[--nfds_];
break;
}
}
return true;
}
bool Wait(std::vector<TcpSocket>& list,int timeout = 1000)
{
timeout_=timeout;
int ret = poll(fds_,nfds_,timeout_);
if(ret < 0)
{
perror("Poll Wait");
return false;
}
else if(ret == 0)
{
std::cout<<"poll wait timeout"<<std::endl;
return false;
}
for(nfds_t i = 0;i < nfds_;++i)
{
if(fds_[i].revents == POLLIN)
{
TcpSocket sock(fds_[i].fd);
list.push_back(sock);
}
}
return true;
}
int GetNfds()
{
return nfds_;
}
const pollfd* GetFds()
{
return fds_;
}
private:
pollfd* fds_;
nfds_t fds_size_;
nfds_t nfds_;
int timeout_;
};
typedef std::function<void(const std::string& req,std::string* resp)> Handler;
class TcpPollServer
{
public:
TcpPollServer(const std::string& ip,const uint16_t port)
:ip(ip)
,port(port)
{}
~TcpPollServer()
{
_sock.Close();
}
bool Start(Handler handler){
if(!_sock.Socket())
return false;
if(!_sock.Bind(ip,port))
return false;
if(!_sock.Listen())
return false;
Poll poll;
poll.Add(_sock);
while(1)
{
std::vector<TcpSocket> list;
if(!poll.Wait(list))
continue;
for(size_t i = 0;i < list.size();++i)
{
if(list[i].GetFd() == _sock.GetFd())
{
TcpSocket NewSock;
if(!_sock.Accept(&NewSock,NULL,NULL))
continue;
printf("客户端已连接!~\n");
poll.Add(NewSock);
}
else
{
std::string req;
int n = list[i].Recv(&req);
if(n == 0)
{
poll.Del(list[i]);
list[i].Close();
continue;
}
else
{
std::cout<<"客户端发送:"<<req<<std::endl;
std::string resp;
handler(req,&resp);
list[i].Send(resp);
}
}
}
}
return true;
}
private:
TcpSocket _sock;
std::string ip;
uint16_t port;
};
评论区