文章目录
前言
我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端`
一、 服务端
1.ServerCal.cc(服务器主文件)
#include"TcpServer.hpp"#include"ServerCal.hpp"#include<unistd.h>#include"Daemon.hpp"usingnamespace std;staticvoidUsage(const string &proc){
cout<<"\nUsage: "<<proc<<" port\n"<<endl;}intmain(int argc,char*argv[]){if(argc!=2){Usage(argv[0]);exit(0);}uint16_t port=stoi(argv[1]);
ServerCal cal;
TcpServer*tsvp=newTcpServer(port,bind(&ServerCal::Calculator,&cal,placeholders::_1));//绑定服务器的回调函数
tsvp->InitServer();//初始化服务器Daemon();//守护进程化
tsvp->Start();//启动return0;}
2.ServerCal.hpp
#pragmaonce#include<iostream>#include"Protocol.hpp"enum{
Div_Zero =1,
Mod_Zero,
Other_Oper
};classServerCal{public:ServerCal(){}
Response CalculatorHelper(const Request &req){
Response resp(0,0);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(req.y ==0)
resp.code = Div_Zero;else
resp.result = req.x / req.y;}break;case'%':{if(req.y ==0)
resp.code = Mod_Zero;else
resp.result = req.x % req.y;}break;default:
resp.code = Other_Oper;break;}return resp;}// 传入的package形式:"len"\n"10 + 20"\n
std::string Calculator(std::string &package){
std::string content;//Decode:去掉字符大小len和\nbool r =Decode(package,&content);// "len"\n"10 + 20"\nif(!r)return"";// Decode后:"10 + 20"
Request req;
r = req.Deserialize(content);// 反序列化过程:"10 + 20" ->x=10 op=+ y=20if(!r)return"";
content ="";//进行计算
Response resp =CalculatorHelper(req);// result=30 code=0;//result与code进行序列化放入content
resp.Serialize(&content);// "30 0"//Encode:序列化后加上len与\n
content =Encode(content);// "len"\n"30 0"return content;}~ServerCal(){}};
3.Sock.hpp(套接字封装)
#pragmaonce#include<iostream>#include<string>#include<unistd.h>#include<cstring>#include<sys/types.h>#include<sys/stat.h>#include<sys/socket.h>#include<arpa/inet.h>#include<netinet/in.h>#include"Log.hpp"enum{
SocketErr =2,
BindErr,
ListenErr
};constint backlog =10;classSock{public:Sock(){}~Sock(){}public:voidSocket(){
sockfd_ =socket(AF_INET, SOCK_STREAM,0);if(sockfd_ <0){lg(Fatal,"socker error,%s: %d",strerror(errno), errno);exit(SocketErr);}}voidBind(uint16_t port){structsockaddr_in local;memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port =htons(port);
local.sin_addr.s_addr = INADDR_ANY;if(bind(sockfd_,(structsockaddr*)&local,sizeof(local))<0){lg(Fatal,"bind error ,%s: %d",strerror(errno), errno);exit(BindErr);}}voidListen(){if(listen(sockfd_, backlog)<0){lg(Fatal,"listen error,%s: %d",strerror(errno), errno);exit(ListenErr);}}intAccept(string *clientip,uint16_t*clientport){structsockaddr_in peer;
socklen_t len =sizeof(peer);int newfd =accept(sockfd_,(structsockaddr*)&peer,&len);if(newfd <0){lg(Warning,"accept error,%s: %d",strerror(errno), errno);return-1;}char ipstr[64];inet_ntop(AF_INET,&peer.sin_addr, ipstr,sizeof(ipstr));*clientip = ipstr;*clientport =ntohs(peer.sin_port);return newfd;}boolConnect(const string &ip,constuint16_t port){structsockaddr_in peer;memset(&peer,0,sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port =htons(port);inet_pton(AF_INET, ip.c_str(),&(peer.sin_addr));int n =connect(sockfd_,(structsockaddr*)&peer,sizeof(peer));if(n ==-1){
std::cerr <<"connect to "<< ip <<":"<< port <<" error"<< std::endl;returnfalse;}returntrue;}voidClose(){close(sockfd_);}intFd(){return sockfd_;}private:int sockfd_;};
4.TcpServer.hpp(服务器)
#pragmaonce#include<functional>#include<string>#include<signal.h>#include"Log.hpp"#include"Socket.hpp"using func_t = std::function<std::string(std::string &package)>;classTcpServer{public:TcpServer(uint16_t port, func_t callback):port_(port),callback_(callback){}boolInitServer(){
listensock_.Socket();
listensock_.Bind(port_);
listensock_.Listen();lg(Info,"init server .... done");returntrue;}voidStart(){signal(SIGCHLD, SIG_IGN);//防止子进程僵尸,子进程执行完PCB自动释放signal(SIGPIPE, SIG_IGN);//程序将忽略 SIGPIPE 信号,这意味着即使发生了向已关闭的管道或者 socket //连接中写数据的情况,程序也不会因为收到 SIGPIPE 而终止。//这样可以让程序继续正常执行,而不受这种情况的影响。while(true){
std::string clientip;uint16_t clientport;int sockfd = listensock_.Accept(&clientip,&clientport);if(sockfd <0)continue;lg(Info,"accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);// 子进程提供服务if(fork()==0){
listensock_.Close();
std::string inbuffer_stream;// 数据计算while(true){//读到客户端发送的报文char buffer[1280];
ssize_t n =read(sockfd, buffer,sizeof(buffer));if(n >0){//说明读取报文成功
buffer[n]=0;//每次都加上读取的数据是因为有可能我的一个完整报文分两次读取//加上第二次的才能算上一个完整的报文
inbuffer_stream += buffer;lg(Debug,"debug:\n%s", inbuffer_stream.c_str());while(true){
std::string info =callback_(inbuffer_stream);//调用string Calculator(std::string &package)if(info.empty())//没有读出说明数据处理失败break;lg(Debug,"debug, response:\n%s", info.c_str());lg(Debug,"debug:\n%s", inbuffer_stream.c_str());//write写入处理好后的计算过的数据(经过了序列化)write(sockfd, info.c_str(), info.size());}}elseif(n ==0)break;elsebreak;}exit(0);}//父进程关闭多余的文件描述符close(sockfd);}}~TcpServer(){}private:uint16_t port_;
Sock listensock_;
func_t callback_;};
5.Protocol(自定义协议)
#pragmaonce#include<iostream>#include<string>#include<jsoncpp/json/json.h>const std::string blank_space_sep =" ";const std::string protocol_sep ="\n";//编码:给序列化后的字符串加上字符长度与\n
std::string Encode(std::string &content){
std::string package = std::to_string(content.size());
package += protocol_sep;
package += content;
package += protocol_sep;// "len"\n"x op y"\nXXXXXXreturn package;}// "protocolnumber"\n"len"\n"x op y"\nXXXXXX//目的:截取字符串“x op y”放入content中,并把与此字符串//相关的数据从总报文package中移除boolDecode(std::string &package, std::string *content){
std::size_t pos = package.find(protocol_sep);if(pos == std::string::npos)returnfalse;//len_str:len的字符串形式
std::string len_str = package.substr(0, pos);
std::size_t len = std::stoi(len_str);// package = len_str + content_str + 2//
std::size_t total_len = len_str.size()+ len +2;if(package.size()< total_len)returnfalse;*content = package.substr(pos+1, len);// earse 移除报文 package.erase(0, total_len);
package.erase(0, total_len);returntrue;}// json, protobufclassRequest{public:Request(int data1,int data2,char oper):x(data1),y(data2),op(oper){}Request(){}public:boolSerialize(std::string *out){#ifdefMySelf// 构建报文的有效载荷// struct => string, "x op y"
std::string s = std::to_string(x);
s += blank_space_sep;
s += op;
s += blank_space_sep;
s += std::to_string(y);*out = s;returntrue;#else
Json::Value root;
root["x"]= x;
root["y"]= y;
root["op"]= op;// Json::FastWriter w;
Json::StyledWriter w;*out = w.write(root);returntrue;#endif}boolDeserialize(const std::string &in)// 目的:从"x op y"中分离出x=?,y=?,op=?填入类的成员变量{#ifdefMySelf
std::size_t left = in.find(blank_space_sep);if(left == std::string::npos)returnfalse;
std::string part_x = in.substr(0, left);
std::size_t right = in.rfind(blank_space_sep);if(right == std::string::npos)returnfalse;
std::string part_y = in.substr(right +1);if(left +2!= right)returnfalse;
op = in[left +1];
x = std::stoi(part_x);
y = std::stoi(part_y);returntrue;#else
Json::Value root;
Json::Reader r;
r.parse(in, root);
x = root["x"].asInt();
y = root["y"].asInt();
op = root["op"].asInt();returntrue;#endif}voidDebugPrint(){
std::cout <<"新请求构建完成: "<< x << op << y <<"=?"<< std::endl;}public:// x op yint x;int y;char op;// + - * / %};classResponse{public:Response(int res,int c):result(res),code(c){}Response(){}public:boolSerialize(std::string *out){#ifdefMySelf// "result code"// 构建报文的有效载荷
std::string s = std::to_string(result);
s += blank_space_sep;
s += std::to_string(code);*out = s;returntrue;#else
Json::Value root;
root["result"]= result;
root["code"]= code;// Json::FastWriter w;
Json::StyledWriter w;*out = w.write(root);returntrue;#endif}boolDeserialize(const std::string &in)// "result code"{#ifdefMySelf
std::size_t pos = in.find(blank_space_sep);if(pos == std::string::npos)returnfalse;
std::string part_left = in.substr(0, pos);
std::string part_right = in.substr(pos+1);
result = std::stoi(part_left);
code = std::stoi(part_right);returntrue;#else
Json::Value root;
Json::Reader r;
r.parse(in, root);
result = root["result"].asInt();
code = root["code"].asInt();returntrue;#endif}voidDebugPrint(){
std::cout <<"结果响应完成, result: "<< result <<", code: "<< code << std::endl;}public:int result;int code;// 0,可信,否则!0具体是几,表明对应的错误原因};
二、用户端
1.ClientCal
#include<iostream>#include<string>#include<ctime>#include<cassert>#include<unistd.h>#include"Socket.hpp"#include"Protocol.hpp"staticvoidUsage(const std::string &proc){
std::cout <<"\nUsage: "<< proc <<" serverip serverport\n"<< std::endl;}// ./clientcal ip portintmain(int argc,char*argv[]){if(argc !=3){Usage(argv[0]);exit(0);}
std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);
Sock sockfd;
sockfd.Socket();bool r = sockfd.Connect(serverip, serverport);if(!r)return1;srand(time(nullptr)^getpid());int cnt =1;const std::string opers ="+-*/%=-=&^";
std::string inbuffer_stream;while(cnt <=10){
std::cout <<"===============第"<< cnt <<"次测试....., "<<"==============="<< std::endl;int x =rand()%100+1;usleep(1234);int y =rand()%100;usleep(4321);char oper = opers[rand()%opers.size()];
Request req(x, y, oper);//构造需求
req.DebugPrint();
std::string package;
req.Serialize(&package);
package =Encode(package);//经过序列化传给服务器write(sockfd.Fd(), package.c_str(), package.size());char buffer[128];
ssize_t n =read(sockfd.Fd(), buffer,sizeof(buffer));// 我们也无法保证我们能读到一个完整的报文if(n >0){//读取服务器处理后的报文
buffer[n]=0;
inbuffer_stream += buffer;// "len"\n"result code"\n
std::cout << inbuffer_stream << std::endl;
std::string content;bool r =Decode(inbuffer_stream,&content);// 目的:得到字符串"result code"assert(r);
Response resp;
。。反序列化
r = resp.Deserialize(content);assert(r);
resp.DebugPrint();}
std::cout <<"================================================="<< std::endl;sleep(1);
cnt++;}
sockfd.Close();return0;}
三、Log.hpp(日志)
详细可参考我之前写的博客【Linux】记录错误信息日志的实现
#pragmaonce#include<iostream>#include<time.h>#include<stdarg.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<stdlib.h>usingnamespace std;#defineSIZE1024#defineInfo0#defineDebug1#defineWarning2#defineError3#defineFatal4#defineScreen1#defineOnefile2#defineClassFile3#defineLogFile"log.txt"classLog{public:Log(){
printMethod=Screen;
path="./log/";}voidEnable(int method){
printMethod=method;}
string levelToString(int level){switch(level){case Info:return"Info";case Debug:return"Debug";case Warning:return"Warning";case Error:return"Error";case Fatal:return"Fatal";default:return"None";}}voidprintLog(int level,const string&logtxt){switch(printMethod){case Screen:
cout<<logtxt<<endl;break;case Onefile:printOneFile(LogFile,logtxt);break;case ClassFile:printClassFile(level,logtxt);break;default:break;}}voidprintOneFile(const string&logname,const string&logtxt){
string _logname=path+logname;int fd=open(_logname.c_str(),O_WRONLY|O_CREAT|O_APPEND,0666);if(fd<0){return;}write(fd,logtxt.c_str(),logtxt.size());close(fd);}voidprintClassFile(int level,const string&logtxt){
string filename=LogFile;
filename+=".";
filename+=levelToString(level);printOneFile(filename,logtxt);}~Log(){}voidoperator()(int level,constchar*format,...){
time_t t =time(nullptr);structtm*ctime =localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%d-%d-%d %d:%d:%d]",levelToString(level).c_str(),
ctime->tm_year +1900, ctime->tm_mon +1, ctime->tm_mday,
ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer,sizeof(rightbuffer), format, s);va_end(s);// 格式:默认部分+自定义部分char logtxt[SIZE *2];snprintf(logtxt,sizeof(logtxt),"%s %s", leftbuffer, rightbuffer);// printf("%s", logtxt); // 暂时打印printLog(level, logtxt);}private:int printMethod;
string path;};
Log lg;
四、makefile
.PHONY:all
all:servercal clientcal
Flag=#-DMySelf=1
Lib=-ljsoncpp
servercal:ServerCal.cc
g++-o $@ $^-std=c++11 $(Lib) $(Flag)
clientcal:ClientCal.cc
g++-o $@ $^-std=c++11 $(Lib) $(Flag).PHONY:clean
clean:
rm -f clientcal servercal
本文转载自: https://blog.csdn.net/m0_74774759/article/details/136155128
版权归原作者 Kaugo 所有, 如有侵权,请联系我们删除。
版权归原作者 Kaugo 所有, 如有侵权,请联系我们删除。