一.概念回顾
建议先学上篇博客,再向下学习,上篇博客的链接如下:
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. return
return std::string();
}
};
但是有一个接口完成了上面的基本功能







//给你一个命令字符串"ls -l",让你执行,执行完,把结果进行返回
std::string Execute(std::string cmdstr)
{
//1. pipe
//2. fork + dup2(pipe[1],1) + exec*,把执行结果交给父进程(pipe[0])
//3. return
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;
}
三.将可执行命令设置进入白名单(防止危险命令)
#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. return
if(!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
就相当于是我们自己定制的报头

这里我们直接使用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 = "
";
//添加报头
//{json} -> len
{json}
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
{json}
-> {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 erase
package.erase(0,full_length);
return true;
}


所以我们说read这个接口是不太完整的(没有进行Decode)
4.Server的代码实现
void HandlerRequest(int sockfd)
{
char inbuffer[4096];
std::string package;
//长任务
while(true)
{
//约定: 用户发过来的是一个完整的命令string
ssize_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 只负责IO
std::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 只负责IO
std::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_tcp
server_tcp:TcpServer.cc
g++ -o $@ $^ -std=c++17 -lpthread -ljsoncpp
client_tcp:TcpClient.cc
g++ -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底层会自动进行bind
int n = ::connect(sockfd, (struct sockaddr*)&server_addr,sizeof(server_addr));
if(n < 0)
{
std::cout << "connect error" << std::endl;
return 3;
}
//echo client
std::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.Encode
Encode(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_tcp
server_tcp:TcpServer.cc
g++ -o $@ $^ -std=c++17 -lpthread -ljsoncpp
client_tcp:TcpClient.cc
g++ -o $@ $^ -std=c++17 -lpthread -ljsoncpp -static
.PHONY:clean
clean:
rm -f server_tcp client_tcp
我们后续还可以更改成发送图片的(有兴趣的自己进行尝试)





