【网络编程】基于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 模拟互联网,内网通过…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...

SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...

day36-多路IO复用
一、基本概念 (服务器多客户端模型) 定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用:应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标…...

uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...

抽象类和接口(全)
一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...