【Linux】简单的网络计算器的实现(自定义协议,序列化,反序列化)

文章目录

  • 前言
  • 一、 服务端
    • 1.ServerCal.cc(服务器主文件)
    • 2.ServerCal.hpp
    • 3.Sock.hpp(套接字封装)
    • 4.TcpServer.hpp(服务器)
    • 5.Protocol(自定义协议)
  • 二、用户端
    • 1.ClientCal
  • 三、Log.hpp(日志)
  • 四、makefile


前言

我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端`

一、 服务端

在这里插入图片描述

1.ServerCal.cc(服务器主文件)

 #include "TcpServer.hpp"
#include "ServerCal.hpp"
#include <unistd.h>
#include "Daemon.hpp"using namespace std;
static void Usage(const string &proc){cout<<"\nUsage: "<<proc<<" port\n"<<endl;
}int main(int argc,char*argv[]){if(argc!=2){Usage(argv[0]);exit(0);}uint16_t port=stoi(argv[1]);ServerCal cal;TcpServer*tsvp=new TcpServer(port,bind(&ServerCal::Calculator,&cal,placeholders::_1));//绑定服务器的回调函数tsvp->InitServer();//初始化服务器Daemon();//守护进程化tsvp->Start();//启动return 0;
}

2.ServerCal.hpp

#pragma once
#include <iostream>
#include "Protocol.hpp"enum
{Div_Zero = 1,Mod_Zero,Other_Oper
};class ServerCal
{
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;elseresp.result = req.x / req.y;}break;case '%':{if (req.y == 0)resp.code = Mod_Zero;elseresp.result = req.x % req.y;}break;default:resp.code = Other_Oper;break;}return resp;}// 传入的package形式:"len"\n"10 + 20"\nstd::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进行序列化放入contentresp.Serialize(&content);  // "30 0"//Encode:序列化后加上len与\ncontent = Encode(content); // "len"\n"30 0"return content;}~ServerCal(){}
};

3.Sock.hpp(套接字封装)

#pragma once#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
};const int backlog = 10;class Sock
{
public:Sock() {}~Sock(){}public:void Socket(){sockfd_ = socket(AF_INET, SOCK_STREAM, 0);if (sockfd_ < 0){lg(Fatal, "socker error,%s: %d", strerror(errno), errno);exit(SocketErr);}}void Bind(uint16_t port){struct sockaddr_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_, (struct sockaddr *)&local, sizeof(local)) < 0){lg(Fatal, "bind error ,%s: %d", strerror(errno), errno);exit(BindErr);}}void Listen(){if (listen(sockfd_, backlog) < 0){lg(Fatal, "listen error,%s: %d", strerror(errno), errno);exit(ListenErr);}}int Accept(string *clientip, uint16_t *clientport){struct sockaddr_in peer;socklen_t len = sizeof(peer);int newfd = accept(sockfd_, (struct sockaddr *)&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;}bool Connect(const string &ip, const uint16_t port){struct sockaddr_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_, (struct sockaddr *)&peer, sizeof(peer));if (n == -1){std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;return false;}return true;}void Close(){close(sockfd_);}int Fd(){return sockfd_;}private:int sockfd_;
};

4.TcpServer.hpp(服务器)

#pragma once
#include <functional>
#include <string>
#include <signal.h>
#include "Log.hpp"
#include "Socket.hpp"using func_t = std::function<std::string(std::string &package)>;class TcpServer
{
public:TcpServer(uint16_t port, func_t callback) : port_(port), callback_(callback){}bool InitServer(){listensock_.Socket();listensock_.Bind(port_);listensock_.Listen();lg(Info, "init server .... done");return true;}void Start(){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());}}else if (n == 0)break;elsebreak;}exit(0);}//父进程关闭多余的文件描述符close(sockfd);}}~TcpServer(){}private:uint16_t port_;Sock listensock_;func_t callback_;
};

5.Protocol(自定义协议)

#pragma once#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中移除
bool Decode(std::string &package, std::string *content)
{std::size_t pos = package.find(protocol_sep);if(pos == std::string::npos) return false;//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) return false;*content = package.substr(pos+1, len);// earse 移除报文 package.erase(0, total_len);package.erase(0, total_len);return true;
}// json, protobuf
class Request
{
public:Request(int data1, int data2, char oper) : x(data1), y(data2), op(oper){}Request(){}
public:bool Serialize(std::string *out){
#ifdef MySelf// 构建报文的有效载荷// 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;return true;
#elseJson::Value root;root["x"] = x;root["y"] = y;root["op"] = op;// Json::FastWriter w;Json::StyledWriter w;*out = w.write(root);return true;
#endif}bool Deserialize(const std::string &in) // 目的:从"x op y"中分离出x=?,y=?,op=?填入类的成员变量{
#ifdef MySelfstd::size_t left = in.find(blank_space_sep);if (left == std::string::npos)return false;std::string part_x = in.substr(0, left);std::size_t right = in.rfind(blank_space_sep);if (right == std::string::npos)return false;std::string part_y = in.substr(right + 1);if (left + 2 != right)return false;op = in[left + 1];x = std::stoi(part_x);y = std::stoi(part_y);return true;
#elseJson::Value root;Json::Reader r;r.parse(in, root);x = root["x"].asInt();y = root["y"].asInt();op = root["op"].asInt();return true;
#endif}void DebugPrint(){std::cout << "新请求构建完成:  " << x << op << y << "=?" << std::endl;}
public:// x op yint x;int y;char op; // + - * / %
};class Response
{
public:Response(int res, int c) : result(res), code(c){}Response(){}
public:bool Serialize(std::string *out){
#ifdef MySelf// "result code"// 构建报文的有效载荷std::string s = std::to_string(result);s += blank_space_sep;s += std::to_string(code);*out = s;return true;
#elseJson::Value root;root["result"] = result;root["code"] = code;// Json::FastWriter w;Json::StyledWriter w;*out = w.write(root);return true;
#endif}bool Deserialize(const std::string &in) // "result code"{
#ifdef MySelfstd::size_t pos = in.find(blank_space_sep);if (pos == std::string::npos)return false;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);return true;
#elseJson::Value root;Json::Reader r;r.parse(in, root);result = root["result"].asInt();code = root["code"].asInt();return true;
#endif}void DebugPrint(){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"static void Usage(const std::string &proc)
{std::cout << "\nUsage: " << proc << " serverip serverport\n"<< std::endl;
}// ./clientcal ip port
int main(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) return 1;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"\nstd::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();return 0;
}

三、Log.hpp(日志)

详细可参考我之前写的博客【Linux】记录错误信息日志的实现

#pragma once#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>using namespace std;#define SIZE 1024#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 LogFile "log.txt"class Log{
public:
Log(){printMethod=Screen;path="./log/";
}void Enable(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";}}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){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);
}~Log(){}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *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 clientcalFlag=#-DMySelf=1
Lib=-ljsoncppservercal:ServerCal.ccg++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
clientcal:ClientCal.ccg++ -o $@ $^ -std=c++11 $(Lib) $(Flag).PHONY:clean
clean:rm -f clientcal servercal

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/475583.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java_方法(重载方法签名等详解)

在之前我们学习C语言时&#xff0c;当我们想要重复使用某段代码的功能时&#xff0c;我们会将这段代码定义为一个函数&#xff0c;而在java中我们把这段重复使用的代码叫做方法。 方法的定义 类体的内容分为变量的声明和方法的定义&#xff0c;方法的定义包括两部分&#xff1…

2024/2/18:IO进程线程

作业1&#xff1a;使用fgets统计给定文件的行数 代码&#xff1a; #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, const char *argv[]) {//定义FILE *类型的句柄FILE *fpNULL;//判断是否进行了外部传参if(argc ! 2){pri…

2.14日学习打卡----初学Zookeeper(一)

2.14日学习打卡 目录: 2.14日学习打卡Zookeeper概念一. 集中式到分布式单机架构集群架构什么是分布式三者区别 二. CAP定理分区容错性一致性可用性一致性和可用性的矛盾一致性和可用性如何选择 三. 什么是Zookeeper分布式架构Zookeeper从何而来Zookeeper介绍 四. 应用场景数据发…

mysql 2-18

加密与解密函数 其他函数 聚合函数 三者效率 GROUP BY HAVING WHERE和HAVING的区别 子查询 单行子查询和多行子查询 单行比较操作符 多行比较操作符 把平均工资生成的结果当成一个新表 相关子查询 EXISTS 一条数据的存储过程 标识符命名规则 创建数据库 MYSQL的数据类型 创建表…

WordPress管理员修改自己用户名的插件Username

有一些站长在刚开搭建WordPress网站时&#xff0c;对于管理员的用户名是随意输入&#xff0c;后来想要修改时发现不懂得如何下手。其实&#xff0c;修改WordPress管理员用户名最快速的方法就是进入数据库直接修改&#xff0c;详见『通过phpMyAdmin直接修改WordPress用户名的图文…

万字长文!非常详细!操作系统【内存管理】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;OS从基础到进阶 本文配套PDF文件&#xff0c;请浏览至文章底部下载。 1 内存管理基础概念1.1 总览1.2 内存管理应有的功能1.2.1 内存空间的分配和回收1.2.2 从逻辑上扩充内存1.2.3 地址转…

品牌/企业/人物百度百科创建攻略:一文掌握百度百科创建与审核要点

在数字化时代&#xff0c;百科全书已经成为大众获取知识和信息的重要渠道。无论是品牌、企业还是个人&#xff0c;拥有一个专属的百度百科词条&#xff0c;不仅可以提升知名度&#xff0c;还可以展现自己的实力和特色。那么&#xff0c;如何创建百度百科词条&#xff0c;并通过…

windows安装Mysql解压版

windows安装Mysql解压版 一、下载mysql-8.0.36-winx64.zip二、解压三、配置3.1. 添加环境变量&#xff1a;新建MYSQL_HOME3.2.如何验证是否添加成功&#xff1a;必须以管理员身份启动3.3. 初始化MySQL&#xff1a;必须以管理员身份启动3.4. 注册MySQL服务&#xff1a;必须以管理…

【软考中级备考笔记】数据的表示和校验码

2024/2/18 – 数据的表示和校验码 天气&#xff1a;阴雨 春节假期结束后第一个工作日&#xff0c;开始备考中级软件工程师。 希望在今年5月底的软考中取得中级证书 视频地址&#xff1a;https://www.bilibili.com/video/BV1Qc411G7fB 1. 计算机的总体架构 从下图中可以看出&am…

文件上传漏洞--Upload-labs--Pass03--特殊后缀与::$DATA绕过

方法一&#xff1a;特殊后缀绕过&#xff1a; 一、什么是特殊后缀绕过 源代码中的黑名单禁止一系列后缀名 之外的后缀&#xff0c;称之为‘特殊后缀名’&#xff0c;利用其来绕过黑名单&#xff0c;达到上传含有恶意代码的文件的目的。 二、代码审计 接下来对代码逐条拆解进行…

网络安全实验(三)

1.办公区设备可以通过电信和移动两条链路上网&#xff0c;且需要保留一个公网ip不能用来转换 2.分公司设备可以通过两条链路访问到dmz区域的http服务器 3.分公司内部客户端可以通过公网地址访问到内部服务器 4.FW1和FW3组成主备模式的双击热备 5.办公区上网用户限制流量不超…

鸿蒙开发系列教程(二十四)--List 列表操作(3)

列表编辑 1、新增列表项 定义列表项数据结构和初始化列表数据&#xff0c;构建列表整体布局和列表项。 提供新增列表项入口&#xff0c;即给新增按钮添加点击事件。 响应用户确定新增事件&#xff0c;更新列表数据。 2、删除列表项 列表的删除功能一般进入编辑模式后才可…