Linux网络:应用层自定义协议与序列化
我们以一个例子来描述这个事情——网络计算器我们需要实现一个服务器版的加法器。我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。约定方案一客户端发送一个形如"1+2"的字符串;这个字符串中有两个操作数, 都是整型;两个数字之间会有一个字符是运算符, 运算符只能是 +;数字和运算符之间没有空格;......约定方案二:定义结构体来表示我们需要交互的信息;
前面我们已经学习过利用UDP和TCP进行网络编程。本期我们就深入底层,学习网络的序列化相关的内容,并编写相关的代码。
本期相关代码已经上传至作者的个人gitee中楼田莉子/Linux学习,喜欢请点个赞谢谢。
目录
应用层
应用层就是我们日常进行的各种行为的we并不是指某个具体的应用程序(如浏览器、游戏客户端),而是指为应用程序提供网络通信服务的协议和接口集合。它负责定义应用程序如何利用底层网络交换有意义的数据。
应用层的核心使命是:将传输层提供的原始字节流(或数据报)转化为应用程序能够理解的业务语义。
为什么read、write、recv、send等TCP socket支持全双工?
1、因为通信双方都有接受和发送的缓冲区
2、用户拷贝数据到内核的发送缓冲区,用户拷贝数据到内核对应的发送缓冲区,用户拷贝接受缓冲区中的数据到用户空间,本质可以看做,CP问题。

但是这样就会导致几个问题:我们发出去的数据对方有接收吗?答案是不确定的。那么我们读到的数据一定是对方发来的吗?也有可能只是一部分信息。这就是“粘包”问题
那么我们该怎么做呢?需要自定义协议
用户层自定义协议
我们以一个例子来描述这个事情——网络计算器
我们需要实现一个服务器版的加法器。我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。
约定方案一:
客户端发送一个形如"1+2"的字符串;
这个字符串中有两个操作数, 都是整型;
两个数字之间会有一个字符是运算符, 运算符只能是 + ;
数字和运算符之间没有空格;
......
约定方案二:
定义结构体来表示我们需要交互的信息;
发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
这个过程叫做 "序列化" 和 "反序列化"
我们先说方案二。这个可行但是不推荐的,主要原因有以下几点:
1、客户端和服务器端可能在不同系统平台上(事实上通常如此)。不同系统对于内存对齐、大小端问题的处理不同。就是兼容性问题
2、可拓展性很差。不同的客户端语言可能不同,如果用结构体语言兼容性低下
应用层协议不推荐传递结构体。我们就需要一个新的东西用户自定义协议
那么为什么内核用结构体呢?它们统一为C语言编写的
序列化
序列化(Serialization) 是指将内存中的数据结构或对象状态,转换为一种可存储或可传输的格式(通常为字节序列)的过程。其逆过程称为反序列化(Deserialization),即从字节流中重建原始数据结构或对象。
为什么需要这种转换?
程序运行时,数据以复杂的形态存在于内存中:
-
指针/引用指向堆中的其他区域;
-
虚函数表指针(vptr)指向类型信息;
-
不同平台对基础类型(如 int、long)的字节长度、字节序(大端/小端)可能有差异;
-
编译器可能会在结构体成员之间插入内存对齐填充字节。
为什么网路编程必须序列化?
网络本质上是一个字节流传输管道,它只关心数据的顺序和完整性,而不关心数据的语义。要在这样的通道上交换结构化数据,就必须解决以下问题:
1、不同OS、不同语言、不同编译器下对内存的理解差异
2、处理复杂的对象数据关系
3、序列化不仅用于网络,也用于将对象保存到文件或数据库。这与网络传输本质相同:都是将内存对象转换为可脱离进程存在的字节流。
推荐的序列化工具
序列化通常是由现有工具完成的,不是很推荐自己写。常见的序列化工具主要有json、protobuf、xml。
为了方便理解,我们后续使用Jsoncpp这个库来实现
接下来我们来测试代码
#include <jsoncpp/json/json.h>
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
// 1. 基本类型操作:创建、赋值、类型判断、取值
void TestValueBasics()
{
std::cout << "=== TestValueBasics ===" << std::endl;
Json::Value v;
// 赋值不同类型
v["bool"] = true;
v["int"] = 42;
v["double"] = 3.14159;
v["string"] = "hello";
v["null"] = Json::nullValue;
// 类型判断
std::cout << "bool isBool: " << v["bool"].isBool() << std::endl;
std::cout << "int isInt: " << v["int"].isInt() << std::endl;
std::cout << "double isDouble: " << v["double"].isDouble() << std::endl;
std::cout << "string isString: " << v["string"].isString() << std::endl;
std::cout << "null isNull: " << v["null"].isNull() << std::endl;
// 取值
std::cout << "bool value: " << v["bool"].asBool() << std::endl;
std::cout << "int value: " << v["int"].asInt() << std::endl;
std::cout << "double value: " << v["double"].asDouble() << std::endl;
std::cout << "string value: " << v["string"].asString() << std::endl;
std::cout << std::endl;
}
// 2. 数组操作
void TestValueArray()
{
std::cout << "=== TestValueArray ===" << std::endl;
Json::Value arr(Json::arrayValue); // 显式声明为数组
// 追加元素
arr.append(10);
arr.append(20);
arr.append(30);
// 索引访问
std::cout << "arr[0] = " << arr[0].asInt() << std::endl;
std::cout << "arr[1] = " << arr[1].asInt() << std::endl;
// 遍历
std::cout << "Array elements: ";
for (const auto& elem : arr) {
std::cout << elem.asInt() << " ";
}
std::cout << std::endl;
// 大小和删除
std::cout << "Size before remove: " << arr.size() << std::endl;
Json::Value removed;
arr.removeIndex(1, &removed); // 删除索引1的元素,存储到removed
std::cout << "Removed element: " << removed.asInt() << std::endl;
std::cout << "Size after remove: " << arr.size() << std::endl;
std::cout << std::endl;
}
// 3. 对象操作
void TestValueObject()
{
std::cout << "=== TestValueObject ===" << std::endl;
Json::Value obj(Json::objectValue);
// 添加键值对
obj["name"] = "加法器";
obj["version"] = 1;
obj["support_ssl"] = false;
// 判断键是否存在
std::cout << "Has 'name'? " << obj.isMember("name") << std::endl;
std::cout << "Has 'port'? " << obj.isMember("port") << std::endl;
// 获取所有键名
auto members = obj.getMemberNames();
std::cout << "Keys: ";
for (const auto& key : members) {
std::cout << key << " ";
}
std::cout << std::endl;
// 移除键
obj.removeMember("version");
std::cout << "After remove, has 'version'? " << obj.isMember("version") << std::endl;
std::cout << std::endl;
}
// 4. 安全的取值方式:使用get()避免隐式创建
void TestValueGetOrDefault()
{
std::cout << "=== TestValueGetOrDefault ===" << std::endl;
Json::Value obj;
obj["x"] = 100;
// 安全获取已存在的键
int x = obj.get("x", 0).asInt();
std::cout << "x = " << x << std::endl;
// 获取不存在的键,返回默认值,且不会在obj中创建该键
int y = obj.get("y", -1).asInt();
std::cout << "y (default) = " << y << std::endl;
std::cout << "obj has 'y'? " << obj.isMember("y") << std::endl; // 仍然为false
// 注意:直接使用obj["y"]会创建null值
auto& ref = obj["z"]; // 这会创建一个键"z",值为null
std::cout << "After obj[\"z\"], has 'z'? " << obj.isMember("z") << std::endl;
std::cout << std::endl;
}
// 5. 序列化:Json::Value -> 字符串
void TestSerialization()
{
std::cout << "=== TestSerialization ===" << std::endl;
Json::Value root;
root["a"] = 1;
root["b"] = 2.5;
root["c"] = "text";
// 使用StreamWriterBuilder生成紧凑格式(无缩进)
Json::StreamWriterBuilder builder;
builder.settings_["indentation"] = ""; // 紧凑
std::string compact = Json::writeString(builder, root);
std::cout << "Compact: " << compact << std::endl;
// 生成美观格式(带缩进)
builder.settings_["indentation"] = " "; // 两个空格缩进
std::string styled = Json::writeString(builder, root);
std::cout << "Styled:\n" << styled << std::endl;
// 使用传统方法(toStyledString)
std::cout << "toStyledString:\n" << root.toStyledString() << std::endl;
std::cout << std::endl;
}
// 6. 反序列化:字符串 -> Json::Value
void TestDeserialization()
{
std::cout << "=== TestDeserialization ===" << std::endl;
std::string jsonStr = R"({"x":10,"y":20,"op":"+"})";
Json::CharReaderBuilder builder;
Json::Value root;
std::string errs;
// 解析
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
bool ok = reader->parse(jsonStr.c_str(),
jsonStr.c_str() + jsonStr.size(),
&root,
&errs);
if (!ok) {
std::cerr << "Parse error: " << errs << std::endl;
return;
}
// 输出解析结果
std::cout << "Parsed: x=" << root["x"].asInt()
<< ", y=" << root["y"].asInt()
<< ", op=" << root["op"].asString() << std::endl;
// 演示从流解析(使用parseFromStream)
std::istringstream iss(R"({"result":30})");
Json::Value resultRoot;
if (Json::parseFromStream(builder, iss, &resultRoot, &errs)) {
std::cout << "Result: " << resultRoot["result"].asInt() << std::endl;
}
std::cout << std::endl;
}
// 7. 配置CharReaderBuilder:严格模式与注释支持
void TestCharReaderConfig()
{
std::cout << "=== TestCharReaderConfig ===" << std::endl;
std::string jsonWithComment = R"({
// this is a comment
"value": 42
})";
Json::CharReaderBuilder builderNormal;
Json::CharReaderBuilder builderStrict;
// 开启严格模式(不允许注释、尾部逗号等)
builderStrict.settings_["strictMode"] = true;
Json::Value root;
std::string errs;
// 普通模式可以解析带注释的JSON
std::unique_ptr<Json::CharReader> readerNormal(builderNormal.newCharReader());
bool okNormal = readerNormal->parse(jsonWithComment.c_str(),
jsonWithComment.c_str() + jsonWithComment.size(),
&root, &errs);
std::cout << "Normal mode parse success: " << okNormal << std::endl;
if (okNormal) {
std::cout << " value = " << root["value"].asInt() << std::endl;
}
// 严格模式应该失败
std::unique_ptr<Json::CharReader> readerStrict(builderStrict.newCharReader());
errs.clear();
bool okStrict = readerStrict->parse(jsonWithComment.c_str(),
jsonWithComment.c_str() + jsonWithComment.size(),
&root, &errs);
std::cout << "Strict mode parse success: " << okStrict << std::endl;
if (!okStrict) {
std::cout << " error: " << errs << std::endl;
}
std::cout << std::endl;
}
// 8. 迭代器遍历(统一遍历数组或对象)
void TestValueIterators()
{
std::cout << "=== TestValueIterators ===" << std::endl;
// 对象
Json::Value obj;
obj["name"] = "adder";
obj["port"] = 8080;
std::cout << "Iterating object:" << std::endl;
for (auto it = obj.begin(); it != obj.end(); ++it) {
// 对于对象,it.key() 返回键名,*it 返回值
std::cout << " key = " << it.key().asString()
<< ", value = " << (*it).asString() << std::endl;
}
// 数组
Json::Value arr(Json::arrayValue);
arr.append(1.1);
arr.append(2.2);
arr.append(3.3);
std::cout << "Iterating array:" << std::endl;
for (const auto& elem : arr) { // 基于范围的for循环
std::cout << " " << elem.asDouble() << std::endl;
}
std::cout << std::endl;
}
// 9. 拷贝与移动语义(Json::Value支持值语义)
void TestValueCopyMove()
{
std::cout << "=== TestValueCopyMove ===" << std::endl;
Json::Value original;
original["data"] = "important";
// 拷贝构造(深拷贝)
Json::Value copy(original);
copy["data"] = "modified";
std::cout << "original: " << original["data"].asString() << std::endl;
std::cout << "copy: " << copy["data"].asString() << std::endl;
// 移动构造
Json::Value moved(std::move(original));
std::cout << "moved: " << moved["data"].asString() << std::endl;
// original 现在处于有效但未指定状态,通常应不再使用
std::cout << "original isNull? " << original.isNull() << std::endl;
std::cout << std::endl;
}
int main()
{
TestValueBasics();
TestValueArray();
TestValueObject();
TestValueGetOrDefault();
TestSerialization();
TestDeserialization();
TestCharReaderConfig();
TestValueIterators();
TestValueCopyMove();
return 0;
}
结果为:

接下来我们来手动写序列化代码
日志
#pragma once
#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string>
#include <memory>
#include <sstream>
#include <chrono>
#include <iomanip>
#include <ctime>
#include <unistd.h>
#include <filesystem> // C++17
#include <fstream>
//RAII风格代码:资源获取即初始化
// 互斥锁类,封装了pthread_mutex_t
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_lock, nullptr);
}
void Lock()
{
pthread_mutex_lock(&_lock);
}
pthread_mutex_t *Ptr()
{
return &_lock;
}
void Unlock()
{
pthread_mutex_unlock(&_lock);
}
~Mutex()
{
pthread_mutex_destroy(&_lock);
}
private:
pthread_mutex_t _lock;
};
class LockGuard // RAII风格代码
{
public:
LockGuard(Mutex &lock):_lockref(lock)
{
_lockref.Lock();
}
~LockGuard()
{
_lockref.Unlock();
}
private:
Mutex &_lockref;
};
//日志本体
namespace Logger
{
enum class LogLevel
{
INFO, ///< 信息性消息,记录应用程序的正常运行状态(如启动、配置加载等)
WARNING, ///< 警告,表示潜在的问题或非预期的情形,但应用程序仍能继续运行
ERROR, ///< 错误,表示发生了严重的操作失败,但不影响整个应用程序的继续运行
FATAL, ///< 致命错误,表示严重的故障,通常会导致应用程序终止
DEBUG ///< 调试信息,用于开发和排错阶段,记录详细的内部状态或流程
};
std::string LogLevelToString(LogLevel level)
{
switch (level)
{
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
case LogLevel::DEBUG:
return "DEBUG";
default:
return "UNKNOWN";
}
}
std::string GetCurrentDateTime()
{
auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::tm tm_info; // 用户提供的缓冲区
localtime_r(&now_c, &tm_info); // POSIX 线程安全函数
std::ostringstream oss;
oss << std::put_time(&tm_info, "%Y-%m-%d %H:%M:%S");
return oss.str();
}
// 输出角度 -- 刷新策略
// 1. 显示器打印
// 2. 文件写入
// 日志的生成:
// 1. 构建日志字符串
// 2. 根据不同的策略,进行刷新
//策略模式接口
class LogStrategy
{
public:
virtual void LogRefresh(const std::string &message) = 0;
virtual ~LogStrategy() = default;
};
// 控制台日志刷新策略, 日志将来要向显示器打印
class ConsoleStrategy : public LogStrategy
{
public:
// 显示器打印策略刷新
void LogRefresh(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::cerr << message << std::endl; // ??
}
~ConsoleStrategy()
{
}
private:
Mutex _mutex;
};
const std::string defaultpath = "./log";
const std::string defaultfilename = "log.txt";
namespace fs = std::filesystem;
// 文件策略
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultpath, const std::string &name = defaultfilename)
: _logpath(path),
_logfilename(name)
{
if (std::filesystem::exists(_logpath))
return;
try
{
std::filesystem::create_directories(_logpath);
}
catch(const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << '\n';
}
if(!_logpath.empty()&&_logpath.back() != '/')
_logpath += '/';
}
// 文件策略刷新
void LogRefresh(const std::string &message) override
{
{
std::string targetlogfile = _logpath + _logfilename;
LockGuard lockguard(_mutex);
std::ofstream logFile(targetlogfile, std::ios::app);// 以追加模式打开文件
if (!logFile.is_open())
{
std::cerr << "无法打开日志文件: " << targetlogfile << std::endl;
return;
}
logFile << message << "\n";
logFile.close();
}
}
~FileLogStrategy()
{
}
private:
std::string _logpath;
std::string _logfilename;
Mutex _mutex;
};
// 根据日志等级分类保存的策略类
class LevelFileLogStrategy : public LogStrategy {
public:
/**
* @brief 构造函数,指定日志根目录
* @param log_dir 存放等级日志文件的目录,默认为 "./logs"
*/
LevelFileLogStrategy(const std::string &log_dir = "./logs") : _log_dir(log_dir) {
// 确保目录存在
if (!std::filesystem::exists(_log_dir)) {
std::filesystem::create_directories(_log_dir);
}
// 规范化目录路径,末尾添加 '/'
if (!_log_dir.empty() && _log_dir.back() != '/') {
_log_dir += '/';
}
}
/**
* @brief 刷新日志:根据等级写入对应文件
* @param message 完整的日志消息(格式由 Logger::LogMessage 生成)
*/
void LogRefresh(const std::string &message) override {
// 1. 从消息中提取日志等级
LogLevel level = extractLogLevel(message);
// 2. 构造对应的文件名(例如:INFO.log)
std::string filename = _log_dir + LogLevelToString(level) + ".log";
// 3. 线程安全地追加写入文件
LockGuard lockguard(_mutex);
std::ofstream logFile(filename, std::ios::app);
if (!logFile.is_open()) {
std::cerr << "无法打开日志文件: " << filename << std::endl;
return;
}
logFile << message << "\n";
logFile.close();
}
private:
std::string _log_dir; // 日志根目录
Mutex _mutex; // 文件写入互斥锁
/**
* @brief 从日志消息中解析出等级
* @param msg 完整日志消息,格式为 "[时间][等级][PID][文件:行号] 用户内容"
* @return 对应的 LogLevel 枚举值,解析失败时默认返回 INFO
*/
LogLevel extractLogLevel(const std::string &msg) {
// 寻找第一个 ']' 的位置
size_t first_close = msg.find(']');
if (first_close == std::string::npos) {
return LogLevel::INFO; // 格式错误,默认 INFO
}
// 寻找第二个 '[' 的位置
size_t second_open = msg.find('[', first_close);
if (second_open == std::string::npos) {
return LogLevel::INFO;
}
// 寻找第二个 ']' 的位置
size_t second_close = msg.find(']', second_open);
if (second_close == std::string::npos) {
return LogLevel::INFO;
}
// 提取等级字符串(如 "INFO")
std::string level_str = msg.substr(second_open + 1, second_close - second_open - 1);
// 映射到 LogLevel 枚举
if (level_str == "INFO") return LogLevel::INFO;
if (level_str == "WARNING") return LogLevel::WARNING;
if (level_str == "ERROR") return LogLevel::ERROR;
if (level_str == "FATAL") return LogLevel::FATAL;
if (level_str == "DEBUG") return LogLevel::DEBUG;
return LogLevel::INFO; // 未知等级,默认 INFO
}
};
class Logger
{
public:
Logger()
{}
void UseConsoleStrategy()
{
_strategy = std::make_unique<ConsoleStrategy>();
}
void UseFileStrategy()
{
_strategy = std::make_unique<FileLogStrategy>();
}
void UseLevelFileStrategy(const std::string &log_dir = "./logs")
{
_strategy = std::make_unique<LevelFileLogStrategy>(log_dir);
}
void Debug(const std::string &message)
{
if (_strategy!= nullptr)
{
_strategy->LogRefresh("[DEBUG] " + GetCurrentDateTime() + " - " + message);
}
}
//日志内容
//一条完整的日志信息=> [日志级别] + 当前时间 + 进程ID + 文件名 + 行号 + 日志信息
//我们想以RAII形式刷新日志信息
class LogMessage
{
public:
LogMessage(LogLevel level, const std::string &filename, size_t line, Logger &logger)
: _level(level), _filename(filename), _line(line),_logger(logger)
{
_cur_time = GetCurrentDateTime();
_pid=getpid();
// 构建日志左半部分信息
std::stringstream oss;
oss <<"["<<_cur_time<<"]"
<<"["<<LogLevelToString(_level)<<"]"
<<"["<<_pid<<"]"
<<"[" << filename << ":"
<< line << "]";
_LogInfo = oss.str();
}
template<typename T>
LogMessage& operator<<(const T&info)
{
std::stringstream oss;
oss<< info;
_LogInfo += oss.str();
return *this;
}
~LogMessage()
{
if (_logger._strategy != nullptr)
{
_logger._strategy->LogRefresh(_LogInfo);
}
}
private:
std::string _cur_time;
LogLevel _level;
pid_t _pid;
std::string _filename;
size_t _line;
std::string _LogInfo;
Logger &_logger ;//方便进行后续策略方式刷新
};
//对LogMessage进行()重载
//必须用拷贝,否则会导致<<重载的时候内容消失
LogMessage operator()(LogLevel level, const std::string filename, size_t line)
{
return LogMessage(level, filename, line, *this);
}
~Logger()
{}
private:
std::unique_ptr<LogStrategy> _strategy;
};
//日志对象全局使用
Logger logger;
#define ENABLE_LOG_CONSOLE() logger.UseConsoleStrategy()
#define ENABLE_LOG_FILE() logger.UseFileStrategy()
#define Log(level) logger(level, __FILE__, __LINE__)
#define ENABLE_LOG_LEVEL_FILE(log_dir) logger.UseLevelFileStrategy(log_dir)
}
计算器
Calculater.hpp
#pragma once
#include "Protocol.hpp"
#include <iostream>
#include <string>
class Calculater
{
public:
Response Execute(const Request &req)
{
Response resp;
if (req.oper_.empty()) {
resp.flag_ = 3; // 非法操作
return resp;
}
char oper_char = req.oper_[0]; // 获取字符串的第一个字符
switch (oper_char)
{
case '+':
resp.result_ = req.xdata_ + req.ydata_;
break;
case '-':
resp.result_ = req.xdata_ - req.ydata_;
break;
case '*':
resp.result_ = req.xdata_ * req.ydata_;
break;
case '/':
{
if (req.ydata_ == 0)
resp.flag_ = 1; // div error
else
resp.result_ = req.xdata_ / req.ydata_;
}
break;
case '%':
{
if (req.ydata_ == 0)
resp.flag_ = 2; // mod error
else
resp.result_ = req.xdata_ % req.ydata_;
}
break;
default:
resp.flag_ = 3; // 非法操作
break;
}
return resp;
}
};
客户端
#pragma once
#include<iostream>
#include<string>
#include<cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define CONV(address) ((struct sockaddr*)address)
//网络客户端封装
class InetAddr
{
private:
std::string IP_;
uint16_t port_;
struct sockaddr_in address_;
socklen_t len_;
public:
InetAddr()=default;
InetAddr(const struct sockaddr_in& address) : address_(address),len_(sizeof(address))
{
IP_ = inet_ntoa(address_.sin_addr); // 将二进制IP转换为字符串
port_ = ntohs(address_.sin_port); // 端口转换正确
}
InetAddr(uint16_t port,const std::string ip="0.0.0.0"):IP_(ip),port_(port)
{
bzero(&address_, sizeof(address_));
address_.sin_family = AF_INET;
address_.sin_addr.s_addr = inet_addr(IP_.c_str());
address_.sin_port = htons(port_);
len_=sizeof(address_);
}
struct sockaddr *GetNetAddress()
{
return CONV(&address_);
}
~InetAddr()=default;
socklen_t len()
{
return len_;
}
bool operator==(const InetAddr&addrs)
{
return (this-> IP_==addrs.IP_)&&(this->port_==addrs.port_);
}
bool operator=(struct sockaddr_in &addrs)
{
char ipstr[32];
inet_ntop(AF_INET, &(addrs.sin_addr), ipstr, sizeof(ipstr));
IP_ = ipstr;
port_ = ntohs(addrs.sin_port);
}
std::string ToString()
{
return "[" + IP_ + ":" + std::to_string(port_) + "]";
}
};
原型
自定义套接字
socket.hpp
#pragma once
#include "InetAddr.hpp"
#include "Log.hpp"
#include<iostream>
#include<cstdlib>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
namespace CustomProtocol
{
using namespace Logger;
enum
{
SUCCESS = 0,
SOCKET_ERROR = -1,
BIND_ERR=-2,
LISTEN_ERR=-3,
};
class Socket
{
private:
public:
virtual void CreateSocketOrDie() = 0;
virtual void BindSocketOrDie(uint16_t port) = 0;
virtual void ListenSocketOrDie() = 0;
// virtual ssize_t Reve() = 0;
// virtual void send() = 0;
// virtual void Close() = 0;
// virtual bool Connect(InetAddr &addr) = 0;
virtual std::shared_ptr<Socket> Accept(InetAddr &addr) = 0;
public:
Socket()=default;
~Socket()=default;
void BuildTcpSocket(uint16_t port)
{
CreateSocketOrDie();
BindSocketOrDie(port);
ListenSocketOrDie();
}
};
class TcpSocket : public Socket
{
private:
int sockfd_;
public:
TcpSocket()
:sockfd_(0)
{}
TcpSocket(int sockfd)
:sockfd_(sockfd)
{}
~TcpSocket()
{
close(sockfd_);
}
void CreateSocketOrDie() override
{
sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd_ < 0)
{
Log(LogLevel::FATAL)<<"socket create error";
exit(SOCKET_ERROR);
}
}
void BindSocketOrDie(uint16_t port) override
{
InetAddr addr(port);
if (bind(sockfd_, (struct sockaddr*)addr.GetNetAddress(), addr.len()) != 0)
{
Log(LogLevel::FATAL) << "bind socket error";
exit(BIND_ERR);
}
}
void ListenSocketOrDie() override
{
if (listen(sockfd_, 5) != 0)
{
Log(LogLevel::FATAL) << "listen socket error";
exit(LISTEN_ERR);
}
}
std::shared_ptr<Socket> Accept(InetAddr &addr) override
{
struct sockaddr_in clientaddr;
socklen_t len=sizeof(clientaddr);
int connfd = accept(sockfd_, CONV(&clientaddr), &len);
if(connfd<0)
{
Log(LogLevel::FATAL)<<"accept socket error";
return nullptr;
}
addr=clientaddr;
return std::make_shared<TcpSocket>(connfd);
}
};
class UdpSocket : public Socket
{
};
}
自定义TCP服务器
TcpServer.hpp
#include"Socket.hpp"
#include "InetAddr.hpp"
#include "Log.hpp"
#include<memory>
static const int cport=8080;
using namespace CustomProtocol;
class TcpServer
{
private:
uint16_t port_;
std::unique_ptr<Socket> listsock_;
public:
TcpServer(uint16_t port=cport)
:port_(port)
{
listsock_=std::make_unique<TcpSocket>();
listsock_->BuildTcpSocket(port_);
}
~TcpServer()
{}
void Loop()
{
while(1)
{
InetAddr clientaddr;
auto sockfd=listsock_->Accept(clientaddr);
if(!sockfd)
continue;
Log(LogLevel::DEBUG)<<"accept one new link ,socketfd: "<<clientaddr.ToString();
sleep(1);
}
}
};
多进程改版
自定义协议
#pragma once
//自定义协议
#include "Log.hpp"
#include <jsoncpp/json/json.h>
#include <iostream>
#include <functional>
#include <string>
using namespace Logger;
///常见的序列化与反序列化工具:json、protobuf、xml
//请求报文
class Request
{
public:
Request()
:xdata_(0),ydata_(0),oper_("")
{
}
Request(int xdata,int ydata,char oper)
:xdata_(xdata),ydata_(ydata),oper_(std::string(1, oper))
{
}
~Request()=default;
//序列化
bool Serialize(std::string *OutMessage)
{
//格式为:xdata_ oper_ ydata_
//结构体->xdata_ oper_ ydata_
Json::Value root;
root["xdata_"] = xdata_;
root["ydata_"] = ydata_;
root["oper_"] = oper_;
Json::FastWriter fastWriter;
*OutMessage = fastWriter.write(root);
return true;
}
//反序列化
bool Deserialize(std::string &InMessage)
{
//xdata_ oper_ ydata_->结构体
//结构体->xdata_ oper_ ydata_
Json::Value root;
Json::Reader reader;
bool parsesuccess = reader.parse(InMessage, root);
if(!parsesuccess)
return false;
xdata_ = root["xdata_"].asInt();
ydata_ = root["ydata_"].asInt();
oper_ = root["oper_"].asString();
return true;
}
public:
//格式为:xdata_ oper_ ydata_
int xdata_;//前
int ydata_;//后
std::string oper_;//运算符
};
//回应报文
class Response
{
public:
Response()=default;
Response(int result,int flag)
:result_(result),flag_(flag)
{}
~Response()
{}
//序列化
bool Serialize(std::string *OutMessage)
{
//格式为:xdata_ oper_ ydata_
//结构体->xdata_ oper_ ydata_
Json::Value root;
root["result_"] = result_;
root["flag_"] = flag_;
Json::FastWriter fastWriter;
*OutMessage = fastWriter.write(root);
return true;
}
//反序列化
bool Deserialize(std::string &InMessage)
{
//xdata_ oper_ ydata_->结构体
//结构体->xdata_ oper_ ydata_
Json::Value root;
Json::Reader reader;
bool parsesuccess = reader.parse(InMessage, root);
if(!parsesuccess)
return false;
result_ = root["result_"].asInt();
flag_ = root["flag_"].asInt();
return true;
}
public:
int result_;//结果
int flag_;//状态码
};
const std::string cap = "\r\n";
using HandlerRequest_t = std::function<Response(Request &)>;
using HandlerResponse_t = std::function<void (Response &)>;
//自定义协议
class Protocol
{
public:
Protocol(HandlerRequest_t handler_request)
: version_("1.0"), handler_request_(handler_request)
{
}
Protocol(HandlerResponse_t handler_response)
:version_("1.0"), handler_response_(handler_response)
{
}
// {"left": 10, "right": 20, oper: '+'}
// len\r\n{"left": 10, "right": 20, oper: '+'}\r\n
std::string Pack(const std::string& JsonString)
{
return std::to_string(JsonString.size()) + cap + JsonString + cap;
}
// len\r\n{"left": 10, "right": 20, oper: '+'}\r\n
// len\r\n{"left": 10, "right": 20, oper: '+'}\r\nlen\r\n{"left": 10, "right": 20, oper: '+'}\r\n
// len\r\n{"left": 10, "right": 20, oper: '+'}\r\nlen\r\n{"left": 10,
// len\r\n{"left": 10, "right": 20, o
// le
// ret > 0: no error, json_string != NULL
// ret == 0: no error, json_string == NULL
// ret < 0 : error.
int Unpack(std::string& Packet,std::string* JsonString)
{
if(Packet.empty())
return 0;
if(JsonString==nullptr)
return -1;
// 分析报文
auto pos = Packet.find(cap);
if (pos == std::string::npos)
return 0;
std::string lenstr = Packet.substr(0, pos);
// lenstr 合法性判断,lenstr -> 123 345
int len = std::stoi(lenstr);
size_t total = lenstr.size() + len + 2 * cap.size();
if (Packet.size() < total)
return 0;
// 提取报文
*JsonString = Packet.substr(pos + cap.size(), len);
Packet.erase(0, total);
return 1;
}
// 如果读到半个报文,什么都不做
// 如果读到一个报文+,循环处理,把所有合法的报文都进行统一处理
std::string ParseRequest(std::string &inbuffer)
{
std::string result;
while (true)
{
std::string json_string;
// 1. 解包
int n = Unpack(inbuffer, &json_string);
if (n < 0)
{
Log(LogLevel::DEBUG) << "no way !!";
return std::string();
}
if (n == 0)
{
Log(LogLevel::INFO) << inbuffer << " parse done";
return result;
}
Log(LogLevel::DEBUG) << "json_string:\n" << json_string;
Log(LogLevel::DEBUG) << "unpack done, inbuffer:\n" << inbuffer;
// 2. 反序列化
// 得到一个完整的报文jsonstring
Request req;
if (!req.Deserialize(json_string))
return std::string();
// 3. 业务计算
Response resp;
if (handler_request_)
resp = handler_request_(req);
// 4. 应答序列化
std::string resp_json_string;
resp.Serialize(&resp_json_string);
// 5. 添加报头
result += Pack(resp_json_string);
}
}
std::string ParseResponse(std::string &inbuffer)
{
while (true)
{
std::string json_string;
// 1. 解包
int n = Unpack(inbuffer, &json_string);
if (n < 0)
{
Log(LogLevel::DEBUG) << "no way !!";
return std::string();
}
if (n == 0)
{
Log(LogLevel::INFO) << inbuffer << " parse done";
return std::string();
}
// 2. 反序列化
// 得到一个完整的报文jsonstring
Response resp;
if (!resp.Deserialize(json_string))
return std::string();
// 3. 回调处理
if (handler_response_)
handler_response_(resp);
}
}
~Protocol()=default;
private:
std::string version_;//版本号
HandlerRequest_t handler_request_;
HandlerResponse_t handler_response_;
};
服务器运行
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Calculater.hpp"
#include <memory>
static void Usage(const std::string &proc)
{
std::cout << "Usage:\n\t" << proc << " port" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(1);
}
uint16_t port = std::stoi(argv[1]);
// 1. 定义计算机
std::unique_ptr<Calculater> cal = std::make_unique<Calculater>();
// 2. 定义协议对象
std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>(
[&cal](Request &req)->Response{
return cal->Execute(req);
}
);
// 3. 定义网络对象
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(
[&protocol](std::string &inbuffer)->std::string{
return protocol->ParseRequest(inbuffer);
}, port
);
// 4. 启动
tsvr->Loop();
return 0;
}
客户端运行
#include <iostream>
#include <string>
#include <memory>
#include "InetAddr.hpp"
#include "Socket.hpp"
#include "Protocol.hpp"
using namespace CustomProtocol;
static void Usage(const std::string &proc)
{
std::cout << "Usage:\n\t" << proc << " server_ip server_port" << std::endl;
}
static void HanlderResponse(Response &resp)
{
std::cout << "result: " << resp.result_ << "[" << resp.flag_ << "]" << std::endl;
}
// ./netcal_client 目标IP 目标主机端口
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
std::unique_ptr<Socket> socket = std::make_unique<TcpSocket>();
socket->CreateSocketOrDie(); // 只创建套接字,不绑定和监听
InetAddr serveraddress(server_port, server_ip);
bool n = socket->Connect(serveraddress);
if (!n)
{
std::cerr << "connect error: " << serveraddress.ToString() << std::endl;
exit(2);
}
Protocol procotol(HanlderResponse);
std::string inbuffer;
while (true)
{
int cnt = 3;
std::string outbuffer;
while (cnt--)
{
// 0. 获取数据
int x, y;
char oper;
std::cout << "Enter Your x: ";
std::cin >> x;
std::cout << "Enter Your y: ";
std::cin >> y;
std::cout << "Enter Your oper: ";
std::cin >> oper;
// 1. 定义请求对象
Request req(x, y, oper);
// 2. 序列化
std::string req_json;
req.Serialize(&req_json);
// 3. 封装报头
std::string send_req_string = procotol.Pack(req_json);
outbuffer += send_req_string;
}
std::cout << "\n" << outbuffer << std::endl;
// 4. 发送
socket->Send(outbuffer);
// 5. 接收
socket->Recv(&inbuffer);
// 6. 解析
procotol.ParseResponse(inbuffer);
}
socket->Close();
// sock
return 0;
}
测试
测试结果为:

后续我还会补充多线程版本,并且结合线程池。敬请期待!
封面图自取:

更多推荐



所有评论(0)