目录
- 网络版本计算器
- 1.1 TcpServer.hpp
- 1.2 ServerCal.hpp
- 1.3 ServerCal.cc
- 1.4 Protocol.hpp
- 1.5 Socket.hpp
- 1.6 makefile
- 1.7 ClientCal.cc
- 1.8 log.hpp
网络版本计算器
1.1 TcpServer.hpp
#pragma once#include "Protocol.hpp"
#include "Socket.hpp"
#include <functional>
#include "log.hpp"
#include <signal.h>using func_t = function<string(string &)>;class TcpServer
{
public:TcpServer(const uint16_t &port, func_t callback): _port(port), _callback(callback){}~TcpServer(){}bool InitServer(){// 创建套接字_listenSock.Socket();int sockfd = _listenSock.Sockfd();//设置端口复用int opt = 1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));// 绑定_listenSock.Bind(_port);// 监听_listenSock.Listen();log(Info, "Init server successed...");return true;}void Start(){// 守护进程化//参数中的第一个0表示更改该进程的路径为根目录执行,第二个0表示//把0,1,2三个文件描述符输出的东西重定向到 /dev/null 文件中daemon(0,0);// 屏蔽一些信号signal(SIGPIPE, SIG_IGN);signal(SIGCHLD, SIG_IGN);while (true){string clientip;uint16_t clientport;int sockfd = _listenSock.Accept(clientip, clientport);if (sockfd > 0){if (fork() == 0){// 子进程继承了父进程的监听套接字,但是并用不上,所以可以close掉_listenSock.Close();// 提供服务string inbuffer_stream;while (true){char tmp[10240];bzero(tmp, sizeof(tmp));ssize_t n = read(sockfd, tmp, sizeof(tmp) - 1);if (n > 0){tmp[n] = '\0';inbuffer_stream += tmp;cout<<"=========================="<<endl;cout<<inbuffer_stream<<endl;cout<<"=========================="<<endl;string ret;while (true){//只要返回的不是一个空字符串,就说明请求还没有//处理完,那么就继续处理剩下的请求,把处理结果//存在ret中string str = _callback(inbuffer_stream);if (str.empty()){break;}ret+=str;}//把请求发送给客户端write(sockfd, ret.c_str(), ret.size());}else if (n == 0){log(Info, "client exit...");break;}else{log(Fatal, "read failed,errno:%d,errstring:%s", errno, strerror(errno));break;}}close(sockfd);exit(0);}close(sockfd);}}}private:Sock _listenSock;uint16_t _port;func_t _callback;
};
1.2 ServerCal.hpp
#pragma once#include <iostream>
#include "Protocol.hpp"enum
{DivZeroErr,ModZeroErr,OtherErr
};class ServerCal
{
public:ServerCal(){}~ServerCal(){}Response CalculatorHelper(const Request &req){Response resp(0, 0);int x = req._x;int y = req._y;switch (req._op){case '+':{resp._result = x + y;break;}case '-':{resp._result = x - y;break;}case '*':{resp._result = x * y;break;}case '/':{if (y == 0){resp._code = DivZeroErr;break;}resp._result = x / y;break;}case '%':{if (y == 0){resp._code = ModZeroErr;break;}resp._result = x % y;break;}default:{resp._code = OtherErr;break;}}return resp;}//package: "len"\n"1 + 1"\nstring Calculator(string& package){string content;//解码bool ret=Decode(package,content);if(!ret){return "";}cout<<"///"<<endl;cout<<package<<endl;cout<<"///"<<endl;Request req;//反序列化ret=req.Request_DeSerialize(content);if(!ret){return "";}cout<<"get a new task:";printf("%d %c %d\n",req._x,req._op,req._y);//传入请求,返回响应Response resp=CalculatorHelper(req);//序列化string resp_str=resp.Response_Serialize();//添加报头Encode(resp_str);return resp_str;}
};
1.3 ServerCal.cc
#include <iostream>
using namespace std;
#include "ServerCal.hpp"
#include "Protocol.hpp"
#include "TcpServer.hpp"
#include <memory>// int main()
// {
// Request req(145,20,'%');
// string ret=req.Request_Serialize();
// Encode(ret);
// cout<<ret<<endl;// cout<<endl;
// Request r;
// r.Request_DeSerialize(ret);
// cout<<r._x<<endl;
// cout<<r._op<<endl;
// cout<<r._y<<endl;// string content;
// Decode(ret,content);
// cout<<content<<endl;// // Response resp(10,0);
// // string ret=resp.Response_Serialize();
// // Encode(ret);
// // cout<<ret<<endl;// // string content;
// // Decode(ret,content);
// // cout<<content<<endl;// // Response r;
// // r.Response_DeSerialize(content);
// // cout<<r._result<<endl;
// // cout<<r._code<<endl;// return 0;
// }void Usage(string proc)
{cout<<"\n\t";cout<<"Usage: "<<proc<<" serverPort"<<endl<<endl;
}//Usage: ./servercal
int main(int argc,char* argv[])
{if(argc!=2){Usage(argv[0]);exit(1);}uint16_t serverPort=(uint16_t)stoi(argv[1]);ServerCal cal;//服务器绑定的是Calculator函数,该函数有两个参数,一个绑定为Calculator的this指针,另一个参数自己传unique_ptr<TcpServer> svr(new TcpServer(serverPort,bind(&ServerCal::Calculator,&cal,placeholders::_1)));//初始化svr->InitServer();//启动svr->Start();return 0;
}
1.4 Protocol.hpp
#pragma once
#include <iostream>
using namespace std;
#include "log.hpp"
#include <jsoncpp/json/json.h>const static string blank_space_sep = " ";
const static string protocol_sep = "\n";// #define MySelfextern Log log;// 添加报头和分隔符
void Encode(string &content)
{size_t len = content.size();content = to_string(len) + protocol_sep + content + protocol_sep;
}// 去掉报头,如果解码成功,则继续移除报文,否则,直接返回false
bool Decode(string &package, string &content)
{size_t pos = package.find(protocol_sep);// 找不到\n不一定是报文的格式错误,也有可能是报文不完整导致的,所以这里// 无需打错误日志信息if (pos == string::npos){// log(Warning, "this package format is failed,package:%s", package.c_str());return false;}string len_str = package.substr(0, pos);size_t len = stoi(len_str);size_t total_len = len_str.size() + len + 2;if (package.size() < total_len){log(Warning, "this package format is failed,package:%s", package.c_str());return false;}content = package.substr(pos + 1, len);// 解码成功则移除报文package.erase(0, total_len);return true;
}class Request
{
public:Request(){}Request(int data1, int data2, char oper): _x(data1), _y(data2), _op(oper){}~Request(){}// 序列化,即把结构化的数据序列化成字符串// 格式:"len"\n"x [+-*/%] y"\nstring Request_Serialize(){
#ifdef MySelfstring content;content += to_string(_x);content += blank_space_sep;content += _op;content += blank_space_sep;content += to_string(_y);return content;
#else//用第三方库jsoncpp进行序列化//Json::Value是一个key:value的万能对象Json::Value root;root["x"] = _x;root["op"] = _op;root["y"] = _y;Json::FastWriter w;string content = w.write(root);return content;
#endif}// 反序列化bool Request_DeSerialize(const string &content_str){
#ifdef MySelfsize_t left = content_str.find(blank_space_sep);if (left == string::npos){log(Warning, "this content_str format is failed,content_str:%s", content_str.c_str());return false;}//取出content_str的前半部分string left_str = content_str.substr(0, left);size_t right = content_str.rfind(blank_space_sep);if (right == string::npos){log(Warning, "this content_str format is failed,content_str:%s", content_str.c_str());return false;}//取出content_str的后半部分string right_str = content_str.substr(right + 1);if (left + 2 != right){log(Warning, "this content_str format is failed,content_str:%s", content_str.c_str());return false;}_x = stoi(left_str);_y = stoi(right_str);_op = content_str[left + 1];return true;
#else//万能对象Json::Value v;Json::Reader r;//把字符串形式的content_str变成结构化的数据,并写到万能对象中去r.parse(content_str, v);_x = v["x"].asInt();_op = v["op"].asInt();_y = v["y"].asInt();return true;
#endif}public:int _x;int _y;char _op;
};class Response
{
public:Response(){}Response(const int &result, const int &code = 0): _result(result), _code(code){}~Response(){}// 格式:"len"\n"result code"\nstring Response_Serialize(){
#ifdef MySelfstring package;string result_str = to_string(_result);string code_str = to_string(_code);size_t body_len = result_str.size() + code_str.size();package += result_str;package += blank_space_sep;package += code_str;return package;
#else// 万能对象Json::Value root;root["result"] = _result;root["code"] = _code;Json::FastWriter w;string package = w.write(root);return package;
#endif}bool Response_DeSerialize(const string &content_str){
#ifdef MySelfsize_t content_left = content_str.find(blank_space_sep);if (content_left == string::npos){log(Warning, "this content_str format is failed,content_str:%s", content_str.c_str());return false;}string content_left_str = content_str.substr(0, content_left);string content_right_str = content_str.substr(content_left + 1);_result = stoi(content_left_str);_code = stoi(content_right_str);return true;
#else//Json::Value v是万能对象Json::Value v;Json::Reader r;r.parse(content_str,v);_result=v["result"].asInt();_code=v["code"].asInt();return true;
#endif}public:int _result;int _code = 0;
};
1.5 Socket.hpp
#pragma once#include <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
#include <unistd.h>
#include <strings.h>
#include <cstring>
#include <string>int backlog = 10;enum
{SockErr = 2,BindErr,ListenErr,ConnectErr,
};class Sock
{
public:Sock(): _sockfd(-1){}~Sock(){if(_sockfd>0){close(_sockfd);}}// 创建套接字void Socket(){_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0){log(Fatal, "socket failed,errno:%d,errstring:%s", errno, strerror(errno));exit(SockErr);}log(Info, "socket successed, sockfd:%d", _sockfd);}// 绑定void Bind(const uint16_t &serverPort){struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(serverPort);local.sin_addr.s_addr = INADDR_ANY;if (bind(_sockfd, (struct sockaddr *)(&local), sizeof(local)) < 0){log(Fatal, "bind failed,errno:%d,errstring:%s", errno, strerror(errno));exit(BindErr);}log(Info, "bind successed...");}// 监听void Listen(){if (listen(_sockfd, backlog) < 0){log(Fatal, "set listen state failed,errno:%d,errstring:%s", errno, strerror(errno));exit(ListenErr);}log(Info, "set listen state successed");}//获取连接int Accept(string& clientip,uint16_t& clientport){struct sockaddr_in client;socklen_t len=sizeof(client);bzero(&client,sizeof(client));int sockfd=accept(_sockfd,(struct sockaddr*)(&client),&len);if(sockfd<0){log(Warning, "accept new link failed,errno:%d,errstring:%s", errno, strerror(errno));return -1;}log(Info,"accept a new link...,sockfd:%d",sockfd);clientip=inet_ntoa(client.sin_addr);clientport=(uint16_t)(ntohs(client.sin_port));return sockfd;}// 连接void Connect(const string &serverIp, const uint16_t &serverPort){struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverIp.c_str());server.sin_port = htons(serverPort);if (connect(_sockfd, (struct sockaddr *)(&server), sizeof(server)) < 0){log(Fatal, "connect server failed,errno:%d,errstring:%s", errno, strerror(errno));exit(ConnectErr);}log(Info, "connect server succeeded...");}void Close(){if(_sockfd>0){close(_sockfd);}}int Sockfd(){return _sockfd;}private:int _sockfd;
};
1.6 makefile
.PHONY:all
all:servercal clientcalservercal:ServerCal.ccg++ -o $@ $^ -std=c++11 -ljsoncpp
clientcal:ClientCal.ccg++ -o $@ $^ -std=c++11 -ljsoncpp.PHONY:clean
clean:rm -f clientcal servercal
1.7 ClientCal.cc
#include <iostream>
using namespace std;
#include "Socket.hpp"
#include "Protocol.hpp"
#include <ctime>void Usage(string proc)
{cout << "\n\t";cout << "Usage: " << proc << " serverip serverPort" << endl<< endl;
}// Usage: ./servercal
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}srand(time(nullptr));string serverIp = argv[1];uint16_t serverPort = (uint16_t)stoi(argv[2]);// 创建套接字Sock sock;sock.Socket();// 连接sock.Connect(serverIp, serverPort);string oper = "+-*/%&^$@#";int sockfd = sock.Sockfd();string package;while (true){int x = rand() % 100 + 1;int y = rand() % 100;char op = oper[rand() % oper.size()];//构建请求Request req(x, y, op);//序列化string req_str = req.Request_Serialize();cout << "req:" << req_str << endl;//添加报头Encode(req_str);cout << "这是最新的需要发到网络上去的报文:" << endl;cout << req_str << endl;write(sockfd, req_str.c_str(), req_str.size());char buffer[10240];bzero(buffer, sizeof(buffer));int n = read(sockfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = '\0';package += buffer;while (true){string content;Response resp;//解码bool ret = Decode(package, content);if(!ret){//如果解码失败,说明package出问题了或者package中的//内容不是一个完整的报文,跳出本层循环break;}//来到这里说明解码成功cout << content << endl;//反序列化resp.Response_DeSerialize(content);cout << "len : " << content.size() << endl;cout << "resp._result : " << resp._result << endl;cout << "resp._code : " << resp._code << endl;}}else if (n == 0){cout << "server quit..." << endl;break;}else{perror("read failed");break;}sleep(10);}sock.Close();return 0;
}
1.8 log.hpp
#pragma once#include <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string>
#include <time.h>
#include <stdarg.h>// 日志等级
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define OneFile 2
//向多个文件打印
#define Classfile 3
#define SIZE 1024#define LogFile "log.txt"class Log
{
public:Log(){printMethod = Screen;path = "./log/";}void Enable(int mothod){printMethod = mothod;}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";}}}void printlog(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;}}}void PrintOneFile(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){perror("open fail");return;}write(fd,logtxt.c_str(),logtxt.size());close(fd);}void PrintClassfile(int level,const string& logtxt){string filename=LogFile;filename+='.';filename+=LevelToString(level);PrintOneFile(filename,logtxt);}void operator()(int level,const char* format,...){time_t t=time(nullptr);struct tm* ctime=localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer,SIZE,"[%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]={0};vsnprintf(rightbuffer,SIZE,format,s);va_end(s);char logtxt[SIZE*2];snprintf(logtxt,sizeof(logtxt),"%s %s",leftbuffer,rightbuffer);printlog(level,logtxt);}~Log(){}private:// 打印方法int printMethod;string path;
};//定义一个全局的log
Log log;
以上就是关于网络版本计算器的全部实现过程了,你学会了吗?如果感觉到有所收获的话,就点点赞点点关注呗,后期还会持续更新网络编程的相关知识哦,我们下期见!!!