多路转接之Reactor
多路转接之Reactor
- 一、关于Reactor
- 1.关于监听套接字
- 2.IO分为两部分以及事件就绪
- 3.每个连接文件描述符都需要输入输出缓冲区
- 4.监听套接字的读事件的处理
- 5.什么时候给epoll设置写事件
- 6.连接文件描述符的读事件处理
- 7.写事件的处理函数
- 8.异常事件的处理函数
- 二、实现Reactor的服务器
- Main.cc
- TcpServer.hpp
- Epoller.hpp
- Sock.hpp
- Util.hpp
- Protocal.hpp
- Log.hpp
- Task.hpp
- Public.hpp
一、关于Reactor
Reactor的半同步半异步模式,Reactor负责等待事件就绪以及网络IO,将业务逻辑给线程池处理。
1.关于监听套接字
监听套接字需要绑定的端口号,也是对外开放的端口号。建立监听套接字的作用就是客户端可以向向服务端发送tcp连接的请求, 也就是可以通过端口发送连接请求。
客户端通过此端口号与服务端进行tcp三次握手,建立连接,并将连接存储在accept队列(全连接队列中),当服务端调用accept会将完成三次握手的连接从accept队列(全连接队列)中取出来, 连接取出来后就可以进行网络IO了,服务端就可以向客户端发送消息,服务端可以接受客户端发送的消息。
2.IO分为两部分以及事件就绪
IO分两部分:等待IO资源就绪+进行就绪事件的处理,等待IO资源就绪:读事件就绪和写事件就绪。
读事件就绪:分为监听套接字文件描述符的读事件就绪和连接文件描述符的读事件就绪。
监听套接字文件描述符的读事件就绪:就是accept队列中有完成了tcp三次握手的新连接。
连接文件描述符的读事件就绪:就是在连接对应的传输层接受缓冲区中有客户端发来的数据。
写事件就绪:连接文件描述符对应的传输层的发送缓冲区有空闲空间事件就绪除了读事件就绪和写事件就绪,还有异常事件就绪。
异常事件就绪:就是连接文件描述符被客户端关闭或者通过系统调用对连接文件描述符进行IO,系统调用失败。
异常事件就绪可以转化为读写事件就绪,发生异常的文件描述符进行IO的系统调用时,会返回0。在读写事件的处理函数中会调用事件异常的处理方法,处理异常事件。
连接文件描述符被客户端关闭,是通过epoll告诉用户,而系统调用失败,是通过系统调用的返回值知道。
3.每个连接文件描述符都需要输入输出缓冲区
对于连接文件描述符的读操作:当收到读事件就绪时,进行读操作,无法保证一次读操作就能读取到一个完整的应用层数据报文。
若没有读取完一个完整的应用层数据报文,需要等待下一次的读事件就绪,在进行读取,但是这次读取的数据应该存放在什么地方?下次读取数据怎么与之前的数据拼接在一起呢?
所以对于每一个连接文件描述符都需要维护一个输入缓冲区,来接受读取到的数据。每次读取都会尝试从输入缓冲区中获取一个应用层数据报文,获取成功对请求报文做进行业务处理,获取失败等待连接文件描述符的读事件就绪,再次读取数据放到输入缓冲区中。所以为了能够获取完整的应用数据报文必须给每个文件描述符维护一个输入缓冲区,同理也无法保证一次发送能发送完一个完整的应用层数据报文可以封装一个类,包含文件描述符的IO处理函数,异常的处理函数,文件描述符的输入输出缓冲区。每个实例就是对应一个文件描述符的封装,通过类实例出来的Connection需要管理,通过unordered_map将文件描述符的实例管理起来,便于对文件描述符的增删改查。
4.监听套接字的读事件的处理
服务器初始化,创建监听套接字,并将监听套接字的读事件交给epoll等待,生成监听套接字实例,生成的过程中传进的读事件的处理方法是accept,将实例加入unordered_map中进行管理。
对于监听套接字的读事件就绪,就会调用读事件的处理方法即accept系统调用,获取新连接,将连接的读事件交给epoll等待,将连接的实例加入unordered_map进行管理。
5.什么时候给epoll设置写事件
获取新连接,并不会将连接的写事件交给epoll等待,因为文件描述符的读事件大部分情况都是就绪的,交给epoll等待,epoll会一直通知写事件就绪只有在写数据时,且进行一次写数据没有将数据写完时,才将文件描述符的写事件交给epoll监视。
6.连接文件描述符的读事件处理
对于连接文件描述符的读事件就绪,会通过文件描述符找到对应的实例,调用实例中的读事件的处理方法,对传输层接受缓冲区中的数据进行读取到实例中的输入缓冲区,每次读完对缓冲区做检测,检测是否有一个完整的应用层数据报文。若没有则等待下一次epoll通知读事件就绪,若有则对请求报文做业务处理,返回响应报文给客户端。将响应报文放到文件描述符的输出缓冲区调用文件的写事件处理方法,将输出缓冲区的数据写到传输层的发送缓冲区。若没有将响应报文写完,需要将文件描述符的写事件让epoll监视,若将响应报文写完了,需要将文件描述符的写事件在epoll关闭,不让epoll继续监视文件描述符的写事件。
7.写事件的处理函数
当写事件就绪时,通过文件描述符找到文件描述符对应的实例,调用实例中的写事件处理函数,写事件的处理函数会将文件描述符对应的输出缓冲区中的数据写入传输层的发送缓冲区中。若写完了将文件描述符在epoll中的写事件关闭,若没有写完将文件描述符的写事件加入到epoll中,让epoll等待监视。
8.异常事件的处理函数
当异常事件就绪时,会将异常事件转化为读写事件就绪,通过文件描述符找到对应的实例,通过实例调用读事件处理方法或者写事件处理方法,在处理方法中调用IO的系统调用,会调用失败,调用失败再去调用异常处理方法,异常处理方法中会将文件描述符对应的事件epoll中关闭,会将文件描述符对应的实例在unordered_map中去掉,会将调用文件描述符对应的实例的析构函数,delete实例。
二、实现Reactor的服务器
Main.cc
#include "TcpServer.hpp"
#include <memory>
#include "Task.hpp"
using namespace tcpserver;
//对读到的报文做计算服务
void caculate(Connection* conn){
std::string package;
if(PaseOnePackage(conn->inbuffer_,&package)){
//获取到一个完整有效的报文
//获取有效载荷
std::string reqStr;
if(!deLength(package,&reqStr))return;
Request req;
//将有效载荷进行反序列化
if(!req.deserialize(reqStr))return;
//对数据做计算,返回响应的结构化数据
Response resp;
cal(req,resp);
//将结构化数据进行序列化,得到有效载荷
std::string respStr=resp.serialize();
//添加报头(有效载荷的长度)获取响应报文
std::string sendStr=enLength(respStr);
//将响应报文加入文件符在应用层的发送缓冲区中
conn->outbuffer_+=sendStr;
//将应用层的发送缓冲区中的数据发送到传输层的发送缓冲区中
if(conn->sender_!=nullptr)
conn->sender_(conn);
//若数据没有发送完将文件描述符的写事件设置
//若数据已经发送完将文件描述符的写事件关闭
if(conn->outbuffer_.empty()){
conn->tsp_->EnableReadWrite(conn,true,false);
}
//若没有读完,下次写事件就绪,依旧需要判断数据是否写完
else{
conn->tsp_->EnableReadWrite(conn,true,true);
}
}
}
int main(){
unique_ptr<TcpServer> svr(new TcpServer(caculate,8080));
svr->InitServer();
svr->Dispatcher();
return 0;
}
TcpServer.hpp
#pragma once
#include "Sock.hpp"
#include "Epoller.hpp"
#include <functional>
#include <cassert>
#include <unordered_map>
#include "Util.hpp"
#include "Protocal.hpp"
namespace tcpserver{
static const uint16_t defaultPort=8080;
static const int num=64;
class TcpServer;
class Connection;
using func_t=function<void(Connection*)>;
class Connection{
public:
Connection(int fd,TcpServer* tsp=nullptr)
:fd_(fd),tsp_(tsp){};
//注册IO方法
void Register(func_t r,func_t s,func_t e){
recver_=r;
sender_=s;
excepter_=e;
}
void Close(){
close(fd_);
}
int fd_;
std::string inbuffer_;
std::string outbuffer_;
func_t recver_;//从fd的接受缓冲区中读取数据放到inbuffer中
func_t sender_;//将outbuffer中的数据向fd的发送缓冲区中写
func_t excepter_;//处理IO异常的事件
TcpServer* tsp_;
};
//实现一个单线程的Reactor
class TcpServer{
private:
//将文件描述符加入epoll并加入unordered_map中
void AddConnection(int fd,uint32_t events,func_t recver,func_t sender,func_t excepter){
if(events&EPOLLET)Util::setNonBlock(fd);
bool ret=epoller_.AddEvent(fd,events);
assert(ret);
(void)ret;
Connection* conn=new Connection(fd,this);
conn->Register(recver,sender,excepter);
connections_.insert(pair<int,Connection*>(fd,conn));
}
void Recver(Connection* conn){
std::cout<<"inRecver"<<std::endl;
char buffer[1024];
while(true){
ssize_t n=recv(conn->fd_,buffer,sizeof(buffer),0);
if(n>0){
buffer[n]=0;
conn->inbuffer_+=buffer;
}
//连接断开
else if(n==0){
std::cout<<"1-------"<<std::endl;
if(conn->excepter_!=nullptr)conn->excepter_(conn);
return;
}
else{
if(errno==EAGAIN|| errno==EWOULDBLOCK)break;
else if(errno==EINTR)continue;
else {
std::cout<<"2-------"<<std::endl;
if(conn->excepter_!=nullptr)conn->excepter_(conn);
return;
}
}
}
handler_(conn);
}
void Sender(Connection* conn){
std::cout<<"void Sender(Connection* conn)"<<std::endl;
while(true){
std::cout<<"conn->outbuffer_.c_str():"<<conn->outbuffer_.c_str()<<std::endl;
ssize_t n=send(conn->fd_,conn->outbuffer_.c_str(),conn->outbuffer_.size(),0);
if(n>0){
conn->outbuffer_.erase(0,n);
//将应用层发送缓冲区的数据发送完了,将文件描述符的写事件关闭,避免一直通知用户
if(conn->outbuffer_.empty()){
EnableReadWrite(conn,true,false);
break;
}
//数据没有发送完,说明传输层发送缓冲区没有剩余的空间,需要等待写事件就绪
break;
}
else{
//传输层的没有空闲剩余空间了
if(errno==EAGAIN||errno==EWOULDBLOCK)break;
//发生了信号中断
else if(errno==EINTR)continue;
//send调用失败,处理异常 或者其他异常事件转化为读写事件
else{
std::cout<<"3-------"<<std::endl;
if(conn->excepter_!=nullptr)conn->excepter_(conn);
return;
}
}
}
}
//处理异常事件
void Excepter(Connection* conn){
std::cout<<"Excepter(Connection* conn)"<<std::endl;
//将出现异常的文件描述符从epoll中删除
epoller_.Control(conn->fd_,0,EPOLL_CTL_DEL);
conn->Close();
connections_.erase(conn->fd_);
std::cout<<"close fd="<<conn->fd_<<std::endl;
delete conn;
}
void Accepter(Connection *conn){
while(true){
std::string clientIp;
uint16_t clientPort;
int err;
int fd=sock_.Accept(&clientIp,&clientPort,&err);
std::cout<<"get a link ip="<<clientIp<<" port="<<clientPort<<std::endl;
if(fd>0){
AddConnection(fd,EPOLLIN|EPOLLET,
std::bind(&TcpServer::Recver,this,placeholders::_1),
std::bind(&TcpServer::Sender,this,placeholders::_1),
std::bind(&TcpServer::Excepter,this,placeholders::_1));
}
else{
if(err==EAGAIN||err==EWOULDBLOCK)break;//没有连接了
else if(err==EINTR)continue;//信号中断
else break;
}
}
}
bool ConnectionIsExits(int fd){
return connections_.end()!=connections_.find(fd);
}
void Loop(int timeout){
//阻塞等待事件就绪
int n=epoller_.Wait(revs_,num_,timeout);
std::cout<<"epoller_.Wait(revs_,num_,timeout);"<<std::endl;
//获取就绪事件
for(int i=0;i<n;++i){
int fd=revs_[i].data.fd;
uint32_t events=revs_[i].events;
//将异常事件,转为读写事件,在读写事件处理函数中,会处理异常事件
//异常举例:客户端将连接断开,与服务器进行四次挥手,服务器会将连接close
//用户让epoll等待一个无效的文件描述符的事件就绪,epoll就会给用户返回异常事件就绪
//用户需要对这异常做处理,这里异常是让epoll等待一个无效文件描述符
//异常处理应该将无效的文件描述符从epoll中删除,不让其继续等待无效文件描述符的事件就绪
if(events&EPOLLERR)events|=(EPOLLIN|EPOLLOUT);
if(events&EPOLLHUP)events|=(EPOLLIN|EPOLLOUT);
if((events&EPOLLIN)&&ConnectionIsExits(fd)&&connections_[fd]->recver_!=nullptr)
connections_[fd]->recver_(connections_[fd]);
if((events&EPOLLOUT)&&ConnectionIsExits(fd)&&connections_[fd]->sender_!=nullptr){
std::cout<<"connections_[fd]->sender_(connections_[fd]);"<<std::endl;
connections_[fd]->sender_(connections_[fd]);
}
}
}
public:
//构造函数对Reactor初始化,初始化监听套接字需要绑定的端口,以及业务处理的处理函数
TcpServer(func_t handler,uint16_t port=defaultPort)
:port_(port),num_(num),revs_(nullptr),handler_(handler){}
~TcpServer(){
epoller_.Close();
sock_.Close();
if(revs_!=nullptr)delete[] revs_;
}
//将文件描述符的读事件和写事件进行设置
void EnableReadWrite(Connection* conn,bool readable,bool writeable){
uint32_t event=(readable?EPOLLIN:0)|(writeable?EPOLLOUT:0)|EPOLLET;
epoller_.Control(conn->fd_,event,EPOLL_CTL_MOD);
}
void InitServer(){
//创建监听套接字文件描述符
sock_.Socket();
sock_.Bind(port_);
sock_.Listen();
//创建epoll模型
epoller_.Create();
//将监听套接字加入epoll中
//将epoll模型设置为ET模式,必须将文件描述符设为非阻塞
//并加入unordered_map中
// Util::setNonBlock(sock_.Fd());
// epoller.AddEvent(listenSockfd_,EPOLLIN|EPOLLET);
AddConnection(sock_.Fd(),EPOLLIN|EPOLLET,
std::bind(&TcpServer::Accepter,this,std::placeholders::_1),nullptr,nullptr);
revs_=new struct epoll_event[num];
}
//派发事件
void Dispatcher(){
int timeout=-1;
while(true){
Loop(timeout);
std::cout<<"time out..."<<std::endl;
}
}
void start(){}
private:
uint16_t port_;
Sock sock_;
Epoller epoller_;
//将所有的连接用unordered_map管理起来
unordered_map<int,Connection*> connections_;
//定义struct epoll_event数组接受就绪的事件
struct epoll_event* revs_;
int num_;
func_t handler_;
};
}
Epoller.hpp
#pragma once
#include "Log.hpp"
#include "Public.hpp"
#include <sys/epoll.h>
#include <errno.h>
#include <cstring>
static const int size=128;
class Epoller{
public:
Epoller():epfd_(-1){}
~Epoller(){}
void Close(){if(epfd_!=-1)close(epfd_);}
void Create(){
epfd_=epoll_create(size);
if(epfd_<0){
log(FATAL,"epoll_create error, errno=%d,strerror=%s",errno,strerror(errno));
exit(EPOLL_CREATE_ERR);
}
}
bool AddEvent(int fd,uint32_t events){
struct epoll_event ev;
ev.data.fd=fd;
ev.events=events;
int n=epoll_ctl(epfd_,EPOLL_CTL_ADD,fd,&ev);
return n==0;
}
int Wait(struct epoll_event* revs,int num,int timeout){
int n=epoll_wait(epfd_,revs,num,timeout);
return n;
}
bool Control(int fd,uint32_t event, int action){
int n=0;
if(action==EPOLL_CTL_MOD){
struct epoll_event ev;
ev.data.fd=fd;
ev.events=event;
n=epoll_ctl(epfd_,action,fd,&ev);
}
else if(action==EPOLL_CTL_DEL){
n=epoll_ctl(epfd_,action,fd,nullptr);
}
else{
}
return n==0;
}
private:
int epfd_;
};
Sock.hpp
#pragma oncce
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <sys/wait.h>
#include "Log.hpp"
#include "Public.hpp"
static const int backlog=5;
static const int defaultfd=-1;
class Sock{
public:
Sock():listenSockfd_(defaultfd){}
~Sock(){}
void Close(){if(listenSockfd_!=defaultfd)close(listenSockfd_);}
//创建套接字文件描述符
void Socket(){
listenSockfd_=socket(AF_INET,SOCK_STREAM,0);
if(listenSockfd_==-1){
log(ERROR,"create a socket error");
exit(SOCKET_ERR);
}
else log(NORMAL,"create socket success, listenSockfd_=%d",listenSockfd_);
//地址复用
int opt=1;
setsockopt(listenSockfd_,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt));
}
//给套接字绑定端口号
void Bind(uint16_t port){
struct sockaddr_in local;
socklen_t len=sizeof(local);
bzero(&local,len);
local.sin_family=AF_INET;
local.sin_addr.s_addr=INADDR_ANY;
local.sin_port=htons(port);
int n=bind(listenSockfd_,(const struct sockaddr*)&local,len);
if(n==-1){
log(ERROR,"bind listenSockfd_ error");
exit(BIND_ERR);
}
else log(NORMAL,"bind listenSockfd_ success");
}
//监听套接字
void Listen(){
int n=listen(listenSockfd_,backlog);
if(n==-1){
log(ERROR,"listen listenSockfd_ error");
exit(BIND_ERR);
}
else log(NORMAL,"listen listenSockfd_ success");
}
//获取新连接套接字
int Accept(std::string* clientIp,uint16_t* clientPort,int* err){
struct sockaddr_in client;
socklen_t len=sizeof(client);
bzero(&client,len);
int fd=accept(listenSockfd_,(struct sockaddr*)&client,&len);
*err=errno;
if(fd==-1)log(ERROR,"accept listenSockfd_ error");
else {
log(NORMAL,"accept a link socket=%d",fd);
*clientIp=inet_ntoa(client.sin_addr);
*clientPort=ntohs(client.sin_port);
}
return fd;
}
int Fd(){return listenSockfd_;}
private:
int listenSockfd_;
};
Util.hpp
#pragma once
#include <fcntl.h>
#include <unistd.h>
class Util{
public:
//将文件描述符设置为非阻塞
static bool setNonBlock(int fd){
int flag=fcntl(fd,F_GETFL);
if(flag==-1)return false;
fcntl(fd,F_SETFL,flag|O_NONBLOCK);
return true;
}
};
Protocal.hpp
#pragma once
#include <string>
#include <cstring>
#include <jsoncpp/json/json.h>
#define SEP_LINE "\r\n"
#define SEP_LINE_LEN strlen(SEP_LINE)
#define SEP " "
#define SEP_LEN strlen(SEP)
//判断是不是一个完整的报文,如果是将报文读出来
bool PaseOnePackage(std::string& inbuffer,std::string* text){
auto pos=inbuffer.find(SEP_LINE);
if(pos==std::string::npos)return false;//不符合协议
std::string text_len_str=inbuffer.substr(0,pos);
int text_len=stoi(text_len_str);
int total_len=text_len+SEP_LINE_LEN*2+text_len_str.size();
if(total_len>inbuffer.size())return false;//不是一个完整报文
//一定是一个完整的报文
*text=inbuffer.substr(0,total_len);
inbuffer.erase(0,total_len);
return true;
}
bool deLength(const std::string& package,std::string* text){
auto pos=package.find(SEP_LINE);
if(pos==std::string::npos)return false;
string text_len_str=package.substr(0,pos);
int text_len=std::stoi(text_len_str);
*text=package.substr(pos+SEP_LINE_LEN,text_len);
return true;
}
std::string enLength(const std::string& text){
std::string ret;
std::string text_len_string=std::to_string(text.size());
ret+=text_len_string;
ret+=SEP_LINE;
ret+=text;
ret+=SEP_LINE;
return ret;
}
class Request{
public:
Request(){}
Request(int x,int y,char op)
:_x(x),_y(y),_op(op){}
std::string serialize(){
std::string ret;
#ifdef MYSELF
ret+=to_string(_x);
ret+=SEP;
ret+=_op;
ret+=SEP;
ret+=to_string(_y);
#else
Json::Value root;
root["first"]=_x;
root["oper"]=_op;
root["second"]=_y;
Json::FastWriter writer;
ret=writer.write(root);
#endif
return ret;
}
bool deserialize(const string& text ){
#ifdef MYSELF
auto left=text.find(SEP);
auto right=text.rfind(SEP);
if(right-(left+SEP_LEN)!=1)return false;
if(left==std::string::npos)return false;
std::string str_x=text.substr(0,left);
if(str_x.empty())return false;
_op=text[left+SEP_LEN];
std::string str_y=text.substr(right+SEP_LEN);
if(str_y.empty())return false;
_x=stoi(str_x);
_y=stoi(str_y);
#else
Json::Value root;
Json::Reader reader;
reader.parse(text,root);
_x=root["first"].asInt();
_y=root["second"].asInt();
_op=root["oper"].asInt();
#endif
return true;
}
public:
int _x;
int _y;
char _op;
};
class Response{
public:
std::string serialize(){
std::string ret;
#ifdef MYSELF
ret+=to_string(_ret);
ret+=SEP;
ret+=to_string(_exitcode);
#else
Json::Value root;
root["ret"]=_ret;
root["exitcode"]=_exitcode;
Json::FastWriter writer;
ret=writer.write(root);
#endif
return ret;
}
bool deserialize(const string& text){
#ifdef MYSELF
auto pos=text.find(SEP);
//cout<<"text="<<text<<endl;
if(pos==std::string::npos)return false;
std::string ret_str=text.substr(0,pos);
std::string exitcode_str=text.substr(pos+SEP_LEN);
_exitcode=stoi(exitcode_str);
_ret=stoi(ret_str);
#else
Json::Value root;
Json::Reader reader;
reader.parse(text,root);
_exitcode=root["exitcode"].asInt();
_ret=root["ret"].asInt();
#endif
return true;
}
public:
int _exitcode;
int _ret;
};
Log.hpp
#pragma once
#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <cstdio>
#define NORMAL 0
#define DEBUG 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
using namespace std;
class LogMessage{
private:
string log_path="./log.txt";
string err_path="./err.txt";
static string getLevel(int level){
switch(level){
case NORMAL:
return "NORMAL";
break;
case DEBUG:
return "DEBUG";
break;
case WARNING:
return "WARNING";
break;
case ERROR:
return "ERROR";
break;
case FATAL:
return "FATAL";
break;
default:
return "OTHER";
}
}
string getTime(){
time_t now=time(nullptr);
struct tm* t=localtime(&now);
int year=t->tm_year+1900;
int mon=t->tm_mon+1;
int day=t->tm_mday;
int hour=t->tm_hour;
int min=t->tm_min;
int sec=t->tm_sec;
char buffer[64];
sprintf(buffer,"%d-%02d-%02d %02d:%02d:%02d",year,mon,day,hour,min,sec);
return buffer;
}
public:
void operator()(int level,const char* format,...){
string lev=getLevel(level);
string time=getTime();
va_list list;
va_start(list,format);
char msg[1024];
vsnprintf(msg,sizeof(msg),format,list);
FILE* log=fopen(log_path.c_str(),"a+");
FILE* err=fopen(err_path.c_str(),"a+");
if(log!=nullptr&&err!=nullptr){
FILE* cur=nullptr;
if(level==NORMAL||level==DEBUG){
cur=log;
}
else cur=err;
fprintf(cur,"[%s][%s][%s]\n",lev.c_str(),time.c_str(),msg);
fclose(log);
fclose(err);
}
}
};
static LogMessage log;
Task.hpp
#pragma once
#include <iostream>
#include "Protocal.hpp"
using namespace std;
//enum{OK=0,DIV_ZERO,MOD_ZERO,OP_ERR};
void cal(const Request& req,Response& resp){
char op=req._op;
int x=req._x;
int y=req._y;
int ret;
int exitcode=OK;
switch(op){
case '+':
ret=x+y;
break;
case '-':
ret=x-y;
break;
case '*':
ret=x*y;
break;
case '/':
if(y==0)exitcode=DIV_ZERO;
else ret=x/y;
break;
case '%':
if(y==0)exitcode=MOD_ZERO;
else ret=x%y;
break;
default:
exitcode=-1;
}
resp._ret=ret;
resp._exitcode=exitcode;
}
Public.hpp
#pragma once
#include <iostream>
enum{SOCKET_ERR=1,BIND_ERR,LISTEN_ERR,ACCEPT_ERR,USAGE_ERR,EPOLL_CREATE_ERR};