文章目录
- select的缺点
- poll
- struct pollfd
- 解决缺点的方式
- 代码实现
本篇总结的是poll的相关内容,在总结poll的内容前,先回顾一下select的缺点
select的缺点
select的缺点也比较明显
- 等待的fd是有上限的,在我们当前这个版本来说,它能等待的最大值是1024,也就是说超过来了这个1024我们的处理方式是直接把链接的这个socket丢弃
- 输入输出型参数比较多,数据拷贝的频率比较高
- 输入输出型参数比较多,每次都要对关心的fd进行事件重置
- 在用户层来说,在使用第三方数组进行管理fd的时候,要进行很多次的遍历,在内核中检测fd的事件就绪的时候,也要进行遍历
那么为了解决上述的这些问题,我们引入了poll的概念:
poll
先看一下poll的相关接口
poll的接口和select的接口几乎一样,所以使用起来基本没什么区别,这里出现了一个新的结构体,pollfd:
struct pollfd
那么到这里就可以看一下poll是如何解决select的缺点的:
解决缺点的方式
1. 等待的fd是有上限的
在select当中,一个很大的问题是有上限,而这个问题出现的原因是因为select的设计当中,最根源的问题在于它的位图大小决定了只能容纳1024个新的请求,再多了就不能容纳了,所以为了解决这个问题,poll直接设置成无限大小,只要操作系统和内存能放得下,那就能放得下,这是poll对于select的一个解决方案
2. 输入输出型参数比较多
这个问题poll也成功的解决了,poll的解决方案是直接定义一个结构体:
在这个结构体当中,存在有fd,events,revents,那么这就意味着可以免去拷贝的工作,直接在结构体当中进行设置即可,对于不同类型的参数,用位图来进行表示,存储在short当中:
至此,对于输入输出型参数太多导致的问题也被poll解决了
3. 遍历
但是遗憾的是,poll并没有解决遍历带来的效率问题,并且更为严重的是,poll还可以无限的增加数组中元素的个数,所以理论上来说是可以存放100000个数组内容的,这就给遍历带来了相当大的问题,所以遍历这件事poll并没有解决,这是Linux的这种设计模式决定确实不能解决的,具体解决只能留到epoll来解决了
代码实现
整体来说只要会用select,poll就是在它之上进行优化的,所以我只是对于select的代码进行了一些更变就有了poll的代码逻辑
#pragma once
#include <iostream>
#include <poll.h>
#include <sys/time.h>
#include "Socket.hpp"
using namespace std;
static const uint16_t defaultport = 8888;
static const int fd_num_max = 64;
int defaultfd = -1;
int non_event = 0;
class PollServer
{
public:
PollServer(uint16_t port = defaultport) : _port(port)
{
for (int i = 0; i < fd_num_max; i++)
{
_event_fds[i].fd = defaultfd;
_event_fds[i].events = non_event;
_event_fds[i].revents = non_event;
// cout << "fd_array[" << i << "]" << " : " << fd_array[i] << endl;
}
}
bool Init()
{
_listensock.Socket();
_listensock.Bind(_port);
_listensock.Listen();
return true;
}
void Accepter()
{
// 我们的连接事件就绪了
string clientip;
uint16_t clientport = 0;
int sock = _listensock.Accept(&clientip, &clientport); // 会不会阻塞在这里?不会
if (sock < 0)
return;
lg(Info, "accept success, %s: %d, sock fd: %d", clientip.c_str(), clientport, sock);
// sock -> fd_array[]
int pos = 1;
for (; pos < fd_num_max; pos++) // 第二个循环
{
if (_event_fds[pos].fd != defaultfd)
continue;
else
break;
}
if (pos == fd_num_max)
{
lg(Warning, "server is full, close %d now!", sock);
close(sock);
// 扩容
}
else
{
// fd_array[pos] = sock;
_event_fds[pos].fd = sock;
_event_fds[pos].events = POLLIN;
_event_fds[pos].revents = non_event;
PrintFd();
// TODO
}
}
void Recver(int fd, int pos)
{
// demo
char buffer[1024];
ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?
if (n > 0)
{
buffer[n] = 0;
cout << "get a messge: " << buffer << endl;
}
else if (n == 0)
{
lg(Info, "client quit, me too, close fd is : %d", fd);
close(fd);
_event_fds[pos].fd = defaultfd; // 这里本质是从select中移除
}
else
{
lg(Warning, "recv error: fd is : %d", fd);
close(fd);
_event_fds[pos].fd = defaultfd; // 这里本质是从select中移除
}
}
void Dispatcher()
{
for (int i = 0; i < fd_num_max; i++) // 这是第三个循环
{
int fd = _event_fds[i].fd;
if (fd == defaultfd)
continue;
if (_event_fds[i].revents & POLLIN)
{
if (fd == _listensock.Fd())
{
Accepter(); // 连接管理器
}
else // non listenfd
{
Recver(fd, i);
}
}
}
}
void Start()
{
_event_fds[0].fd = _listensock.Fd();
_event_fds[0].events = POLLIN;
int timeout = 3000; // 3s
for (;;)
{
int n = poll(_event_fds, fd_num_max, timeout);
switch (n)
{
case 0:
cout << "time out... " << endl;
break;
case -1:
cerr << "poll error" << endl;
break;
default:
// 有事件就绪了,TODO
cout << "get a new link!!!!!" << endl;
Dispatcher(); // 就绪的事件和fd你怎么知道只有一个呢???
break;
}
}
}
void PrintFd()
{
cout << "online fd list: ";
for (int i = 0; i < fd_num_max; i++)
{
if (_event_fds[i].fd == defaultfd)
continue;
cout << _event_fds[i].fd << " ";
}
cout << endl;
}
~PollServer()
{
_listensock.Close();
}
private:
Sock _listensock;
uint16_t _port;
struct pollfd _event_fds[fd_num_max]; // 数组, 用户维护的!
// struct pollfd *_event_fds;
// int fd_array[fd_num_max];
// int wfd_array[fd_num_max];
};
而在实际的使用当中,除非是在特殊的环境下,比如是比较老的平台,否则一般不用select,还是用后面新出的这些接口比较好用