【网络编程】基于epoll的ET模式下的Reactor

需要云服务器等云产品来学习Linux的同学可以移步/-->腾讯云<--/-->阿里云<--/-->华为云<--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。
目录
一、Reactor介绍
二、基于epoll的ET模式下的Reactor计算器代码
1、TcpServer.hpp
2、Epoll.hpp
3、Main.cc
4、protocol.hpp
5、calServer.hpp
一、Reactor介绍
reactor模式是一种半同步(负责就绪事件的通知+IO)半异步(业务处理)IO,在Linux网络中,是使用最频繁的一种网络IO的设计模式。(还有一种比较少见的Proactor前摄器模式)Reactor模式中文译为反应堆模式,代码效果类似打地鼠游戏,玩家监控地鼠洞,哪个地鼠洞的“事件”就绪了,就去执行对应的回调方法。
注意,listen套接字也是非阻塞的,我们无法保证一次读取完毕所有的新连接,所以需要程序员使用while循环监听,读取新连接。
只要套接字被设置成非阻塞,即可不经过epoll直接发送(大不了发送失败用errno判断一下),但是我们无法保证数据是否一次被发完,所以必须保证一个socket一个发送缓冲区,否则残留的数据会被其他socket覆盖。
在处理发送事件时,其实非常不建议直接发送,因为程序员是无法保证写事件是就绪的,只有epoll有知晓写缓冲区是否就绪的能力。什么叫写事件就绪?就是发送缓冲区有空间,epoll就会提示写事件就绪。在大部分情况下,乃至服务器刚启动时,写事件其实都是就绪的。所以在epoll中,我们对读事件要常设关心,对写事件则按需设置(写事件常设时调用epoll_wait极大概率就绪)。
二、基于epoll的ET模式下的Reactor计算器代码

1、TcpServer.hpp
#pragma once
#include <iostream>
#include <functional>
#include <unordered_map>
#include <string>
#include <cassert>
#include "Err.hpp"
#include "Log.hpp"
#include "Sock.hpp"
#include "Epoll.hpp"
#include "Util.hpp"
#include "protocol.hpp"
namespace tcp_server
{class Connection;class TcpServer;static const uint16_t defaultPort = 8080;static const int num = 64;//表示最多可以存储多少个就绪事件static const int timeout = 1000;//超时时间using func_t = std::function<void (Connection*)>;//三种回调方法,读就绪,写就绪,异常就绪//using hander_t = std::function<void(const std::string&)>;class Connection//每一个套接字都要有自己的缓冲区(把每一个套接字看成Connection对象){public:Connection(int sock, TcpServer* pTS):_sock(sock),_pTS(pTS){}~Connection(){}public:void Register(func_t readFunc, func_t writeFunc, func_t errFunc)//注册事件{_recver = readFunc;_sender = writeFunc;_excepter = errFunc;}void Close(){close(_sock);}public:int _sock;std::string _inBuffer;//输入缓冲区。注意图片和视频的传输格式,每个对象一个缓冲区,考科一防止数据读一半的情况std::string _outBuffer;//输出缓冲区func_t _recver;//从sock中读func_t _sender;//向sock中写func_t _excepter;//处理sock在io时的异常事件TcpServer* _pTS;//tcpServer的指针,用于外部调用Connection对象可以控制TcpServer中的EnableReadWrite()接口uint64_t lastTime;//最近一次访问时间,每一次读和写都更新一下时间};class TcpServer//Reactor{public:TcpServer(func_t func, uint16_t port = defaultPort):_service(func),_port(port) ,_revs(nullptr){}~TcpServer(){_sock.Close();_epoll.Close();if(nullptr != _revs) delete[] _revs;//还有unordered_map没有析构}public:void InitServer(){//1、创建socket_sock.Socket();_sock.Bind(_port);_sock.Listen();//构建epoll对象_epoll.Create();//将listen套接字添加到epoll模型中AddConnnection(_sock.GetListenSocket(), EPOLLIN | EPOLLET, std::bind(&TcpServer::Accept, this, std::placeholders::_1), nullptr, nullptr);_revs = new struct epoll_event[num];_num = num;}void EnableReadWrite(Connection* conn, bool readAble, bool writeAble)//使能读、写{uint32_t event = (readAble ? EPOLLIN : 0) | (writeAble ? EPOLLOUT : 0) | EPOLLET;_epoll.Control(conn->_sock, event, EPOLL_CTL_MOD); }void Dispatch()//事件派发{while(1){Loop(timeout);//所有事情做完后,遍历所有的连接,计算每一个连接已经多久没发消息了,现在时间和lastTime相减,超过5分钟就关闭连接}}private:void Accept(Connection* conn)//监听事件的回调函数{//获取新连接,监听套接字也是非阻塞的。//Accept在非阻塞模式,返回值为-1时,判断errno即可知道是否读到所有的新连接while(1){std::string clientIp;uint16_t clientPort;int err = 0;//用于提取Accept的返回值int sock = _sock.Accept(&clientIp, &clientPort, &err);if(sock >= 0){AddConnnection(sock, EPOLLIN | EPOLLET, std::bind(&TcpServer::Read, this, std::placeholders::_1), std::bind(&TcpServer::Write, this, std::placeholders::_1), std::bind(&TcpServer::Except, this, std::placeholders::_1));LogMessage(DEBUG, "git a new link, info: [%s:%d]", clientIp.c_str(), clientPort);}else{if(err == EAGAIN || err == EWOULDBLOCK) break;//次数说明Accept把文件描述符全部读完了else if(err == EINTR) continue;//信号中断else {break;//Accept出错了}}}}void Read(Connection* conn)//普通读事件的回调{conn->lastTime = time(nullptr);char buffer[1024];while(1){ssize_t s = recv(conn->_sock, buffer, sizeof(buffer)-1, 0);if (s > 0){buffer[s] = 0;conn->_inBuffer += buffer;//将读到的数据存入string_service(conn);//对读取到的数据进行处理}else if (s == 0)//对端关闭连接{if (conn->_excepter)//conn将会被释放,后续代码就不要操作conn指针了{conn->_excepter(conn);return;}}else//判断几种读取出异常的情况{if(errno == EINTR) continue;else if(errno == EAGAIN || errno == EWOULDBLOCK) break;else{if(conn->_excepter){conn->_excepter(conn);return;}}}}}void Write(Connection* conn)//普通写事件的回调{conn->lastTime = time(nullptr);while(1){ssize_t s = send(conn->_sock, conn->_outBuffer.c_str(), sizeof(conn->_outBuffer.size()), 0);if (s > 0){if (conn->_outBuffer.empty()) { //EnableReadWrite(conn, true, false);//写事件写完了就关掉break; }else{conn->_outBuffer.erase(0, s);}}else{if (errno == EAGAIN || errno ==EWOULDBLOCK) { break; }else if (errno == EINTR) { continue; }else{if (conn->_excepter){conn->_excepter(conn);return;}}}} if (!conn->_outBuffer.empty())//如果没发完{conn->_pTS->EnableReadWrite(conn, true, true);}else//如果发完了{conn->_pTS->EnableReadWrite(conn, true, false);}}void Except(Connection* conn)//异常事件的回调{LogMessage(DEBUG, "Except");_epoll.Control(conn->_sock, 0, EPOLL_CTL_DEL);//在del的时候不关心是何种事件,有fd即可conn->Close();//关闭套接字_connections.erase(conn->_sock);delete conn;}void AddConnnection(int sock, uint32_t events, func_t readFunc, func_t writeFunc, func_t errFunc)//添加连接{//1、为该sock创建connection并初始化后添加到_connectionsif(events & EPOLLET){Util::SetNonBlock(sock);//将监听套接字设置为非阻塞}Connection* conn = new Connection(sock, this);//构建Connection对象//2、给对应的sock设置对应的回调方法conn->Register(readFunc, writeFunc, errFunc);//3、将sock与它所关心的事件注册到epoll中bool r = _epoll.AddEvent(sock, events);assert(r); (void)r;//4、将k、v添加到_connection中_connections.insert(std::pair<int, Connection*>(sock, conn));LogMessage(DEBUG, "add new sock : %d in epoll and unordered_map", sock);}void Loop(int timeout)//事件派发中的循环函数{int n = _epoll.Wait(_revs, _num, timeout);//捞出就绪事件的_revsfor(int i = 0; i < n; ++i){//通过_revs获得已就绪的fd和就绪事件int sock = _revs[i].data.fd;uint32_t events = _revs[i].events;//将异常问题,全部转化为读写问题,因为在读写时,读写接口自带读写问题的异常处理方式if((events & EPOLLERR)) events |= (EPOLLIN | EPOLLOUT);if((events & EPOLLHUP)) events |= (EPOLLIN | EPOLLOUT);//对端关闭连接if((events & EPOLLIN) && IsConnectionExist(sock))//监听事件及其他读事件就绪,保险起见,先判断connect对象是否存在{if(_connections[sock]->_recver)//检查存在,防止空指针_connections[sock]->_recver(_connections[sock]);//从map中找到key值为sock的Connection对象}if((events & EPOLLOUT) && IsConnectionExist(sock)){if(_connections[sock]->_sender)//检查存在,防止空指针_connections[sock]->_sender(_connections[sock]);}}}bool IsConnectionExist(int sock){auto iter = _connections.find(sock);return iter != _connections.end();}private:uint16_t _port;Sock _sock;//里面包含有listenS ocketEpoll _epoll;std::unordered_map<int, Connection*> _connections;//fd和Connection* struct epoll_event* _revs;//捞出就绪的事件及其fd的数组,epoll_wait会去捞int _num;//表示最多可以存储多少个就绪事件// hander_t _handler;//解协议func_t _service;};
}
2、Epoll.hpp
#pragma once
#include <iostream>
#include <sys/epoll.h>
#include <string>
#include <cstring>
#include "Err.hpp"
#include "Log.hpp"
const int size = 128;//epoll_create使用,大于0即可
class Epoll
{
public:Epoll():_epfd(-1){}~Epoll(){if(_epfd >= 0){close(_epfd);}}
public:void Create();bool AddEvent(int sock, uint32_t events);int Wait(struct epoll_event revs[], int num, int timeout);void Close();bool Control(int sock, uint32_t event, int action);
private:int _epfd;
};void Epoll::Create()
{_epfd = epoll_create(size);if(_epfd < 0)//创建epoll模型失败{LogMessage(FATAL, "epoll_create error, code: %d, errstring: %s",errno, strerror(errno));exit(EPOLL_CREATE_ERR);}
}
bool Epoll::AddEvent(int sock, uint32_t events)//用户到内核
{struct epoll_event ev;ev.events = events;ev.data.fd = sock;int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, sock, &ev);return n == 0;
}
int Epoll::Wait(struct epoll_event revs[], int num, int timeout)//revs是就绪的事件,num表示最多可以存储多少个就绪事件,均为输出型参数
{int n = epoll_wait(_epfd, revs, num, timeout);return n;//返回就绪事件的个数
}
void Epoll::Close()
{if(_epfd >= 0){close(_epfd);}
}
bool Epoll::Control(int sock, uint32_t event, int action)
{bool n = 0;if (action == EPOLL_CTL_MOD){struct epoll_event ev;ev.events = event;ev.data.fd = sock;n = epoll_ctl(_epfd, action, sock, &ev);}else if (action == EPOLL_CTL_DEL){n = epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);}else { n = -1; }return n == 0;
}
3、Main.cc
#include <memory>
#include "TcpServer.hpp"
using namespace tcp_server;
static void Usage(std::string proc)
{std::cerr << "Usage:\n\t" << proc << "port" << "\n\n";
}
//根据传入的req,输出resp
bool Cal(const Request& req,Response& resp)
{resp._exitCode = OK;resp._result = OK;switch(req._op){case '+':resp._result=req._x+req._y;break;case '-':resp._result=req._x-req._y;break;case '*':resp._result=req._x*req._y;break;case '/':{if(0==req._y){resp._exitCode=DIV_ZERO_ERR;}elseresp._result=req._x/req._y;} break;case '%':{if(0==req._y){resp._exitCode=MOD_ZERO_ERR;}elseresp._result=req._x%req._y;}break;default:resp._exitCode=OP_ZERO_ERR;return false;}return true;
}
void calculate(Connection* conn)//读就绪后,会进行回调,进行计算的处理
{std::string onePackage;while(ParseOncePackage(conn->_inBuffer, &onePackage)){std::string reqStr;//从一个报文中解析出来的正文部分if(!deLength(onePackage, &reqStr)) { return; }//提取报文中的有效载荷std::cout << "仅剩有效载荷的请求:\n" << reqStr << std::endl;//二、对有效载荷进行反序列化。(将正文的string对象解析x,y,op存储至req对象中)Request req;//运算数与运算符对象if(!req.deserialize(reqStr)) { return; }Response resp;Cal(req, resp);//四、对得到的Response计算结果对象,进行序列化,得到一个"字符串",发送给客户端std::string respStr;//输出型参数,获取序列化string类型的内容(resp_str是序列化后的字符串)resp.serialize(&respStr);//对计算结果对象resp进行序列化//五、先构建一个完整的报文,再将其添加到发送缓冲区中conn->_outBuffer = enLength(respStr);//对序列化数据添加自定义协议规则std::cout << "result" << conn->_outBuffer << std::endl;}//处理完了,直接发回去if (conn->_sender){conn->_sender(conn);}//如果没有发送完毕,需要对对应的socket开启对写事件的关心,如果发完了,则关闭对写事件的关心// if (!conn->_outBuffer.empty())//如果没发完// {// conn->_pTS->EnableReadWrite(conn, true, true);// }// else//如果发完了// {// conn->_pTS->EnableReadWrite(conn, true, false);// }
}
int main(int argc, char* argv[])
{if(argc != 2){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port = atoi(argv[1]);std::unique_ptr<TcpServer> tsvr(new TcpServer(calculate, port));tsvr->InitServer();tsvr->Dispatch();return 0;
}
4、protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <jsoncpp/json/json.h>
#include <sys/types.h>
#include <sys/socket.h>
enum
{OK=0,DIV_ZERO_ERR,MOD_ZERO_ERR,OP_ZERO_ERR,
};
#define SEP " "
#define SEP_LEN strlen(SEP)//不能使用sizeof,用sizeof会统计到'\0'
#define LINE_SEP "\r\n"
#define LINE_SEP_LINE strlen(LINE_SEP)
//加包头包尾:"_exitcode result" -> "content_len"\r\n"_exitcode result"\r\n
//加包头包尾:"_x _op _y" 修改为 "content_len"\r\n"_x _op _y"\r\n
std::string enLength(const std::string& text)//text:_x _op _y。添加协议规则,用于构建一个完整的报文(类似"打包")
{std::string send_string=std::to_string(text.size());//计算有效载荷的长度"_x _op _y"send_string += LINE_SEP;send_string += text;send_string += LINE_SEP;return send_string;
}
//去掉包头包尾"content_len"\r\n"_exitcode result"\r\n -> "_exitcode result"
bool deLength(const std::string& package,std::string* text)//获取报文中的有效载荷(类似"解包")
{auto pos = package.find(LINE_SEP);if(pos == std::string::npos) { return false; }int textLen = std::stoi(package.substr(0, pos));//计算有效载荷的长度*text = package.substr(pos + LINE_SEP_LINE, textLen);return true;
}
class Request//请求类
{
public:Request(int x,int y,char op):_x(x),_y(y),_op(op){}Request():_x(0),_y(0),_op(0){}bool serialize(std::string* out)//序列化,将成员变量转字符串{
#ifdef MYSELF//结构化->"_x _op _y"*out="";//清空string对象std::string x_tostring=std::to_string(_x);std::string y_tostring=std::to_string(_y);*out=x_tostring+SEP+_op+SEP+y_tostring;//_x _op _y
#else//Json序列化Json::Value root;//Json::Value万能对象,可接收任何对象root["first"]=_x;//自动将_x转换为字符串root["second"]=_y;root["oper"]=_op;//序列化Json::FastWriter writer;//Json::StyledWriter write;等价*out=writer.write(root);//将root进行序列化,返回值为string对象,接收即可
#endifreturn true;}bool deserialize(const std::string& in)//反序列化{
#ifdef MYSELF//"_x _op _y"->结构化auto leftSpace=in.find(SEP);//左边的空格auto rightSpace=in.rfind(SEP);//右边的空格if(leftSpace==std::string::npos||rightSpace==std::string::npos){return false;}if(leftSpace==rightSpace){return false;} //子串提取std::string x_tostring=in.substr(0,leftSpace);if(rightSpace-(leftSpace+SEP_LEN)!=1){return false;}//表示操作符一定只占1位_op=in.substr(leftSpace+SEP_LEN,rightSpace-(leftSpace+SEP_LEN))[0];std::string y_tostring=in.substr(rightSpace+SEP_LEN);//对x,y进行转换_x=std::stoi(x_tostring); _y=std::stoi(y_tostring);
#else//Json反序列化Json::Value root;//Json::Value万能对象,可接收任何对象Json::Reader reader;reader.parse(in,root);//第一个参数:解析哪个流;第二个参数:将解析的数据存放到对象中//反序列化_x=root["first"].asInt();//默认是字符串,转换为整型_y=root["second"].asInt();_op=root["oper"].asInt();//转换为整型,整型可以给char类型。
#endifreturn true;}
public://_x _op _yint _x;//左操作数int _y;//右操作数char _op;//操作符
};class Response//响应类
{
public:Response():_exitCode(0),_result(0){}Response(int exitCode,int result):_exitCode(exitCode),_result(result){}bool serialize(std::string* out)//序列化,将成员变量转string对象{
#ifdef MYSELF*out="";//清空string对象std::string outString=std::to_string(_exitCode)+SEP+std::to_string(_result);*out=outString;
#else//Json序列化(对象被序列化为了对应的Json字符串)Json::Value root;//Json::Value万能对象,可接收任何对象root["exitCode"]=_exitCode;//自动将_exitCode转换为字符串root["result"]=_result;//序列化Json::FastWriter writer;//Json::StyledWriter write;等价*out=writer.write(root);//将root进行序列化,返回值为string对象,接收即可#endifreturn true; }bool deserialize(const std::string& in)//反序列化{
#ifdef MYSELFauto space=in.find(SEP);//找空格if(space==std::string::npos){return false;}std::string exitString=in.substr(0,space);std::string resString=in.substr(space+SEP_LEN);if(exitString.empty()||resString.empty()){return false;}//一个字符串为空就false_exitCode=std::stoi(exitString);_result=std::stoi(resString);
#else//Json反序列化Json::Value root;//Json::Value万能对象,可接收任何对象Json::Reader reader;reader.parse(in,root);//第一个参数:解析哪个流;第二个参数:将解析的数据存放到对象中//反序列化_exitCode=root["exitCode"].asInt();//默认是字符串,转换为整型_result=root["result"].asInt();
#endifreturn true;}
public:int _exitCode;//0表示计算成功,非零代表除零等错误int _result;//运算结果
};bool ParseOncePackage(std::string& inbuffer, std::string* text)//一次从缓冲区解析出一个报文
{*text = "";//拆分成一个个报文auto pos = inbuffer.find(LINE_SEP);//找\r\n的起始位置if(pos == std::string::npos)//没找到说明暂时还没找到\r\n分隔符,跳过本次循环,等待下次读取{return false; }std::string textLenString = inbuffer.substr(0, pos);int textLen = std::stoi(textLenString);//拿出有效载荷的长度int totalLen = textLenString.size() + 2 * LINE_SEP_LINE + textLen;//单个报文总长度if(inbuffer.size() < totalLen)//说明缓冲区长度还不到一个报文大小,需要跳过本次循环继续读取{return false;}std::cout<<"截取报文前inbuffer中的内容:\n"<<inbuffer<<std::endl;//走到这里,一定有一个完整的报文*text = inbuffer.substr(0, totalLen);//取出一个报文inbuffer.erase(0, totalLen);//删掉缓冲区中刚刚被提取走的报文数据return true;
}
5、calServer.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include <cstdlib>
#include <functional>
#include "Log.hpp"
#include "protoCal.hpp"
namespace Server
{enum {USAGE_ERR=1,SOCKET_ERR,BIND_ERR,LISTEN_ERR,};static const uint16_t gport=8080;//缺省的端口号static const int gbacklog=5;//最大连接数=5+1const static std::string defaultIp="0.0.0.0";//缺省的IP//const Request&:输入型 Response&:输出型typedef std::function<bool(const Request&,Response&)> func_t;void handlerEntery(int sock,func_t func){std::string inbuffer;//接收报文的缓冲区while(1){//一、如何保证服务器读到数据是完整的?std::string req_text;//输出型参数,得到一条报文std::string req_str;//输出型参数,得到报文中的有效载荷if(!recvPackage(sock,inbuffer,&req_text)){return;}//服务器读取单条报文std::cout<<"带报头的请求:\n"<<req_text<<std::endl;if(!deLength(req_text,&req_str)){return;}//提取报文中的有效载荷std::cout<<"仅剩有效载荷的请求:\n"<<req_text<<std::endl;//二、对有效载荷进行反序列化,将提取到的数据存放至req中Request req;//运算数与运算符对象if(!req.deserialize(req_str)) return;//三、计算业务处理,得到一个结构化的结果对象(Response对象)Response resp;//计算结果对象func(req,resp);//对req提供的运算数与运算符,通过func将计算结果存放至resp中//四、对得到的Response计算结果对象,进行序列化,得到一个"字符串",发送给客户端std::string resp_str;//输出型参数,获取序列化string类型的内容resp.serialize(&resp_str);//对计算结果对象resp进行序列化std::cout<<"计算完成的序列化string对象:"<<resp_str<<std::endl;//五、先构建一个完整的报文,再进行发送std::string send_string=enLength(resp_str);//对序列化数据添加自定义协议规则std::cout<<"添加报头的序列化string对象:"<<send_string<<std::endl;send(sock,send_string.c_str(),send_string.size(),0);//服务器发送序列化内容给客户端(此处存在问题)}}class CalServer{public:CalServer(const uint16_t& port=gport,const std::string& ip=defaultIp ):_listenSocket(-1),_port(port) ,_ip(ip){}void InitServer()//初始化服务器{//1、创建监听socket套接字_listenSocket=socket(AF_INET,SOCK_STREAM,0);if(_listenSocket<0){LogMessage(FATAL,"create socket error");exit(SOCKET_ERR);}LogMessage(NORMAL,"create socket success");//2、绑定端口号+ip地址struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_addr.s_addr=inet_addr(_ip.c_str());local.sin_family=AF_INET;local.sin_port=htons(_port);if(bind(_listenSocket,(struct sockaddr*)&local,sizeof(local))<0){LogMessage(FATAL,"bind socket error");exit(BIND_ERR);}LogMessage(NORMAL,"bind socket success");//3、设置监听状态if(-1==listen(_listenSocket,gbacklog)){LogMessage(FATAL,"listen socket error");exit(LISTEN_ERR);}LogMessage(NORMAL,"listen socket success");}void Start(func_t func)//启动服务器{LogMessage(NORMAL,"Thread init success");while(1){//4、服务器获取客户端连接请求struct sockaddr_in peer;//输出型参数,拿到客户端的信息socklen_t len=sizeof(peer);int sock=accept(_listenSocket,(struct sockaddr*)&peer,&len); if(-1==sock) {LogMessage(ERROR,"accept error,next");continue;} LogMessage(NORMAL,"accept a new link success");//6、使用accept的返回值sock进行通信,均为文件操作pid_t id=fork();if(id==0)//子进程{close(_listenSocket);//关闭子进程的监听套接字,使监听套接字计数-1(防止下一步孙子进程拷贝)if(fork()>0) exit(0);//让子进程退出,孙子进程成为孤儿进程,交给1号进程托管回收其退出资源//ServerIO(sock);handlerEntery(sock,func);//从sock读取请求close(sock);//必须关闭使用完毕的sock,否则文件描述符泄漏(虽然下一句代码exit(0),孙子进程退出也会释放文件描述符,最好还是手动关一下)exit(0);}close(sock);//这是用于通信的套接字fd,父进程和孙子进程都有这个文件描述符,父进程关了,该文件描述符引用技术-1,直至孙子进程退出,该fd才会减为0,关闭//父进程pid_t ret=waitpid(id,nullptr,0);//这里不能用非阻塞等待,否则父进程先跑去执行其他代码,可能会被卡在accept出不来了(没有新的客户端来连接的话)if(ret>0){LogMessage(NORMAL,"wait child success");}}} ~CalServer(){}private:int _listenSocket;//监听客户端的连接请求,不用于数据通信uint16_t _port;//服务器端口号std::string _ip;//服务器ip地址};
}相关文章:
【网络编程】基于epoll的ET模式下的Reactor
需要云服务器等云产品来学习Linux的同学可以移步/-->腾讯云<--/-->阿里云<--/-->华为云<--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。 目录 一、Reactor介绍 二、基于epoll的ET模式下的Reactor计算器代码 1、Tcp…...
位操作符^以及正负数在计算机中的存储
(数据是怎么在计算机中存储的) 正数和负数在内存中都是以补码的形式存储的,但不同的是正数的原码,补码,反码都是相同的,而负数的原码,补码和反码是不同的。 负数的原码,补码,反码之间存在什么…...
Linux系统管理:虚拟机Kylin OS安装
目录 一、理论 1.Kylin OS 二、实验 1.虚拟机Kylin OS安装准备阶段 2.安装Kylin OS 3.进入系统 一、理论 1.Kylin OS (1)简介 麒麟操作系统(Kylin OS)亦称银河麒麟,是由中国国防科技大学、中软公司、联想公司…...
Nvidia显卡L40S学习:产品规格,常用名词解释
L40S 1 产品形态 构建NVIDIA OVX服务器,面向数据中心,用于加速AI训练和推理、3D设计和可视化、视频处理和工业数字化等复杂的计算密集型应用每个OVX服务器上8个L40S GPU,每个GPU配备48GB GDDR6超快内存 2 产品发展 具有许多与之前的 NVID…...
【safetensor】介绍和基础代码
Hugging Face, EleutherAI, StabilityAI 用的多 介绍 文件形式 header,体现其特性。如果强行将pickle或者空软连接 打开,会出现报错。解决详见:debug 连接到其他教程结构和参数 安装 with pip:Copied pip install safetensors with con…...
【STM32】--PZ6860L,STM32F4,ARM3.0开发板
一、ARM3.0开发板详细介绍 1.开发板整体介绍 (1)各种外设和主板原理图 (2)主板供电部分5V和3.3V兼容设计 注意跳线帽 2.STM32核心板介绍 3.核心板原理图 STM32和51的IO对应关系 下载电路 二、ARM3.0开发板ISP下载原理分析 1.I…...
百分点科技再度亮相GITEX全球大会
10月16-20日,全球最大科技信息展会之一 GITEX Global 2023在迪拜世贸中心开展,本届展会是历年来最大的一届,吸引了来自180个国家的6,000家参展商和180,000名技术高管参会。 百分点科技作为华为生态合作伙伴,继去年之后再度参展&a…...
机器学习笔记 - 深度学习中跳跃连接的直观解释
一、概述 如今人们利用深度学习做无数的应用。然而,为了理解在许多作品中看到的大量设计选择(例如跳过连接),了解一点反向传播机制至关重要。 如果你在 2014 年尝试训练神经网络,你肯定会观察到所谓的梯度消失问题。简单来说:你在屏幕后面检查网络的训练过程,你看到的只…...
利用python中if函数判断三角形的形状
1 问题 如何利用python中if函数判断三角形形状。 2 方法 给以一个三角形的三边长a,b和c(边长是浮点数),根据三角形三边关系定理以及勾股定理为基础,使用if函数判断三角形的形状。若是锐角三角形,输出R, 若是直角三角形,输出Z, 若是…...
分布式共识算法及落地
摘要 本文介绍常见的分布式共识算法,使用场景,以及相关已经落地了的程序或框架 1. 为什么要分布式共识算法 在分布式系统中,不同节点之间可能存在网络延迟、故障等原因导致彼此之间存在数据不一致的情况,为了保证分布式系统中的…...
HTML图像标签
html文件: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>图像标签学习</title> </head> <body> <img src"../resources/image/01.jpg" alt"小狗图…...
Openssl数据安全传输平台006:粘包的处理-代码框架及实现-TcpSocket.cpp
文章目录 0. 代码仓库1. TCP通信粘包问题2. 粘包、拆包表现形式2.1 正常情况2.2 两个包合并成一个包2.3 出现了拆包 3. 粘包的处理-参考仓库中的文件TcpSocket.cpp3.1 发送数据时候的处理3.2 接收数据时候的处理 0. 代码仓库 https://github.com/Chufeng-Jiang/OpenSSL_Secure_…...
Java中在控制台读取字符
Scanner 是 Java 中的一个类,用于从各种输入源获取输入,如键盘、字符串、文件等。以下是如何使用 Scanner 的基本示例: javaimport java.util.Scanner; // 导入 Scanner 类public class Main { public static void main(String[] args) { Sca…...
PositiveSSL的泛域名SSL证书
PositiveSSL是Sectigo旗下的一个子品牌,致力于为全球用户提供优质、高效的SSL证书服务。PositiveSSL以Sectigo强大的品牌影响力和全球网络为基础,秉承“安全、可靠、高效”的服务理念,为各类网站提供全面的SSL证书解决方案。今天就随SSL盾小编…...
模拟 Junit 框架
需求 定义若干个方法,只要加了MyTest注解,就可以在启动时被触发执行 分析 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在定义若干个方法,只要有MyTest注解的方法就能在启动时被触发执行,没有这…...
瑞萨e2studio(27)----使用EZ-CUBE3烧录
瑞萨e2studio.27--使用EZ-CUBE3烧录 概述视频教学样品申请引脚配置EZ-CUBE3 仿真器开关设置对RA族MCU进行Flash编程蓝色 LED 指示灯的状态信息 概述 EZ-CUBE3(CYRCNEZCUBE03)是具有Flash存储器编程功能的片上调试仿真器,可以用于调试MCU程序…...
springBoot--web--函数式web
函数式web 前言场景给容器中放一个Bean:类型是 RouterFunction<ServerResponse>每个业务准备一个自己的handler使用集合的时候加注解请求的效果 前言 springmvc5.2 以后允许我们使用函数式的方式,定义web的请求处理流程 函数式接口 web请求处理的…...
react中hooks闭包
import React, { useState } from react;function Counter() {const [count, setCount] useState(0);return (<div><p>Count: {count}</p><button onClick{() > setCount(count 1)}>Increment</button></div>); }在上面的 React 组件…...
C++笔记之vector的初始化以及assign()方法
C笔记之vector的初始化以及assign()方法 —— 2023年4月15日 上海 code review 文章目录 C笔记之vector的初始化以及assign()方法代码——实践出真知0. 空的vector1. 花括号(initializer_list)——最推荐的初始化方法2. 花括号3. 圆括号花括号4. 圆括号5. 圆括号6. 指针花括号7…...
OSPF基础实验
一、实验拓扑 二、实验要求 1、按照图示配置 IP 地址 2、R1,R2,R3 运行 OSPF 使内网互通,所有接口(公网接口除外)全部宣告进 Area 0; 要求使用环回口作为 Router-id 3、业务网段不允许出现协议报文 4、R5 模拟互联网,内网通过…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...
Windows 下端口占用排查与释放全攻略
Windows 下端口占用排查与释放全攻略 在开发和运维过程中,经常会遇到端口被占用的问题(如 8080、3306 等常用端口)。本文将详细介绍如何通过命令行和图形化界面快速定位并释放被占用的端口,帮助你高效解决此类问题。 一、准…...
Linux基础开发工具——vim工具
文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...
