点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃
1.poll初始
poll也是一种linux中多路转接的方案。它所对应的多路转接方案主要是解决select两个问题。
- select的文件描述符有上限的问题
- select每次都要重新设置关心的fd
下面通过poll接口来认识它是怎么解决select的问题的。
2.poll函数接口
struct pollfd * fds:这里可以把它想象一个动态数组、数组或者new/malloc出来的结构体数组
nfds_t nfds:代表这个数组的长度
int timeout:纯输入型,时间单位ms
- 大于0:在timeout以内 阻塞,超过timeout非阻塞返回一次
- 等于0 :非阻塞
- 小于<0:阻塞
这个和select一模一样的意思。用起来更简单了。
返回值:同select一模一样
- 大于0:表示有几个fd就绪了
- 等于0:表示超时了
- 小于0:表示poll等待失败了
poll的作用和select一模一样:只负责等待!
这个struct pollfd 结构体 在传给poll表示 用户->内核:
int fd:你要关心一下这个fd哦
short events:关心的是这个fd的什么事件。我们把对应的事件设置进events里
输入看:fd+events
当poll返回时这个struct pollfd 结构体 表示内核->用户:
你要关心的fd上面的events中有那些事件已经就绪啦
short revents:就绪事件由revents返回
输出看:fd+revents
很显然这种设计解决了这样的问题:
- 输入输出分离!
现在,用户->内核,内核->用户,events和revents的分离!以前select就用一张位图表示不同含义,因为输入输出分离了所以决定了poll不需要对参数进行重新设定
events和revents类型是整数,对应的事件如下:
其中对我们来说常用的是POLLIN、POLLOUT、POLLERR ,这些都是大写的宏每一个占一个比特位,不同比特位表示不同事件。
所以用户->内核,只要将events设置成要关心的宏值,那么操作系统就帮我们进行关心了。当操作系统返回时只要把revents设置成对应的宏值,不就把那些事件就绪不就告诉我们了吗。
因为它的类型是short而没有用操作系统自己封装的各种各样的结构体,所以对于事件的设计,我们自己用户检测事件有没有设置或者就绪一定要由我们自己来做,按位与,按位或这样的操作。
- select等待fd有上限的问题
struct pollfd *fds不是一个数组吗,nfds_t nfds不就是该数组大小也就是上限吗,你怎么说poll解决了select等待fd上限的问题?
select是一个具体的数据类型fd_set,既然是一个具体的类型那就直接决定了数据类型大小只能由你的编译环境自己定,今天不一样了,因为这个数组由我们自己说的算!
3.poll服务器
前面不是写了select服务器吗,现在我们把它改成poll服务器
错误码封装
#pragma once
enum
{
USAGG_ERR = 1,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR
};
日志封装
#pragma once
#include<iostream>
#include<string>
#include<stdio.h>
#include <cstdarg>
#include<ctime>
#include<sys/types.h>
#include<unistd.h>
#include<fstream>
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
#define LOG_NORMAL "log.txt"
#define LOG_ERR "log.error"
const char* level_to_string(int level)
{
switch(level)
{
case DEBUG: return "DEBUG";
case NORMAL: return "NORMAL";
case WARNING: return "WARNING";
case ERROR: return "ERROR";
case FATAL: return "FATAL";
}
}
//时间戳变成时间
char* timeChange()
{
time_t now=time(nullptr);
struct tm* local_time;
local_time=localtime(&now);
static char time_str[1024];
snprintf(time_str,sizeof time_str,"%d-%d-%d %d-%d-%d",local_time->tm_year + 1900,\
local_time->tm_mon + 1, local_time->tm_mday,local_time->tm_hour, \
local_time->tm_min, local_time->tm_sec);
return time_str;
}
void logMessage(int level,const char* format,...)
{
//[日志等级] [时间戳/时间] [pid] [message]
//[WARNING] [2024-3-21 10-46-03] [123] [创建sock失败]
#define NUM 1024
//获取时间
char* nowtime=timeChange();
char logprefix[NUM];
snprintf(logprefix,sizeof logprefix,"[%s][%s][pid: %d]",level_to_string(level),nowtime,getpid());
//
char logconten[NUM];
va_list arg;
va_start(arg,format);
vsnprintf(logconten,sizeof logconten,format,arg);
std::cout<<logprefix<<logconten<<std::endl;
};
套接字封装
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
#include "err.hpp"
using namespace std;
class Sock
{
const static int backlog = 32;
public:
static int sock()
{
// 1. 创建socket文件套接字对象
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock