一、IO多路转接之poll
1.1poll函数接口

1.2poll函数返回值

1.3poll参数

二、poll服务器代码
2.1PollServer.hpp文件
cpp
#pragma once
#include <iostream>
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h>
#include "Sock.hpp"
static const uint16_t defaultport = 8080;
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 (size_t i = 0; i < fd_num_max; i++)
{
_event_fds[i].fd = defaultfd;
_event_fds[i].events = non_event;
_event_fds[i].revents = non_event;
}
}
bool Init()
{
_listensock.Socket();
_listensock.Bind(_port);
_listensock.Listen();
return true;
}
void PrintFd()
{
std::cout << "online fd list:";
for (int i = 0; i < fd_num_max; i++)
{
if (_event_fds[i].fd == defaultfd)
{
continue;
}
else
{
std::cout << _event_fds[i].fd << "->";
}
}
std::cout << std::endl;
}
void Accepter()
{
// 连接事件就绪
uint16_t clientport;
std::string clientip;
// 在此处不会在阻塞了,因为selec已经告诉我事件已经就绪
int sock = _listensock.Accept(&clientip, &clientport);
if (sock < 0)
{
return;
}
else
{
lg.logmessage(Info, "accept success! %s, %d", clientip.c_str(), clientport);
// 这里不能直接读,因为读写事件不一定就绪,所以要直接把这里获取的sock交给select
// 这里吧sock交给辅助数组就可以把sock交给select来监听了
int pos = 1;
for (; pos < fd_num_max; pos++)
{
if (_event_fds[pos].fd != defaultfd)
{
continue;
}
else
{
break;
}
}
// 这里跳出循环有两种情况,一是找到了没有设置的数组下标,二是数组满了
if (pos == fd_num_max)
{
lg.logmessage(Warning, "sorry, server is full, sockfd[%d] will close", sock);
close(sock);
// poll这里也可以进行扩容
}
else
{
// 找到未被设置的下标了
_event_fds[pos].fd = sock;
// 设置关心事件
_event_fds[pos].events = POLLIN | POLLOUT;
_event_fds[pos].revents = non_event;
PrintFd();
}
}
}
void Recvr(int fd, int pos)
{
// 其他的文件描述符就绪,进行读取操作
char buffer[1024];
ssize_t n = read(fd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = 0;
std::cout << "get a message:" << buffer << std::endl;
}
else if (n == 0)
{
lg.logmessage(Info, "client quit, close fd is:%d", fd);
_event_fds[pos].fd = defaultfd;
close(fd);
}
else
{
lg.logmessage(Warning, "read error, fd is:%d", fd);
_event_fds[pos].fd = defaultfd;
close(fd);
}
}
void Dispacter()
{
// 便利所有的文件描述符数组,因为我不知道哪一个就绪了
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 (_listensock.Fd() == fd)
{
// 连接管理器
Accepter();
}
else
{
Recvr(fd, i);
}
}
}
}
void Start()
{
_event_fds[0].fd = _listensock.Fd();
_event_fds[0].events = POLLIN;
int timeout = 3000;
for (;;)
{
int n = poll(_event_fds, fd_num_max, timeout);
switch (n)
{
case 0:
std::cout << "time out......" << std::endl;
break;
case -1:
std::cout << "poll fail" << std::endl;
break;
default:
// 有事件就绪
std::cout << "get a link" << std::endl;
// 处理事件
Dispacter();
break;
}
}
}
~PollServer()
{
_listensock.Close();
}
private:
Sock _listensock;
uint16_t _port;
struct pollfd _event_fds[fd_num_max];
};
2.2 Log.hpp文件
cpp
#pragma once
#include <iostream>
#include <stdarg.h>
#include <time.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include <fcntl.h>
#include<string.h>
#define SIZE 1024
// 设置日志等级
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
#define Screen 1
#define Onefile 2
#define Classfile 3
#define logFile "log.txt"
class log
{
public:
log()
{
PrintMethod = Screen;
path = "./log/";
}
void Enable(int method)
{
PrintMethod = method;
}
std::string levelToString(int level)
{
switch (level)
{
case Info:
return "Info";
case Debug:
return "Debug";
case Warning:
return "Warning";
case Error:
return "Error";
default:
return "None";
}
}
void logmessage(int level, const char *format, ...) // 后面的省略号表示可变参数
{
char leftbuffer[SIZE];
time_t t = time(nullptr);
struct tm *ctime = localtime(&t);
snprintf(leftbuffer, sizeof(leftbuffer), "[%s],[%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
ctime->tm_year + 1900, ctime->tm_mon, ctime->tm_mday,
ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
char rightbuffer[SIZE];
va_list s;
va_start(s, format);
vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
va_end(s);
char logtxt[SIZE * 3];
snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
// printf("%d-%d-%d %d:%d:%d\n",ctime->tm_year + 1900, ctime->tm_mon, ctime->tm_mday, ctime->tm_hour,ctime->tm_min,ctime->tm_sec);
//printf("%s", logtxt);
PrintLog(level,logtxt);
// 格式:默认部分+自定义部分(可变参数部分)
}
void PrintLog(int level, const std::string& logtxt)
{
switch(PrintMethod)
{
case Screen:
std::cout << logtxt << std::endl;
break;
case Onefile:
printOneFile(logFile, logtxt);
break;
case Classfile:
printClassFile(level, logtxt);
break;
default:
break;
}
}
void printOneFile(const std::string &logname, const std::string &logtxt)
{
std::string _logname = path + logname;
int fd = open(_logname.c_str(),O_WRONLY|O_CREAT|O_APPEND,0666);
if(fd < 0)
{
return ;
}
write(fd,logtxt.c_str(),logtxt.size());
close(fd);
}
void printClassFile(int level, const std::string &logtxt)
{
std::string filename = logFile;
filename += ".";
filename += levelToString(level);
printOneFile(filename, logtxt);
}
~log()
{
}
private:
int PrintMethod;
std::string path;
};
log lg;
2.3 Sock.hpp文件
cpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include "Log.hpp"
const int backlog = 10;
extern log lg;
enum
{
SocketError = 2,
BindError,
ListenError
};
class Sock
{
public:
Sock()
: _sockfd(-1)
{
}
~Sock()
{
if (_sockfd >= 0)
{
close(_sockfd);
}
}
// 创建套接字
void Socket()
{
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
lg.logmessage(Fatal, "socket fail, %s, %d", errno, strerror(errno));
exit(SocketError);
}
int opt = -1;
setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
}
// 创建一个绑定接口
void Bind(const uint16_t port)
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(_sockfd, (struct sockaddr *)&local, sizeof(local)) < 0)
{
lg.logmessage(Fatal, "bind fail, %s, %d", errno, strerror(errno));
exit(BindError);
}
}
void Listen()
{
if (listen(_sockfd, backlog) < 0)
{
lg.logmessage(Fatal, "listen fail, %s, %d", errno, strerror(errno));
exit(ListenError);
}
}
int Accept(std::string *clientip, uint16_t *clientport)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newfd = accept(_sockfd, (struct sockaddr *)&peer, &len);
if (newfd < 0)
{
lg.logmessage(Warning, "accept fail, %s, %d", errno, strerror(errno));
return -1;
}
char ipstr[64];
inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
// 获取远端主机的信息
*clientport = ntohs(peer.sin_port);
*clientip = ipstr;
return newfd;
}
bool Connect(const std::string &ip, const uint16_t &port)
{
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &peer.sin_addr);
int n = connect(_sockfd, (struct sockaddr *)&peer, sizeof(peer));
if (n == -1)
{
std::cerr << "connect to" << ip << ":" << port << std::endl;
return false;
}
return true;
}
void Close()
{
close(_sockfd);
}
int Fd()
{
return _sockfd;
}
private:
int _sockfd;
};
2.4main.cc文件
cpp
#include "PollServer.hpp"
#include <memory>
int main()
{
std::unique_ptr<PollServer> svr(new PollServer());
svr->Init();
svr->Start();
return 0;
}
三、poll的优缺点
3.1 poll的优点
(1)不同于select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现。
(2)pollfd结构包含了要监视的event和发生的event,不再使用select"参数---值"传递的方式,接口使用比select更方便。
(3)poll并没有最大数量限制(但是数量过大后性能也是会下降)。
3.2 poll的缺点
当poll中监听的文件描述符数目增多时:
(1)poll和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。
(2)每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中。
(3)同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。