网站佣金怎么做会计分录,办公楼网络组建方案设计,广东网站建设公司968,免费广告目录 前言
一、select的认识
二、select的接口
三、select的使用
四、select的优缺点 前言
在前面#xff0c;我们学习了五种IO模型#xff0c;对IO有了基本的认识#xff0c;知道了select效率很高#xff0c;可以等待多个文件描述符#xff0c;那他是如何等待的呢我们学习了五种IO模型对IO有了基本的认识知道了select效率很高可以等待多个文件描述符那他是如何等待的呢我们又该如何使用呢
一、select的认识
系统提供select函数来实现多路复用输入/输出模型 select系统调用是用来让我们的程序监视多个文件描述符的状态变化的程序会停在select这里等待直到被监视的文件描述符有一个或多个发生了状态改变 select只负责等待不负责拷贝一次可以等待多个文件描述符。他的作用是让read和write不再阻塞。
二、select的接口 select的调用接口如下 参数 1 int nfds值最大的文件描述符1。 参数 2 fd_set* readfdsfd_set本质是一张位图。代表select需要关心的读事件 参数 3 fd_set* writefds代表select需要关心的读事件 参数 4 fd_set* execptfdsfds代表select需要关心的异常事件我们暂时不考虑 参数 5 struct timeval* timeout时间结构体成员有秒和微秒代表等待的时间 {n,m}为阻塞等待n秒m微秒时间结束后返回 {0,0}为非阻塞等待 nullptr为阻塞等待 参数2,3,4类似都是输入输出型参数参数5也是输入输出型参数输出的是剩余时间 以readfds为例 输入时比特位的位置表示文件描述符的值比特位的内容0/1用户关心内核是否关心这个fd的读事件。 输出时比特位的位置表示文件描述符的值比特位的内容0/1内核告诉用户哪些文件fd上的读事件是否就绪 返回值 ret 0 select等待的多个fd中已经就需要的fd个数ret 0 select超时返回ret 0 select出错 同时fd_set 是特定的类型我们对其赋值时是不方便赋值的因此库里面也给提供的一个函数方便我们处理。 FD_CLR 从文件描述符集合 set 中清除文件描述符 fd。 FD_ISSET 检查文件描述符 fd 是否在文件描述符集合 set 中。 FD_SET 将文件描述符 fd 添加到文件描述符集合 set 中。 FD_ZERO 清空文件描述符集合 set将其所有位都设置为零。 三、select的使用
Log.hpp
#pragma once#include iostream
#include cstdarg
#include unistd.h
#include sys/stat.h
#include sys/types.h
#include pthread.h
using namespace std;enum
{Debug 0,Info,Warning,Error,Fatal
};enum
{Screen 10,OneFile,ClassFile
};string LevelToString(int level)
{switch (level){case Debug:return Debug;case Info:return Info;case Warning:return Warning;case Error:return Error;case Fatal:return Fatal;default:return Unknown;}
}const int default_style Screen;
const string default_filename Log.;
const string logdir log;class Log
{
public:Log(int style default_style, string filename default_filename): _style(style), _filename(filename){if (_style ! Screen)mkdir(logdir.c_str(), 0775);}// 更改打印方式void Enable(int style){_style style;if (_style ! Screen)mkdir(logdir.c_str(), 0775);}// 时间戳转化为年月日时分秒string GetTime(){time_t currtime time(nullptr);struct tm *curr localtime(currtime);char time_buffer[128];snprintf(time_buffer, sizeof(time_buffer), %d-%d-%d %d:%d:%d,curr-tm_year 1900, curr-tm_mon 1, curr-tm_mday, curr-tm_hour, curr-tm_min, curr-tm_sec);return time_buffer;}// 写入到文件中void WriteLogToOneFile(const string logname, const string message){FILE *fp fopen(logname.c_str(), a);if (fp nullptr){perror(fopen failed);exit(-1);}fprintf(fp, %s\n, message.c_str());fclose(fp);}// 打印日志void WriteLogToClassFile(const string levelstr, const string message){string logname logdir;logname /;logname _filename;logname levelstr;WriteLogToOneFile(logname, message);}pthread_mutex_t lock PTHREAD_MUTEX_INITIALIZER;void WriteLog(const string levelstr, const string message){pthread_mutex_lock(lock);switch (_style){case Screen:cout message endl; // 打印到屏幕中break;case OneFile:WriteLogToClassFile(all, message); // 给定all直接写到all里break;case ClassFile:WriteLogToClassFile(levelstr, message); // 写入levelstr里break;default:break;}pthread_mutex_unlock(lock);}// 提供接口给运算符重载使用void _LogMessage(int level, const char *file, int line, char *rightbuffer){char leftbuffer[1024];string levelstr LevelToString(level);string currtime GetTime();string idstr to_string(getpid());snprintf(leftbuffer, sizeof(leftbuffer), [%s][%s][%s][%s:%d], levelstr.c_str(), currtime.c_str(), idstr.c_str(), file, line);string messages leftbuffer;messages rightbuffer;WriteLog(levelstr, messages);}// 运算符重载void operator()(int level, const char *file, int line, const char *format, ...){char rightbuffer[1024];va_list args; // va_list 是指针va_start(args, format); // 初始化va_list对象format是最后一个确定的参数vsnprintf(rightbuffer, sizeof(rightbuffer), format, args); // 写入到rightbuffer中va_end(args);_LogMessage(level, file, line, rightbuffer);}~Log(){}private:int _style;string _filename;
};Log lg;class Conf
{
public:Conf(){lg.Enable(Screen);}~Conf(){}
};Conf conf;// 辅助宏
#define lg(level, format, ...) lg(level, __FILE__, __LINE__, format, ##__VA_ARGS__)Socket.hpp
#pragma once#include iostream
#include string
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include cstring
#include unistd.h
using namespace std;
namespace Net_Work
{static const int default_backlog 5;static const int default_sockfd -1;using namespace std;enum{SocketError 1,BindError,ListenError,ConnectError,};// 封装套接字接口基类class Socket{public:// 封装了socket相关方法virtual ~Socket() {}virtual void CreateSocket() 0;virtual void BindSocket(uint16_t port) 0;virtual void ListenSocket(int backlog) 0;virtual bool ConnectSocket(string serverip, uint16_t serverport) 0;virtual Socket *AcceptSocket(string *peerip, uint16_t *peerport) 0;virtual int GetSockFd() 0;virtual void SetSockFd(int sockfd) 0;virtual void CloseSocket() 0;virtual bool Recv(string *buff, int size) 0;virtual void Send(string send_string) 0;// 方法的集中在一起使用public:void BuildListenSocket(uint16_t port, int backlog default_backlog){CreateSocket();BindSocket(port);ListenSocket(backlog);}bool BuildConnectSocket(string serverip, uint16_t serverport){CreateSocket();return ConnectSocket(serverip, serverport);}void BuildNormalSocket(int sockfd){SetSockFd(sockfd);}};class TcpSocket : public Socket{public:TcpSocket(int sockfd default_sockfd): _sockfd(sockfd){}~TcpSocket() {}void CreateSocket() override{_sockfd socket(AF_INET, SOCK_STREAM, 0);if (_sockfd 0)exit(SocketError);}void BindSocket(uint16_t port) override{int opt 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, opt, sizeof(opt));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;int n bind(_sockfd, (struct sockaddr *)local, sizeof(local));if (n 0)exit(BindError);}void ListenSocket(int backlog) override{int n listen(_sockfd, backlog);if (n 0)exit(ListenError);}bool ConnectSocket(string serverip, uint16_t serverport) override{struct sockaddr_in addr;memset(addr, 0, sizeof(addr));addr.sin_family AF_INET;addr.sin_port htons(serverport);// addr.sin_addr.s_addr inet_addr(serverip.c_str());inet_pton(AF_INET, serverip.c_str(), addr.sin_addr);int n connect(_sockfd, (sockaddr *)addr, sizeof(addr));if (n 0)return true;return false;}Socket *AcceptSocket(string *peerip, uint16_t *peerport) override{struct sockaddr_in addr;socklen_t len sizeof(addr);int newsockfd accept(_sockfd, (sockaddr *)addr, len);if (newsockfd 0)return nullptr;// *peerip inet_ntoa(addr.sin_addr);// INET_ADDRSTRLEN 是一个定义在头文件中的宏表示 IPv4 地址的最大长度char ip_str[INET_ADDRSTRLEN];inet_ntop(AF_INET, addr.sin_addr, ip_str, INET_ADDRSTRLEN);*peerip ip_str;*peerport ntohs(addr.sin_port);Socket *s new TcpSocket(newsockfd);return s;}int GetSockFd() override{return _sockfd;}void SetSockFd(int sockfd) override{_sockfd sockfd;}void CloseSocket() override{if (_sockfd default_sockfd)close(_sockfd);}bool Recv(string *buff, int size) override{char inbuffer[size];ssize_t n recv(_sockfd, inbuffer, size - 1, 0);if (n 0){inbuffer[n] 0;*buff inbuffer;return true;}elsereturn false;}void Send(string send_string) override{send(_sockfd, send_string.c_str(),send_string.size(),0);}private:int _sockfd;string _ip;uint16_t _port;};
} select只负责等待不负责处理最初我们有一个listen_sock需要交给select去管理当有新链接到来是listen_sock要去接受新链接但是接受后不能立刻read或者write因为不确定当前事件是否就绪需要将新链接也交给select管理。 如何将新链接交给select呢我们得有一个数据结构这里用的数组把所有的fd都管理起来新链接到来时都可以往这个数组里面添加文件描述符fd。后面select遍历数组就可以找到需要管理的fd了但这样我们需要经常遍历这个数组 添加时需要遍历找到空再插入select传参需要遍历查找最大的文件描述符select等待成功后调用处理函数时也需遍历查找就绪的文件描述符 同时由于select的事件参数是一个输入输出型参数因此我们每次都得重新对该参数重新赋值。 如下是SelectServer.hpp的核心代码
SelectServer.hpp
#pragma once
#include iostream
#include string
#include sys/select.h
#include Log.hpp
#include Socket.hppusing namespace Net_Work;
const static int gdefaultport 8888;
const static int gbacklog 8;
const static int num sizeof(fd_set) * 8;class SelectServer
{
public:SelectServer(int port) : _port(port), _listensock(new TcpSocket()){}void HandlerEvent(fd_set rfds){for (int i 0; i num; i){if (_rfds_array[i] nullptr)continue;int fd _rfds_array[i]-GetSockFd();// 判断事件是否就绪if (FD_ISSET(fd, rfds)){// 读事件分两类一类是新链接到来一类是新数据到来if (fd _listensock-GetSockFd()){// 新链接到来lg(Info, get a new link);// 获取连接std::string clientip;uint16_t clientport;Socket *sock _listensock-AcceptSocket(clientip, clientport);if (!sock){lg(Error, accept error);return;}lg(Info, get a client,client info is# %s:%d,fd: %d, clientip.c_str(), clientport, sock-GetSockFd());// 此时获取连接成功了但是不能直接read write,sockfd仍需要交给select托管 -- 添加到数组_rfds_array中int pos 0;for (; pos num; pos){if (_rfds_array[pos] nullptr){_rfds_array[pos] sock;lg(Info, get a new link, fd is : %d, sock-GetSockFd());break;}}if (pos num){sock-CloseSocket();delete sock;lg(Warning, server is full, be carefull...);}}else{// 普通的读事件就绪std::string buffer;bool res _rfds_array[i]-Recv(buffer, 1024);if (res){lg(Info,client say# %s,buffer.c_str());buffer: 你好呀,同志\n;_rfds_array[i]-Send(buffer);buffer.clear();}else{lg(Warning,client quit ,maybe close or error,close fd: %d,fd);_rfds_array[i]-CloseSocket();delete _rfds_array[i];_rfds_array[i] nullptr;}}}}}void InitServer(){_listensock-BuildListenSocket(_port, gbacklog);for (int i 0; i num; i){_rfds_array[i] nullptr;}_rfds_array[0] _listensock.get();}void Loop(){_isrunning true;// 循环重置select需要的rfdswhile (_isrunning){// 不能直接获取新链接因为accpet可能阻塞// 所有的fd,都要交给select,listensock上面新链接,相当于读事件// 因此需要将listensock交给select// 遍历数组, 1.找最大的fd 2. 合法的fd添加到rfds集合中fd_set rfds;FD_ZERO(rfds);int max_fd _listensock-GetSockFd();for (int i 0; i num; i){if (_rfds_array[i] nullptr){continue;}else{// 添加fd到集合中int fd _rfds_array[i]-GetSockFd();FD_SET(fd, rfds);if (max_fd fd) // 更新最大值{max_fd fd;}}}// 定义时间struct timeval timeout {0, 0};PrintDebug();// rfds是输入输出型参数rfds是在select调用返回时不断被修改所以每次需要重置rfdsint n select(max_fd 1, rfds, nullptr, nullptr, /*timeout*/ nullptr);switch (n){case 0:lg(Info, select timeout...,last time: %u.%u, timeout.tv_sec, timeout.tv_usec);break;case -1:lg(Error, select error!!!);default:// 正常就绪的fdlg(Info, select success,begin event handler,last time: %u.%u, timeout.tv_sec, timeout.tv_usec);HandlerEvent(rfds); break;}}_isrunning false;}void Stop(){_isrunning false;}void PrintDebug(){std::cout current select rfds list is :;for (int i 0; i num; i){if (_rfds_array[i] nullptr)continue;elsestd::cout _rfds_array[i]-GetSockFd() ;}std::cout std::endl;}private:std::unique_ptrSocket _listensock;int _port;bool _isrunning;// select 服务器要被正确设计需要程序员定义数据结构来吧所有的fd管理起来Socket *_rfds_array[num];
}; Main.cc
#include iostream
#include memory
#include SelectServer.hppvoid Usage(char* argv)
{std::coutUsage: \n\targv port\nstd::endl;
}
// ./select_server 8080
int main(int argc,char* argv[])
{if(argc!2){Usage(argv[0]);return -1;}uint16_t localport std::stoi(argv[1]);std::unique_ptrSelectServer svr std::make_uniqueSelectServer(localport);svr-InitServer();svr-Loop();return 0;
}
四、select的优缺点
优点select只负责等待可以等待多个fdIO的时候效率会比较高一些。 缺点 由于select是输入输出型参数因此我们每次都要对select的参数重新设置。编写代码时select因为要使用第三方数组充满了遍历这可能会影响select的效率。用户到内核内核到用户每次select调用和返回都要对位图重新设置用户和内核之间要一直进行数据拷贝。select让OS在底层遍历需要关心所有的fd这也会造成效率低下这也是为何第一个参数需要传入max_fd 1就是因为select的底层需要遍历。fd_set 是系统提供的类型fd_set大小是固定的就意味着位图的个数是固定的也就是select最多能够检测到fd的总数是有上限的。