linux笔记归纳16:自定义协议与序列化
自定义协议与序列化
目录
一、应用层
1.1.应用层协议
应用层:解决实际问题,满足日常需求的网络程序
协议:一种约定,套接字的API接口以双方约定好的结构化数据读写传输
1.2.序列化和反序列化
序列化:保证报文完整性,方便网络传输
反序列化:方便上层处理
序列化与反序列化的本质:对软件进行解耦,在应用层进行扩展


客户端和服务器都认识约定好的结构化数据,所以原则上可以直接发送二进制对象
但是客户端和服务器的结构体大小必须相同,可能会因为内存对齐问题而各不相同
同时客户端和服务端的语言种类也必须相同,语言种类不同类的类型大小也不相同
而在操作系统的内部,协议全部是传递结构体对象,因为操作系统都是C语言写的
进行网络协议的通信,在应用层主要使用序列化和反序列化方案
1.3.重新理解read和write
write函数将数据从用户层拷贝到了操作系统内核中的TCP发送缓冲区中
由TCP(传输控制协议)决定什么时候发送,要发送多少,发生错误如何处理
操作系统把数据拷贝到网络中,对方网卡接收数据,将数据拷贝到接收缓冲区
由TCP(传输控制协议)决定什么时候接收,要接收多少,发生错误如何处理
read函数将数据从操作系统内核中TCP的接收缓冲区中拷贝到了用户层

计算机通信的本质:拷贝
主机间通信的本质:把发送方的发送缓冲区数据拷贝到接收方的接收缓冲区
TCP全双工的本质:有两对发送和接收缓冲区
- 发送缓冲区写满后,应用层会在write函数内阻塞
- 接收缓冲区无数据,应用层会在read函数内阻塞
本质是为了让用户与操作系统之间实现同步机制
来维护用户与操作系统之间的生产者消费者模型
UDP面向数据报:发送方发送一次报文,接收方接收到一个完整的报文
TCP面向字节流:发送方发送一次报文,接收方接收到报文的一部分(粘包问题)或者多条报文
定制协议
- 结构化字段,提供序列化和反序列化方案
- 解决字节流, 导致读取报文不完整的问题
二、Jsoncpp
2.1.Jsoncpp概念
一个用于处理JSON数据的C++库
功能:将JSON数据序列化为字符串,将字符串反序列化为C++数据结构
相似的第三方库:xml,protobuf(二进制级别的序列化与反序列化)
2.2.Jsoncpp安装
ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel

2.3.序列化
2.3.1.toStyledString
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value root;
root["name"] = "张三";
root["sex"] = "男";
root["age"] = 18;
std::string s = root.toStyledString();
std::cout << s << std::endl;
return 0;
}

2.3.2.FastWriter
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value root;
root["name"] = "张三";
root["sex"] = "男";
root["age"] = 18;
Json::FastWriter writer;
std::string s = writer.write(root);
std::cout << s << std::endl;
return 0;
}

2.3.3.StyledWriter
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value root;
root["name"] = "张三";
root["sex"] = "男";
root["age"] = 18;
// 用\n按行设置, 可读性好
Json::StyledWriter writer;
std::string s = writer.write(root);
std::cout << s << std::endl;
return 0;
}

2.3.4.StreamWriter
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
#include <sstream>
#include <memory>
int main()
{
Json::Value root;
root["name"] = "张三";
root["sex"] = "男";
root["age"] = 18;
Json::StreamWriterBuilder sbuilder;
std::unique_ptr<Json::StreamWriter> writer(sbuilder.newStreamWriter());
std::stringstream ss;
writer->write(root, &ss);
std::string s = ss.str();
std::cout << s << std::endl;
}

2.4.反序列化
2.4.1.Reader
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
std::string json_string = "{\"name\":\"张三\", \"age\":30, \"city\":\"北京\"}";
Json::Value root;
Json::Reader reader;
bool ok = reader.parse(json_string, root);
(void)ok;
std::string name = root["name"].asString();
int age = root["age"].asInt();
std::string city = root["city"].asString();
std::cout << name << std::endl;
std::cout << age<< std::endl;
std::cout << city << std::endl;
}

2.5.手写序列化和反序列化
- Protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include "Socket.hpp"
#include <jsoncpp/json/json.h>
#include <functional>
// #define SELF 1
// 等价于Makefile的-D
// 自定义网络版本的计算器
using namespace SocketModule;
const std::string ProtSep = " ";
// client → server
class Request
{
public:
Request()
{
}
Request(int x, int y, char oper)
: _x(x), _y(y), _oper(oper)
{
}
// 序列化
std::string Serialize()
{
#ifdef SELF
std::string _x_str = std::to_string(_x);
std::string _y_str = std::to_string(_y);
std::string s = _x_str + ProtSep + _oper + ProtSep + _y_str;
return s;
#else
// _x = 10 _y = 20 _oper = '+'
// "10" "20" '+': 用空格作为分隔符
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["oper"] = _oper; // _oper是char, 也是整数(ASCII码)
Json::FastWriter writer;
std::string s = writer.write(root);
return s;
#endif
}
// 反序列化
bool Deserialize(std::string &in)
{
#ifdef SELF
// "x op y"
auto left = in.find(ProtSep);
if (left == std::string::npos)
{
return false;
}
auto right = in.rfind(ProtSep);
if (right == std::string::npos)
{
return false;
}
if (left + ProtSep.size() + 1 != right)
{
return false;
}
std::string left_data = in.substr(0, left);
if (left_data.empty())
{
return false;
}
std::string right_data = in.substr(right + ProtSep.size());
if (right_data.empty())
{
return false;
}
_oper = in[right - 1];
_x = std::stoi(left_data);
_y = std::stoi(right_data);
return true;
#else
// "10" "20" '+': 用空格作为分隔符
// 10 20 '+'
Json::Value root;
Json::Reader reader;
bool ok = reader.parse(in, root);
if (ok)
{
_x = root["x"].asInt();
_y = root["y"].asInt();
_oper = root["oper"].asInt();
}
return ok;
#endif
}
~Request()
{
}
int X()
{
return _x;
}
int Y()
{
return _y;
}
char Oper()
{
return _oper;
}
private:
int _x;
int _y;
char _oper;
};
// server → client
class Response
{
public:
Response()
{
}
Response(int result, int code)
: _result(result), _code(code)
{
}
// 序列化
std::string Serialize()
{
#ifdef SELF
std::string _result_str = std::to_string(_result);
std::string _code_str = std::to_string(_code);
std::string s = _result_str + ProtSep + _code_str;
return s;
#else
Json::Value root;
root["result"] = _result;
root["code"] = _code;
Json::FastWriter writer;
return writer.write(root);
#endif
}
// 反序列化
bool Deserialize(std::string &in)
{
#ifdef SELF
// "result code"
auto mid = in.find(ProtSep);
if(mid == std::string::npos)
{
return false;
}
std::string left_data = in.substr(0, mid);
if(left_data.empty())
{
return false;
}
std::string right_data = in.substr(mid + ProtSep.size());
if(right_data.empty())
{
return false;
}
_result = std::stoi(left_data);
_code = std::stoi(right_data);
return true;
#else
Json::Value root;
Json::Reader reader;
bool ok = reader.parse(in, root);
if (ok)
{
_result = root["result"].asInt();
_code = root["code"].asInt();
}
return ok;
#endif
}
~Response()
{
}
void SetResult(int res)
{
_result = res;
}
void SetCode(int code)
{
_code = code;
}
void ShowResult()
{
std::cout << "计算结果是: " << _result << "[ " << _code << " ]" << std::endl;
}
private:
int _result; // 运算结果
int _code; // 0: success 1,2,3,4: 不同异常
};
// 基于TCP的协议要解决两个问题
// 1. request和response必须有序列化和反序列化功能(使用C++的Jsoncpp库)
// 2. 读取时, 读到完整的请求(仅TCP, UDP不用考虑)
// 因为TCP是面向字节流的, 当读取方读取的时候, 可能读到一个完整的Json请求, 也可能读到半个或一个半(粘报问题)
// read函数本身并不保证读取到的报文完整性, 只要有数据, 就进行读取
// 由应用层的程序员自己控制, 需要进一步定制协议
// 在Json字符串前面加Json字符串长度(应用层报头)
// content_len jsonstring
// 50\r\n{"x" : 10, "y" : 20, "oper" : '+'}\r\n
// 50
// {"x" : 10, "y" : 20, "oper" : '+'}
//
const std::string sep = "\r\n";
using func_t = std::function<Response(Request &req)>; // 计算业务回调
class Protocol
{
public:
Protocol()
{
}
Protocol(func_t func)
: _func(func)
{
}
// 编码
std::string Encode(const std::string jsonstr)
{
// 50\r\n{"x" : 10, "y" : 20, "oper" : '+'}\r\n
std::string len = std::to_string(jsonstr.size());
return len + sep + jsonstr + sep; // 应用层封装
}
// 解码
// 1. 判断报文完整性
// 2. 如果包含至少一个完整请求, 就进行提取, 并从缓冲区移除,便于处理下一个
bool Decode(std::string &buffer, std::string *package)
{
ssize_t pos = buffer.find(sep);
if (pos == std::string::npos)
{
return false; // 让调用方从内核继续读取数据
}
// 获取报文长度
std::string package_len_str = buffer.substr(0, pos);
int package_len_int = std::stoi(package_len_str);
// 计算一个完整报文长度
int target_len = package_len_str.size() + sep.size() + package_len_int + sep.size();
// 保证buffer里至少有一个完整报文
if (buffer.size() < target_len)
{
return false;
}
// 提取一个完整报文
*package = buffer.substr(pos + sep.size(), package_len_int);
// 将报文移除缓冲区
buffer.erase(0, target_len);
return true;
}
void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
{
// 读取请求
std::string buffer_queue;
while (true)
{
int n = sock->Recv(&buffer_queue);
if (n > 0)
{
std::cout << "--------------- request_buffer --------------" << std::endl;
std::cout << buffer_queue << std::endl;
std::cout << "---------------------------------------------" << std::endl;
// 1. 解析报文, 提取完整的Json请求, 如果不完整则继续读取
std::string json_package;
while (Decode(buffer_queue, &json_package))
{
std::cout << "--------------- request json --------------" << std::endl;
std::cout << json_package << std::endl;
std::cout << "---------------------------------------------" << std::endl;
std::cout << "--------------- request_buffer --------------" << std::endl;
std::cout << buffer_queue << std::endl;
std::cout << "---------------------------------------------" << std::endl;
// 2. 请求Json报文的反序列化
LOG(LogLevel::DEBUG) << client.StringAddr() << " 请求: " << json_package;
Request req;
bool ok = req.Deserialize(json_package);
if (!ok)
{
continue;
}
// 3. 完成计算功能(业务)
Response resp = _func(req);
// 4. 序列化
std::string json_str = resp.Serialize();
// 5. 编码, 添加自定义长度
std::string send_str = Encode(json_str);
// 6. 发送
sock->Send(send_str);
}
}
else if (n == 0)
{
LOG(LogLevel::INFO) << "client: " << client.StringAddr() << " Quit!";
break;
}
else
{
LOG(LogLevel::WARNING) << "client: " << client.StringAddr() << " , recv error";
break;
}
}
}
bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buff, Response *resp)
{
// 面向字节流
while (true)
{
int n = client->Recv(&resp_buff);
if (n > 0)
{
// std::cout << "---------------- resp_buffer ----------------" << std::endl;
// std::cout << resp_buff << std::endl;
// std::cout << "---------------------------------------------" << std::endl;
// 解析报文, 提取完整的Json报文
std::string json_package;
// bool ret = Decode(resp_buff, &json_package);
// if (!ret)
// {
// continue;
// }
while (Decode(resp_buff, &json_package))
{
// std::cout << "--------------- response json ---------------" << std::endl;
// std::cout << json_package << std::endl;
// std::cout << "---------------------------------------------" << std::endl;
// std::cout << "---------------- resp_buffer ----------------" << std::endl;
// std::cout << resp_buff << std::endl;
// std::cout << "---------------------------------------------" << std::endl;
// 反序列化
resp->Deserialize(json_package);
}
return true;
}
else if (n == 0)
{
std::cout << "server quit" << std::endl;
return false;
}
else
{
std::cout << "recv error" << std::endl;
return false;
}
}
}
std::string BuildRequestString(int x, int y, char oper)
{
// 构建一个完整的请求
Request req(x, y, oper);
// 序列化
std::string json_req = req.Serialize();
// std::cout << "-------------- json_req string --------------" << std::endl;
// std::cout << json_req << std::endl;
// std::cout << "---------------------------------------------" << std::endl;
// 编码 添加自定义长度
return Encode(json_req);
}
~Protocol()
{
}
private:
// 多进程(可以写时拷贝)
// Request _req;
// Response _resp;
func_t _func; // 计算业务回调
};
- Makefile
.PHONY:all
all: ServerNetCald client_netcal
LDFLAG=-DSELF=1
ServerNetCald:Main.cc
g++ -o $@ $^ $(LDFLAG) -std=c++17 -ljsoncpp -lpthread
client_netcal:TcpClient.cc
g++ -o $@ $^ $(LDFLAG) -std=c++17 -ljsoncpp -lpthread
.PHONY:output
output:
@mkdir output
@mkdir -p output/bin
@mkdir -p output/conf
@mkdir -p output/log
@cp ServerNetCald output/bin
@cp client_netcal output/bin
@cp test.conf output/conf
@cp install.sh output/
@cp uninstall.sh output/
@tar czf output.tgz output
.PHONY:clean
clean:
rm -rf ServerNetCald client_netcal output output.tgz
三、进程间关系与守护进程
3.1.进程组
进程组:一个或者多个进程的集合
进程是以进程组的形式完成对应任务的
单个进程执行任务独立成为一个进程组
- PGID:进程组
- TTY:终端号
- TPGID:终端与进程的关联
- UID:创建进程的用户
- TIME:时间
- COMMAND:命令

3.2.组长进程
每一个进程组都有一个组长进程,组长进程的ID等于其进程的ID
组长进程的作用:创建一个进程组或者创建该组中的进程
进程组生命周期:从进程组创建开始到最后一个进程离开
注意:某个进程组有一个进程存在,进程组就存在,与组长进程是否终止无关

3.3.会话
SID(Session ID):会话ID
会话:一个或多个进程组的集合(进程组一定属于某一个会话)
xshell登录linux系统后,linux系统会为用户新建一个会话
会话内部默认有一个进程组Bash,将该会话的ID设置为Bash进程的ID


3.4.任务
任务:由进程来完成的某种工作
进程组和任务是一个硬币的两面
键盘只有一个,输入的数据必须明确地给一个进程组
在会话内部,进程组必须区分成为前台和后台进程组
一次会话中,只能有一个前台进程组,后台可以多个
一次会话中,只能有一个前台任务,后台可以有多个
所以键盘输入的数据,只是给前台进程组
3.5.控制终端
控制终端:终端文件
用户通过终端登录系统后得到的一个Shell(Bash)进程

3.6.作业控制
作业:用户为了完成某项任务而启动的进程组
作业号:作业的编号
- (+):该作业号是默认作业
- (-):该作业号即将成为默认作业
- (无符号):其他作业

作业控制:Shell同时运行一个前台作业和任意多个后台作业
- jobs:查看系统当前的后台任务
- fg(front ground)任务号: 把指定任务提到前台
- bg(back ground) 任务号:运行后台任务
- ctrl + c:只能终止前台任务,后台任务不受键盘输入影响
- ctrl + z:暂停前台进程组,自动被切换到后台,Bash重新回到前台



3.7.守护进程
用户登录是建立会话的过程,关闭终端是销毁会话的过程
用户的登录和注销都会影响到服务器的运行,导致不稳定
需要为服务器新建一个会话,将服务器进程组迁移新会话

3.8.setsid函数
作业:创建一个会话
前提:调用的进程不能是一个进程组的组长
创建子进程,终止父进程,子进程继承父进程的进程组ID,守护进程也是孤儿进程的一种

3.9.null文件
凡是写入该文件的内容都丢弃
凡是该文件读取的内容都是空

3.10.daemon函数
作用:守护进程化
参数1:0 - 将当前进程的工作目录改为根目录
参数2:0 - 关闭标准输出,标准输入,标准错误

四、网络版计算器
2.1.socket封装
设计模式:模板方法类
- Socket.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
namespace SocketModule
{
using namespace LogModule;
const static int gbacklog = 16;
// 设计模式: 模板方法模式
// 基类socket
// 大部分方法为纯虚方法
class Socket
{
public:
virtual ~Socket() {} // 析构函数
virtual void SocketOrDie() = 0; // 创建套接字
virtual void BindOrDie(uint16_t port) = 0; // 绑定IP和端口号
virtual void ListenOrDie(int backlog) = 0; // 监听
virtual std::shared_ptr<Socket> Accept(InetAddr *client) = 0; // 获取
virtual void Close() = 0; // 关闭
virtual int Recv(std::string *out) = 0; // 读取
virtual int Send(const std::string &message) = 0; // 写入
virtual int Connect(const std::string &server_ip, uint16_t port) = 0; // 连接
public:
void BuildListenSocketMethod(uint16_t port, int backlog = gbacklog)
{
SocketOrDie();
BindOrDie(port);
ListenOrDie(backlog);
}
void BuildTcpClientSocketMethod()
{
SocketOrDie();
}
};
const static int defaultfd = -1;
class TcpSocket : public Socket
{
public:
TcpSocket()
: _sockfd(defaultfd)
{
}
TcpSocket(int fd)
: _sockfd(fd)
{
}
void SocketOrDie() override
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success";
}
void BindOrDie(uint16_t port) override
{
InetAddr localaddr(port);
int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success";
}
void ListenOrDie(int backlog) override
{
int n = ::listen(_sockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success";
}
std::shared_ptr<Socket> Accept(InetAddr *client) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int fd = ::accept(_sockfd, CONV(peer), &len);
if (fd < 0)
{
LOG(LogLevel::WARNING) << "accept warning...";
return nullptr;
}
client->SetAddr(peer);
// return fd;
return std::make_shared<TcpSocket>(fd);
}
void Close() override
{
if (_sockfd >= 0)
{
::close(_sockfd);
}
}
int Recv(std::string *out) override
{
// 流式读取(不用关心读到的是什么)
char buffer[1024];
ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
*out += buffer;
}
return n;
}
int Send(const std::string &message) override
{
return ::send(_sockfd, message.c_str(), message.size(), 0);
}
int Connect(const std::string &server_ip, uint16_t port) override
{
InetAddr server(server_ip, port);
return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen());
}
~TcpSocket()
{
}
private:
int _sockfd;
};
};
2.2.自定义协议
- Protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include "Socket.hpp"
#include <jsoncpp/json/json.h>
#include <functional>
// 自定义网络版本的计算器
using namespace SocketModule;
// client → server
class Request
{
public:
Request()
{
}
Request(int x, int y, char oper)
: _x(x), _y(y), _oper(oper)
{
}
// 序列化
std::string Serialize()
{
// _x = 10 _y = 20 _oper = '+'
// "10" "20" '+': 用空格作为分隔符
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["oper"] = _oper; // _oper是char, 也是整数(ASCII码)
Json::FastWriter writer;
std::string s = writer.write(root);
return s;
}
// 反序列化
bool Deserialize(std::string &in)
{
// "10" "20" '+': 用空格作为分隔符
// 10 20 '+'
Json::Value root;
Json::Reader reader;
bool ok = reader.parse(in, root);
if (ok)
{
_x = root["x"].asInt();
_y = root["y"].asInt();
_oper = root["oper"].asInt();
}
return ok;
}
~Request()
{
}
int X()
{
return _x;
}
int Y()
{
return _y;
}
char Oper()
{
return _oper;
}
private:
int _x;
int _y;
char _oper;
};
// server → client
class Response
{
public:
Response()
{
}
Response(int result, int code)
: _result(result), _code(code)
{
}
// 序列化
std::string Serialize()
{
Json::Value root;
root["result"] = _result;
root["code"] = _code;
Json::FastWriter writer;
return writer.write(root);
}
// 反序列化
bool Deserialize(std::string &in)
{
Json::Value root;
Json::Reader reader;
bool ok = reader.parse(in, root);
if (ok)
{
_result = root["result"].asInt();
_code = root["code"].asInt();
}
return ok;
}
~Response()
{
}
void SetResult(int res)
{
_result = res;
}
void SetCode(int code)
{
_code = code;
}
void ShowResult()
{
std::cout << "计算结果是: " << _result << "[ " << _code << " ]" << std::endl;
}
private:
int _result; // 运算结果
int _code; // 0: success 1,2,3,4: 不同异常
};
// 基于TCP的协议要解决两个问题
// 1. request和response必须有序列化和反序列化功能(使用C++的Jsoncpp库)
// 2. 读取时, 读到完整的请求(仅TCP, UDP不用考虑)
// 因为TCP是面向字节流的, 当读取方读取的时候, 可能读到一个完整的Json请求, 也可能读到半个或一个半(粘报问题)
// read函数本身并不保证读取到的报文完整性, 只要有数据, 就进行读取
// 由应用层的程序员自己控制, 需要进一步定制协议
// 在Json字符串前面加Json字符串长度(应用层报头)
// content_len jsonstring
// 50\r\n{"x" : 10, "y" : 20, "oper" : '+'}\r\n
// 50
// {"x" : 10, "y" : 20, "oper" : '+'}
//
const std::string sep = "\r\n";
using func_t = std::function<Response(Request &req)>; // 计算业务回调
class Protocol
{
public:
Protocol()
{
}
Protocol(func_t func)
: _func(func)
{
}
// 编码
std::string Encode(const std::string jsonstr)
{
// 50\r\n{"x" : 10, "y" : 20, "oper" : '+'}\r\n
std::string len = std::to_string(jsonstr.size());
return len + sep + jsonstr + sep; // 应用层封装
}
// 解码
// 1. 判断报文完整性
// 2. 如果包含至少一个完整请求, 就进行提取, 并从缓冲区移除,便于处理下一个
bool Decode(std::string &buffer, std::string *package)
{
ssize_t pos = buffer.find(sep);
if (pos == std::string::npos)
{
return false; // 让调用方从内核继续读取数据
}
// 获取报文长度
std::string package_len_str = buffer.substr(0, pos);
int package_len_int = std::stoi(package_len_str);
// 计算一个完整报文长度
int target_len = package_len_str.size() + sep.size() + package_len_int + sep.size();
// 保证buffer里至少有一个完整报文
if (buffer.size() < target_len)
{
return false;
}
// 提取一个完整报文
*package = buffer.substr(pos + sep.size(), package_len_int);
// 将报文移除缓冲区
buffer.erase(0, target_len);
return true;
}
void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
{
// 读取请求
std::string buffer_queue;
while (true)
{
int n = sock->Recv(&buffer_queue);
if (n > 0)
{
std::cout << "--------------- request_buffer --------------" << std::endl;
std::cout << buffer_queue << std::endl;
std::cout << "---------------------------------------------" << std::endl;
// 1. 解析报文, 提取完整的Json请求, 如果不完整则继续读取
std::string json_package;
while (Decode(buffer_queue, &json_package))
{
std::cout << "--------------- request json --------------" << std::endl;
std::cout << json_package << std::endl;
std::cout << "---------------------------------------------" << std::endl;
std::cout << "--------------- request_buffer --------------" << std::endl;
std::cout << buffer_queue << std::endl;
std::cout << "---------------------------------------------" << std::endl;
// 2. 请求Json报文的反序列化
LOG(LogLevel::DEBUG) << client.StringAddr() << " 请求: " << json_package;
Request req;
bool ok = req.Deserialize(json_package);
if (!ok)
{
continue;
}
// 3. 完成计算功能(业务)
Response resp = _func(req);
// 4. 序列化
std::string json_str = resp.Serialize();
// 5. 编码, 添加自定义长度
std::string send_str = Encode(json_str);
// 6. 发送
sock->Send(send_str);
}
}
else if (n == 0)
{
LOG(LogLevel::INFO) << "client: " << client.StringAddr() << " Quit!";
break;
}
else
{
LOG(LogLevel::WARNING) << "client: " << client.StringAddr() << " , recv error";
break;
}
}
}
bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buff, Response *resp)
{
// 面向字节流
while (true)
{
int n = client->Recv(&resp_buff);
if (n > 0)
{
// std::cout << "---------------- resp_buffer ----------------" << std::endl;
// std::cout << resp_buff << std::endl;
// std::cout << "---------------------------------------------" << std::endl;
// 解析报文, 提取完整的Json报文
std::string json_package;
// bool ret = Decode(resp_buff, &json_package);
// if (!ret)
// {
// continue;
// }
while (Decode(resp_buff, &json_package))
{
// std::cout << "--------------- response json ---------------" << std::endl;
// std::cout << json_package << std::endl;
// std::cout << "---------------------------------------------" << std::endl;
// std::cout << "---------------- resp_buffer ----------------" << std::endl;
// std::cout << resp_buff << std::endl;
// std::cout << "---------------------------------------------" << std::endl;
// 反序列化
resp->Deserialize(json_package);
}
return true;
}
else if (n == 0)
{
std::cout << "server quit" << std::endl;
return false;
}
else
{
std::cout << "recv error" << std::endl;
return false;
}
}
}
std::string BuildRequestString(int x, int y, char oper)
{
// 构建一个完整的请求
Request req(x, y, oper);
// 序列化
std::string json_req = req.Serialize();
// std::cout << "-------------- json_req string --------------" << std::endl;
// std::cout << json_req << std::endl;
// std::cout << "---------------------------------------------" << std::endl;
// 编码 添加自定义长度
return Encode(json_req);
}
~Protocol()
{
}
private:
// 多进程(可以写时拷贝)
// Request _req;
// Response _resp;
func_t _func; // 计算业务回调
};
2.3.设计服务器
- TcpServer.hpp
#include "Socket.hpp"
#include <iostream>
#include <memory>
#include <sys/wait.h>
#include <functional>
using namespace SocketModule;
using namespace LogModule;
// IO服务
using ioservice_t = std::function<void(std::shared_ptr<Socket> &sock, InetAddr &client)>;
// TCP服务器: 实现连接, 不需要关心传递的数据
// 网络版本的计算器: 长服务
class TcpServer
{
public:
TcpServer(uint16_t port, ioservice_t service)
: _port(port), _listensockptr(std::make_unique<TcpSocket>()), _isrunning(false), _service(service)
{
_listensockptr->BuildListenSocketMethod(_port);
}
void Start()
{
_isrunning = true;
while (_isrunning)
{
InetAddr client;
auto sock = _listensockptr->Accept(&client);
if (sock == nullptr)
{
continue;
}
LOG(LogLevel::DEBUG) << "accept success..." << client.StringAddr();
// 多进程实现
pid_t id = fork();
if (id < 0)
{
LOG(LogLevel::FATAL) << "fork error...";
exit(FORK_ERR);
}
else if (id == 0)
{
// 子进程
// 关闭listensockfd
_listensockptr->Close();
if (fork() > 0)
{
exit(0);
}
// 孙子进程
_service(sock, client); // 执行IO服务
sock->Close();
exit(OK);
}
else
{
// 父进程
// 关闭sockfd
sock->Close();
pid_t rid = ::waitpid(id, nullptr, 0);
(void)rid;
}
}
_isrunning = false;
}
~TcpServer()
{
}
private:
uint16_t _port;
std::unique_ptr<Socket> _listensockptr;
bool _isrunning;
ioservice_t _service;
};
2.4.设计客户端
- TcpClient.cc
#include "Socket.hpp"
#include "Common.hpp"
#include "Protocol.hpp"
#include <iostream>
#include <string>
#include <memory>
using namespace SocketModule;
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " server_ip server_port" << std::endl;
}
// 获取数据
void GetDataFromStdin(int *x, int *y, char *oper)
{
std::cout << "Please Enter x: ";
std::cin >> *x;
std::cout << "Please Enter y: ";
std::cin >> *y;
std::cout << "Please Enter oper: ";
std::cin >> *oper;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
std::shared_ptr<Socket> client = std::make_shared<TcpSocket>();
client->BuildTcpClientSocketMethod();
if (client->Connect(server_ip, server_port) != 0)
{
// 连接失败
std::cerr << "connect error" << std::endl;
exit(CONNECT_ERR);
}
std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>();
std::string resp_buffer;
// 服务器连接成功
while (true)
{
// 获取数据
int x, y;
char oper;
GetDataFromStdin(&x, &y, &oper);
// 构建请求
std::string req_str = protocol->BuildRequestString(x, y, oper);
// std::cout << "--------------encode req string--------------" << std::endl;
// std::cout << req_str << std::endl;
// std::cout << "---------------------------------------------" << std::endl;
// 发送请求
client->Send(req_str);
// 获取应答
Response resp;
bool res = protocol->GetResponse(client, resp_buffer, &resp);
if (res == false)
{
break;
}
// 显示结果
resp.ShowResult();
}
client->Close();
return 0;
}
2.5.设计计算器
- NetCal.hpp
#pragma once
#include "Protocol.hpp"
#include <iostream>
class Cal
{
public:
Response Execute(Request &req)
{
Response resp(0, 0);
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;
}
};
2.6.设计主函数
- Main.cc
#include "NetCal.hpp"
#include "Protocol.hpp"
#include "TcpServer.hpp"
#include "Daemon.hpp"
#include <memory>
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " port" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
std::cout << "服务器已经启动, 已守护进程化" << std::endl;
// 守护进程化
Daemon(0, 0);
// daemon(0, 0);
// 采用文件方式显示日志
Enbale_File_Log_Strategy();
// 受用户的需求影响, OSI的上三层无法设计到内核中
// 只能由程序员进行编写
// 业务层(应用层)
std::unique_ptr<Cal> cal = std::make_unique<Cal>();
// 协议层(表示层)
std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>(
[&cal](Request &req)
->Response
{
return cal->Execute(req);
});
// 网络通信层(会话层)
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(std::stoi(argv[1]),
[&protocol](std::shared_ptr<Socket> &sock, InetAddr &client)
{
protocol->GetRequest(sock, client);
});
tsvr->Start();
return 0;
}
2.7.守护进程化
- Daemon.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"
#include "Common.hpp"
using namespace LogModule;
const std::string dev = "/dev/null";
// 将服务器守护进程化
void Daemon(int nochdir, int noclose)
{
// 忽略IO, 子进程退出等相关信号
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
// 销毁父进程 创建子进程
if (fork() > 0)
{
exit(0);
}
// 孤儿进程
setsid(); // 成为独立的会话
if(nochdir == 0)
{
chdir("/");
}
// 守护进程不从键盘输入, 也不需向显示器打印
// 方法一: 关闭与stdin、stdout、stderr(不推荐)
// 方法二: 打开/dev/null, 重定向stdin、stdout、stderr到/dev/null
if (noclose == 0)
{
int fd = ::open(dev.c_str(), O_RDWR);
if (fd < 0)
{
LOG(LogLevel::FATAL) << "open " << dev << " error";
exit(OPEN_ERR);
}
else
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}
}
2.8.日志
- Log.hpp
#ifndef __LOG_HPP__
#define __LOG_HPP__
#include <iostream>
#include <string>
#include <filesystem> // C++17中 文件操作的相关封装
#include <fstream>
#include "Mutex.hpp"
#include <memory>
#include <unistd.h>
#include <sstream>
#include <ctime>
#include <cstdio>
namespace LogModule
{
using namespace MutexModule;
const std::string gsep = "\r\n";
// 2. 刷新策略(策略模式: C++多态)
// 策略基类
class LogStrategy
{
public:
~LogStrategy() = default;
virtual void SyncLog(const std::string &message) = 0;
};
// 策略a: 显示器打印
class ConsoleLogStrategy : public LogStrategy
{
public:
ConsoleLogStrategy()
{
}
void SyncLog(const std::string &message) override
{
// 加锁
LockGuard lockguard(_mutex);
// 打印日志
std::cout << message << gsep;
}
~ConsoleLogStrategy()
{
}
private:
Mutex _mutex;
};
// 缺省参数
const std::string defaultpath = "/var/log/";
const std::string defaultfile = "my.log";
// 策略b: 指定文件写入
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
: _path(path), _file(file)
{
// 加锁
LockGuard lockguard(_mutex);
// 如果当前路径存在
if (std::filesystem::exists(_path))
{
return;
}
// 如果当前路径不存在
try
{
std::filesystem::create_directories(_path);
}
catch (const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << "\n";
}
}
void SyncLog(const std::string &message) override
{
// 加锁
LockGuard lockguard(_mutex);
// "./log" + "/" + "my.log"
std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
// 以追加的方式打开文件
std::ofstream out(filename, std::ios::app);
if (!out.is_open())
{
return;
}
// 写入日志
out << message << gsep;
// 关闭文件
out.close();
}
~FileLogStrategy()
{
}
private:
std::string _path; // 日志文件所在路径
std::string _file; // 日志文件名称
Mutex _mutex; // 互斥锁
};
// 形成完整日志 && 根据策略选择不同刷新方式
// 1. 形成日志等级
enum class LogLevel
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
std::string LeveltoStr(LogLevel level)
{
switch (level)
{
case LogLevel::DEBUG:
return "DEBUG";
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
default:
return "UNKNOW";
}
}
// 2. 获取时间方法
std::string GetTimeStamp()
{
time_t curr = time(nullptr);
struct tm curr_tm;
localtime_r(&curr, &curr_tm);
char timebuffer[128];
snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d-%02d-%02d",
curr_tm.tm_year + 1900, curr_tm.tm_mon + 1, curr_tm.tm_mday,
curr_tm.tm_hour, curr_tm.tm_min, curr_tm.tm_sec);
return timebuffer;
}
// 日志类
class Logger
{
public:
Logger()
{
// 默认使用显示器
EnableConsoleLogStrategy();
}
void EnableFileLogStrategy()
{
_fflush_strategy = std::make_unique<FileLogStrategy>();
}
void EnableConsoleLogStrategy()
{
_fflush_strategy = std::make_unique<ConsoleLogStrategy>();
}
// 内部类: 表示未来的一条日志
class LogMessage
{
public:
LogMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger)
: _curr_time(GetTimeStamp()), _level(level), _pid(getpid()), _src_name(src_name), _line_number(line_number), _logger(logger)
{
// 日志左半部分
std::stringstream ss;
ss << "[" << _curr_time << "] "
<< "[" << LeveltoStr(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _src_name << "] "
<< "[" << _line_number << "] "
<< "- ";
_loginfo = ss.str();
}
template <typename T>
LogMessage &operator<<(const T &info)
{
// 日志右半部分
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if (_logger._fflush_strategy)
{
_logger._fflush_strategy->SyncLog(_loginfo);
}
}
private:
std::string _curr_time; // 时间
LogLevel _level; // 等级
pid_t _pid; // 进程PID
std::string _src_name; // 文件名
int _line_number; // 行号
std::string _loginfo; // 一条完整的日志信息
Logger &_logger;
};
LogMessage operator()(LogLevel level, std::string name, int line)
{
return LogMessage(level, name, line, *this);
}
~Logger()
{
}
private:
std::unique_ptr<LogStrategy> _fflush_strategy;
};
// 全局日志对象
Logger logger;
// 使用宏简化用户操作, 获取文件名和行号
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enbale_File_Log_Strategy() logger.EnableFileLogStrategy()
};
#endif
2.9.网络地址转换
- InetAddr.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"
// 网络地址 <=> 主机地址
class InetAddr
{
public:
InetAddr()
{
}
InetAddr(struct sockaddr_in &addr)
{
SetAddr(addr);
}
InetAddr(const std::string &ip, uint16_t port)
: _ip(ip), _port(port)
{
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
// 主机序列 → 网络序列(端口号)
_addr.sin_port = htons(_port);
// 点分十进制 → 网络序列(IP地址)
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
}
InetAddr(uint16_t port)
: _port(port), _ip("0")
{
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_port = htons(_port);
_addr.sin_addr.s_addr = INADDR_ANY;
}
void SetAddr(struct sockaddr_in &addr)
{
_addr = addr;
// 网络序列 → 主机序列(端口号)
_port = ntohs(_addr.sin_port);
// 网络序列 → 点分十进制(IP地址)
char ipbuffer[64];
inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));
_ip = ipbuffer;
}
uint16_t Port()
{
return _port;
}
std::string Ip()
{
return _ip;
}
const struct sockaddr_in &NetAddr()
{
return _addr;
}
const struct sockaddr *NetAddrPtr()
{
return CONV(_addr);
}
socklen_t NetAddrLen()
{
return sizeof(_addr);
}
bool operator==(const InetAddr &addr)
{
return addr._ip == _ip && addr._port == _port;
}
std::string StringAddr()
{
return _ip + " : " + std::to_string(_port);
}
~InetAddr()
{
}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
2.10.共有类
- Common.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
enum ExitCode
{
OK = 0,
USAGE_ERR,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR,
FORK_ERR,
OPEN_ERR,
};
// 禁止拷贝
class NoCopy
{
public:
NoCopy()
{
}
~NoCopy()
{
}
NoCopy(const NoCopy &) = delete;
const NoCopy &operator=(const NoCopy &) = delete;
};
#define CONV(addr) ((struct sockaddr *)&addr)
2.11.互斥锁
- Mutex.hpp
#pragma once
#include <iostream>
#include <pthread.h>
namespace MutexModule
{
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_mutex, nullptr);
}
void Lock()
{
int n = pthread_mutex_lock(&_mutex);
(void)n;
}
void unLock()
{
int n = pthread_mutex_unlock(&_mutex);
(void)n;
}
~Mutex()
{
pthread_mutex_destroy(&_mutex);
}
pthread_mutex_t *Get()
{
return &_mutex;
}
private:
pthread_mutex_t _mutex;
};
class LockGuard
{
public:
LockGuard(Mutex &mutex)
:_mutex(mutex)
{
_mutex.Lock();
}
~LockGuard()
{
_mutex.unLock();
}
private:
Mutex &_mutex;
};
};
2.12.Makefile
.PHONY:all
all: ServerNetCald client_netcal
ServerNetCald:Main.cc
g++ -o $@ $^ -std=c++17 -ljsoncpp -lpthread -static
client_netcal:TcpClient.cc
g++ -o $@ $^ -std=c++17 -ljsoncpp -lpthread -static
.PHONY:output
output:
@mkdir output
@mkdir -p output/bin
@mkdir -p output/conf
@mkdir -p output/log
@cp ServerNetCald output/bin
@cp client_netcal output/bin
@cp test.conf output/conf
@cp install.sh output/
@cp uninstall.sh output/
@tar czf output.tgz output
.PHONY:clean
clean:
rm -rf ServerNetCald client_netcal output output.tgz
2.13.安装卸载脚本
- install.sh
#!/usr/bin/bash
cp -f ./bin/ServerNetCald /usr/bin
cp -f ./bin/client_netcal /usr/bin
- uninstall.sh
#!/usr/bin/bash
rm -rf /usr/bin/ServerNetCald
rm -rf /usr/bin/client_netcal
更多推荐



所有评论(0)