当前位置: 首页 > news >正文

17.TCP编程(二)和序列化

一.概念回顾

建议先学上篇博客,再向下学习,上篇博客的链接如下:

https://blog.csdn.net/weixin_60668256/article/details/154752573?fromshare=blogdetail&sharetype=blogdetail&sharerId=154752573&sharerefer=PC&sharesource=weixin_60668256&sharefrom=from_link

二.远程命令分析功能

我们还是在原来的代码的基础上,增加一个CommandExec的类(我们可以把我们原来的shell直接拿来)

#pragma once#include <iostream>
#include <string>class Command
{
public://给你一个命令字符串"ls -l",让你执行,执行完,把结果进行返回std::string Execute(std::string cmdstr){//1. pipe//2. fork + dup2(pipe[1],1) + exec*,把执行结果交给父进程(pipe[0])//3. returnreturn std::string();}
};

但是有一个接口完成了上面的基本功能

//给你一个命令字符串"ls -l",让你执行,执行完,把结果进行返回std::string Execute(std::string cmdstr){//1. pipe//2. fork + dup2(pipe[1],1) + exec*,把执行结果交给父进程(pipe[0])//3. returnFILE* fp = ::popen(cmdstr.c_str(),"r");if(nullptr == fp){return std::string("Failed");}char buffer[line_size];std::string result;while(true){char* ret = ::fgets(buffer,sizeof(buffer),fp);if(!ret){break;}result += ret;}pclose(fp);return result.empty() ? std::string("Done") : result;}

三.将可执行命令设置进入白名单(防止危险命令)

#pragma once#include <iostream>
#include <string>
#include <cstdio>
#include <set>const int line_size = 1024;class Command
{
public:Command(){_white_list.insert("ls");_white_list.insert("pwd");_white_list.insert("ls -l");_white_list.insert("ll");_white_list.insert("touch");_white_list.insert("who");_white_list.insert("whoami");}bool SafeCheck(const std::string& cmdstr){if(_white_list.count(cmdstr)){return true;}return false;}//给你一个命令字符串"ls -l",让你执行,执行完,把结果进行返回std::string Execute(std::string cmdstr){//1. pipe//2. fork + dup2(pipe[1],1) + exec*,把执行结果交给父进程(pipe[0])//3. returnif(!SafeCheck(cmdstr)){return std::string(cmdstr + "不支持");}FILE* fp = ::popen(cmdstr.c_str(),"r");if(nullptr == fp){return std::string("Failed");}char buffer[line_size];std::string result;while(true){char* ret = ::fgets(buffer,sizeof(buffer),fp);if(!ret){break;}result += ret;}pclose(fp);return result.empty() ? std::string("Done") : result;}
private:std::set<std::string> _white_list;
};

这里我们采取的是严格匹配,所以可执行的命令较少(可以用shell,但是安全一定要做好)

这里我们就基本完成了TCP套接字的初次使用

四.应用层协议

1.协议讲解

但是这样是不行的,对于我们的(两个机器位数,平台等)不同,这样数据会出现不匹配的问题

2.重新理解read、write、recv、send和tcp为什么支持全双工

这里操作系统会对于一个sockfd创建对应的两个缓冲区,用于发送和接收

两对接收和发送缓冲区,所以对应的就是全双工的了

3.面向字节流

TCP的发送方式就是基于拥塞控制,流量控制等操作,将部分报文进行发送,导致我们读取数据出现错误(所以在OS层面是,不能保证读取正确的)

UDP的发送方式就是用户数据报,发送的信息一定是完整的(和快递类似,只能收到一个完整的快递,不能只收到半个快递)

TCP无法确定报文完整性的,所以我们在用户层可以保证自己报文的完整性

五.网络版本计算器的实现

1.协议的定制

但是不能将结构体直接进行发送(可能会出错(兼容性问题))

我们的方案一,要是读取多了怎么办,所以我们要进行协议的定制

所以  head_lengt\n  就相当于是我们自己定制的报头

这里我们直接使用json方案进行处理

2.Jsoncpp的处理

a.序列化

b.反序列化

c.总结

要使用的时候,我们直接查就行了

3.Protocol.hpp的实现

a.结构定义

#pragma once#include <iostream>
#include <string>// _x _oper _y
class Request
{
private:int _x;int _y;char _oper;
};class Response
{
private:int _result; //结果int _code;   //出错码,0,1,2,3,4
};

b.构造的实现

#pragma once#include <iostream>
#include <string>// _x _oper _y
class Request
{
public:Request(int x,int y,char oper):_x(x),_y(y),_oper(oper){}~Request(){}
private:int _x;int _y;char _oper;
};class Response
{
public:Response(int result,int code):_result(result),_code(code){}~Response(){}
private:int _result; //结果int _code;   //出错码,0,1,2,3,4
};

c.Request的序列化和反序列化

class Request
{
public:Request(int x,int y,char oper):_x(x),_y(y),_oper(oper){}~Request(){}bool Serialize(std::string& out_string){Json::Value root;root["x"] = _x;root["y"] = _y;root["oper"] = _oper;Json::StreamWriterBuilder wb;std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());std::stringstream ss;w->write(root,&ss);out_string = ss.str();return true;}bool Deserialize(std::string& in_string){Json::Value root;Json::Reader reader;bool parsingSuccessful = reader.parse(in_string,root);if(!parsingSuccessful){std::cout << "Failed to parse JSON: " << reader.getFormatedErrorMessages() << std::endl;return false;}_x = root["x"].asInt();_y = root["y"].asInt();_oper = root["oper"].asInt();return true;}
private:int _x;int _y;char _oper;
};

序列化的本质,就是将我们对应的  内容  转化成为  字符串

反序列化的本质,就是将我们对应的  字符串  转化成为  内容

d.测试代码

#include "Protocol.hpp"int main()
{Request req(10,20,'+');std::string s;req.Serialize(s);std::cout << s << std::endl; return 0;
}

#include "Protocol.hpp"int main()
{//序列化Request req(10,20,'+');std::string s;req.Serialize(s);std::cout << s << std::endl;//反序列化req.Deserialize(s);req.Print();return 0;
}

e.Response的序列化和反序列化

class Response
{
public:Response(int result,int code):_result(result),_code(code){}~Response(){}bool Serialize(std::string& out_string){Json::Value root;root["result"] = _result;root["code"] = _code;Json::StreamWriterBuilder wb;std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());std::stringstream ss;w->write(root,&ss);out_string = ss.str();return true;}bool Deserialize(std::string& in_string){Json::Value root;Json::Reader reader;bool parsingSuccessful = reader.parse(in_string,root);if(!parsingSuccessful){std::cout << "Failed to parse JSON: " << reader.getFormatedErrorMessages() << std::endl;return false;}_result = root["result"].asInt();_code = root["code"].asInt();return true;}
private:int _result; //结果int _code;   //出错码,0,1,2,3,4
};

f.其他接口的实现

#pragma once#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>// _x _oper _y
class Request
{
public:Request(int x,int y,char oper):_x(x),_y(y),_oper(oper){}~Request(){}bool Serialize(std::string& out_string){Json::Value root;root["x"] = _x;root["y"] = _y;root["oper"] = _oper;Json::StreamWriterBuilder wb;std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());std::stringstream ss;w->write(root,&ss);out_string = ss.str();return true;}bool Deserialize(std::string& in_string){Json::Value root;Json::Reader reader;bool parsingSuccessful = reader.parse(in_string,root);if(!parsingSuccessful){std::cout << "Failed to parse JSON: " << reader.getFormatedErrorMessages() << std::endl;return false;}_x = root["x"].asInt();_y = root["y"].asInt();_oper = root["oper"].asInt();return true;}void Print(){std::cout << _x << std::endl;std::cout << _oper << std::endl;std::cout << _y << std::endl;}int X(){return _x;}int Y(){return _y;}char Oper(){return _oper;}
private:int _x;int _y;char _oper;
};class Response
{
public:Response(int result,int code):_result(result),_code(code){}~Response(){}bool Serialize(std::string& out_string){Json::Value root;root["result"] = _result;root["code"] = _code;Json::StreamWriterBuilder wb;std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());std::stringstream ss;w->write(root,&ss);out_string = ss.str();return true;}bool Deserialize(std::string& in_string){Json::Value root;Json::Reader reader;bool parsingSuccessful = reader.parse(in_string,root);if(!parsingSuccessful){std::cout << "Failed to parse JSON: " << reader.getFormatedErrorMessages() << std::endl;return false;}_result = root["result"].asInt();_code = root["code"].asInt();return true;}int Result(){return _result;}int Code(){return _code;}
private:int _result; //结果int _code;   //出错码,0,1,2,3,4
};

g.报头的添加和删除

const std::string Sep = "\r\n";//添加报头
//{json} -> len\r\n{json}\r\n
bool Encode(std::string& message)
{if(message.size() == 0){return false;}std::string package = std::to_string(message.size()) + Sep + message + Sep;message = package;return true;
}//删除报头
//len\r\n{json}\r\n -> {json}
bool Decode(std::string &package,std::string* content)
{auto pos = package.find(Sep);if(pos == std::string::npos){return false;}std::string content_length_str = package.substr(0,pos);int content_length = std::stoi(content_length_str);int full_length = content_length_str.size() + content_length + 2 * Sep.size();if(package.size() < full_length){return false;}*content = package.substr(pos + Sep.size(),content_length);//package erasepackage.erase(0,full_length);return true;
}

所以我们说read这个接口是不太完整的(没有进行Decode)

4.Server的代码实现

void HandlerRequest(int sockfd){char inbuffer[4096];std::string package;//长任务while(true){//约定: 用户发过来的是一个完整的命令stringssize_t n = recv(sockfd,inbuffer,sizeof(inbuffer)-1,0);if(n > 0){inbuffer[n] = 0;LOG(LogLevel::INFO) << inbuffer;package += inbuffer;std::string cmd_result = _handler(package);if(cmd_result.empty()){continue;}::send(sockfd,cmd_result.c_str(),cmd_result.size(),0);}else if(n == 0){// read 如果读取返回值是0,标识client退出LOG(LogLevel::INFO) << "client exit: " << sockfd;break;}else{//读取失败了break;}}::close(sockfd);}

如果处理完之后的报文为空(我们上层会做相对的处理,如果读取错误就返回空),那么我们直接重新进行读取数据

5.Calculator的代码实现

#pragma once#include <iostream>
#include "Protocol.hpp"class Calculator
{
public:Calculator(){}Response Execute(const Request& req){}~Calculator(){}
private:};

6.func()的设计

#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Calculator.hpp"
#include <memory>using namespace LogModule;Calculator gcal;//计算这里要检测是否有完整的报文
//不完整 -> 继续读
//完整   -> 提取  ->  反序列化  -> Request  -> 计算模块,进行处理
std::string Entry(std::string& package)
{//1. 完整性判断std::string message;bool res = Decode(package,&message);if(!res || message.empty()){return std::string();}//2. 反序列化,message是一个曾经被序列化的数据{json}Request req;if(!req.Deserialize(message)){return std::string();}//3.计算Response resp = gcal.Execute(req);//4.序列化std::string respstr;resp.Serialize(respstr);//5.添加长度报头字段Encode(respstr);return respstr;
}int main()
{ENABLE_CONSOLE_LOG();//TCP 只负责IOstd::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(Entry);tsvr->InitServer();tsvr->Start();return 0;
}

因为我们对应的package被处理了一个请求,我们就要删除该请求

这个package就相当于我们的生产消费模型(生产者从后面追加,消费者从前面读取)

7.func()的多请求处理

上面我们对应的Entry只能一次处理一个请求,那么我们想一次处理多个请求呢?

std::string Entry(std::string& package)
{//1. 完整性判断std::string message;std::string respstr;while(Decode(package,&message)){if(!message.empty()){break;}//2. 反序列化,message是一个曾经被序列化的数据{json}Request req;if(!req.Deserialize(message)){break;}//3.计算Response resp = gcal.Execute(req);//4.序列化std::string res;resp.Serialize(res);//5.添加长度报头字段Encode(res);//6.拼接应答respstr += res;}return respstr;
}

8.Execute()的实现

Response Execute(const Request& req){//我们拿到就是结构化数据Response resp;switch(req.Oper()){case '+':resp.SetResult(req.X() + req.Y());break;case '-':resp.SetResult(req.X() - req.Y());break;case '*':resp.SetResult(req.X() * req.Y());break;case '/':{if(req.Y() == 0){resp.SetCode(1);//1 就是除0}else{resp.SetResult(req.X() + req.Y());}   }break;case '%':{if(req.Y() == 0){resp.SetCode(2);//2 就是模0}else{resp.SetResult(req.X() % req.Y());}   }break;default:resp.SetCode(3);//3 就是没有操作break;}return resp;}

9.Parse类的封装

#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Calculator.hpp"
#include <memory>using namespace LogModule;using cal_fun = std::function<Response(const Request& req)>;
//计算这里要检测是否有完整的报文
//不完整 -> 继续读
//完整   -> 提取  ->  反序列化  -> Request  -> 计算模块,进行处理class Parse
{
public:Parse(cal_fun c):_cal(c){}std::string Entry(std::string& package){//1. 完整性判断std::string message;std::string respstr;while(Decode(package,&message)){if(!message.empty()){break;}//2. 反序列化,message是一个曾经被序列化的数据{json}Request req;if(!req.Deserialize(message)){break;}//3.计算Response resp = _cal(req);//4.序列化std::string res;resp.Serialize(res);//5.添加长度报头字段Encode(res);//6.拼接应答respstr += res;}return respstr;}
private:cal_fun _cal;
};int main()
{ENABLE_CONSOLE_LOG();//1.计算模块Calculator mycal;//2.解析模块Parse myparse([&mycal](const Request& req){return mycal.Execute(req);});//3.通信模块//TCP 只负责IOstd::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>([&myparse](std::string& package){return myparse.Entry(package);});tsvr->InitServer();tsvr->Start();return 0;
}

这样我们就以后就直接会调用myparse的Entry函数了

"makefile".PHONY:all
all:server_tcp client_tcpserver_tcp:TcpServer.ccg++ -o $@ $^ -std=c++17 -lpthread -ljsoncpp
client_tcp:TcpClient.ccg++ -o $@ $^ -std=c++17 -lpthread -ljsoncpp.PHONY:clean
clean:rm -f server_tcp client_tcp

10.客户端的实现

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "Protocol.hpp"// ./clinet server_ip server_port
int main(int argc,char* argv[])
{if(argc != 3){std::cout << "Usage:./client server_ip server_port" << std::endl;return 1;}std::string server_ip = argv[1];int server_port = std::stoi(argv[2]);int sockfd = ::socket(AF_INET,SOCK_STREAM,0);if(sockfd < 0){std::cout << "create socket error" << std::endl;return 2;}//client 不需要显示进行bind, 因为bind只在服务端使用//TCP时面向连接的struct sockaddr_in server_addr;memset(&server_addr,0,sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(server_port);server_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());//connect底层会自动进行bindint n = ::connect(sockfd, (struct sockaddr*)&server_addr,sizeof(server_addr));if(n < 0){std::cout << "connect error" << std::endl;return 3;}//echo clientstd::string message;while(true){int x,y;char oper;std::cout << "input x: ";std::cin >> x;std::cout << "input y: ";std::cin >> y;std::cout << "input oper: ";std::cin >> oper;Request req(x,y,oper);//1.序列化req.Serialize(message);//2.EncodeEncode(message);//3.发送n = send(sockfd,message.c_str(),message.size(),0);if(n > 0){char inbuffer[1024];int m = ::recv(sockfd,inbuffer,sizeof(inbuffer),0);if(m > 0){inbuffer[m] = 0;std::string package = inbuffer;std::string content;//4.读到的应答是完整的Decode(package,&content);//5.反序列化Response resp;resp.Deserialize(content);//6.结构化数据std::cout << resp.Result() << "[" << resp.Code() << "]" << std::endl;}else{break;}}else{std::cout << "write error" << std::endl;break;}}::close(sockfd);return 0;
}

11.OSI七层模型的重新理解

上三层,现在我们都做了(将这三层压缩成为了一层)

以后,下面两层,我们有对应封装好的库,我们真正编写的是上面的应用层模块

TCP/IP只实现了下四层,上面三层根据业务的需求不同,我们程序员要进行定制

六.守护进程

1.前台进程和后台进程

2.守护进程

3.守护进程的操作方式

我们守护进程打印的结果直接往这个黑洞文件里面打印

#pragma once#include <iostream>
#include <cstdlib>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>#define ROOT "/"
#define devnull "/dev/null"void Daemon(bool ischdir,bool isclose)
{//1.守护进程一般要屏蔽到特定的异常信号signal(SIGCHLD,SIG_IGN);signal(SIGPIPE,SIG_IGN);//2.成为非组长if(fork() > 0){exit(0);}//3.建立新会话setsid();//4.每一个进程都有自己的CWD,是否将当前进程的CWD更改成为 / 根目录if(ischdir){chdir(ROOT);}//5.已经变成守护进程了,不需要和用户的输入输出,错误进行关联了if(isclose){::close(0);::close(1);::close(2);}else{int fd = ::open(devnull,O_WRONLY);if(fd > 0){//各种重定向dup2(fd,0);dup2(fd,1);dup2(fd,2);close(fd);}}}

我们现在将云服务器关闭,那个程序还是在跑的,我们将客户端上传到我们对应的应用商店,供别人下载就可以让别人访问了

我们将程序进行静态编译,那么就不需要用户进行下载对应的jsoncpp的库了

.PHONY:all
all:server_tcp client_tcpserver_tcp:TcpServer.ccg++ -o $@ $^ -std=c++17 -lpthread -ljsoncpp
client_tcp:TcpClient.ccg++ -o $@ $^ -std=c++17 -lpthread -ljsoncpp -static.PHONY:clean
clean:rm -f server_tcp client_tcp

我们后续还可以更改成发送图片的(有兴趣的自己进行尝试)

http://www.dtcms.com/a/610280.html

相关文章:

  • x^3 - 3x + 1 = 0
  • Nacos 9848启动端口占用 Failed to bind to address 0.0.0.0/0.0.0.0:9848
  • 前端react 开发 图书列表分页
  • 做vip视频网站赚钱吗最近一周新闻大事件
  • 大数据安全技术实验:Hadoop环境部署
  • 【07】特征匹配算法:ORB算法深度解析与实现
  • vite7更新了哪些内容
  • Aemulo2.0门禁卡复制卡片后修改设置卡片备注名称
  • IP应用场景全图谱:你的IP属于哪一类?
  • 微网站开发 在线商城一键部署wordpress
  • Rust实战:使用Clap和Tokio构建现代CLI应用
  • 中移建设有限公司网站猎头可以做单的网站
  • PostIn V1.3.4版本发布,新增性能测试执行明细,ldap/企业微信/钉钉登录调整为社区版本功能
  • MySQL——表的约束
  • springboot对接xxl-job
  • 企业百度网站建设网络策划是做什么的
  • 网站项目开发流程有哪七步网站素材 按钮
  • Spring Boot 全局异常处理 + 参数校验进阶:让接口告别 “500 报错” 和 “脏数据”
  • Frame structure and physical resources(帧结构与物理资源)
  • 进程状态
  • 做网站ps注意事项个人备案网站可以做电商吗
  • 如何用工控做网站重庆建设安全管理网
  • Java_泛型入门
  • 华为OD机试双机位A卷 - 机器人活动区域 (Python C++ JAVA JS GO)
  • 安卓C语言编译器——高效编程工具,助力开发者提升编程效率
  • 求大神帮忙做网站网站开发收费表
  • 基于uWebSockets开源库实现一个web服务
  • 网站地图后缀WordPress分类中文404错误
  • c 网站做死循环中国建设银行总部网站
  • 力扣(LeetCode)100题:41.缺失的第一个正数