基于tcp实现自定义应用层协议

认识协议 

协议(Protocol) 是一种通信规则或标准,用于定义通信双方或多方之间如何交互和传输数据。在计算机网络和通信系统中,协议规定了通信实体之间信息交换的格式、顺序、定时以及有关同步等事宜的约定。简易来说协议就是通信双方所约定好的结构化字段。

 ​​​​​

 序列化与反序列化的认识

当结构化字段在网络进行传输的过程中可能会由于平台和语言的差异性导致发送方和接收方对于同一个结构化字段的读取方式不同(如内存存储差异、数据格式差异)所以在针对结构化数据进行网络收发时会先将其序列化成字节流然后再进行发送到发送缓冲区中进行发送。然后接收端在接收缓冲区中收到数据以后再将字节流格式的数据反序列化成结构化字段数据,从而实现数据准确的接受与发送。其实序列化同时也间接的提高了数据的传输效率,因为序列化后的字段通常是压缩成更加紧凑的字节流形式,从而减少了数据量,进而提高传输效率。

自定义协议实现网络版本计算器 

在自定义协议时必须让客户端与服务器都能够看到同一份协议字段,这样才能保证在发送接收数据的时候按照规定的格式进行准确无误的发收。

自定义协议

对于我们实现的协议不仅仅有序列化结构体字段,其实还有报头的封装,因为tcp是有连接的,数据是流式传输的,所以传输过程并不是每次发送时将想要发送的数据完整的发送到对端的接收端缓冲区。其实准确来说,应用层在对数据进行序列化打包以后其实是将数据发送到TCP缓冲区内部的,然后在TCP缓冲区内部进行数据的流量控制等处理以便能够可靠传输,再然后就是将部分数据打包好再向下递交。所以接收方将数据接收在对应的TCP接受缓冲区时并不能保证将完整的信息全部读取到内存缓冲区中,所以此时就需要解决粘包问题(实际就是将数据进行解析拆分组装),也就是通过报头的数据长度字段来获取完整的一次数据请求。一般可以采用报头中的定长字段或特殊标识字符结尾的方式来将每一次的完整数据进行分离从而解决粘包问题。

以下是采用包头中的定长字段进行解决数据粘包的问题:

#pragma once
#include <iostream>
#include <string>
using namespace std;

// 模拟定协议

const string sepa = " ";
const string line_break = "\n";

// 添加报头数据和解报头
void Encode(string &mes)
{
    int len = mes.size();
    string ret = to_string(len) + line_break + mes + line_break;
    mes = ret;
}

bool Decode(string &package, string &ret)
{
    // 格式:"len\n数据信息\n"
    // 先判断收到的数据中是否可以提炼出一次完整的数据请求
    int len = package.size();
    int pos = package.find(line_break, 0); // 指向第一个换行符
    if (pos == string::npos)
        return false;

    int data_size = stoi(package.substr(0, pos));
    int sz = pos + line_break.size() + data_size + line_break.size();
    if (sz > len)
        return false;

    // 解包后的数据
    ret = package.substr(pos + line_break.size(), data_size);
    // 去掉被读走的数据
    package = package.substr(sz);

    return true;
}

class Request
{
    friend class Cal;

public:
    Request() {}
    Request(int x, int y, char op)
        : _x(x), _y(y), _oper(op)
    {
    }

    void info()
    {
        cout << _x << ' ' << _oper << ' ' << _y << " = ?" << endl;
    }

    void Serialize(string &out) // 序列化
    {
        out = to_string(_x) + sepa + _oper + sepa + to_string(_y);
    }

    //"x + y"
    void Deserialize(const string s) // 反序列化
    {

        int begin = 0;
        int end = s.find(sepa, begin);
        _x = stoi(s.substr(begin, end - begin));
        begin = end + sepa.size(); // 加的1其实就是' '的长度

        end = s.find(sepa, begin);
        _oper = s.substr(begin, end - begin)[0];
        begin = end + sepa.size();

        _y = stoi(s.substr(begin));
    }

private:
    int _x;
    int _y;
    char _oper;
};

class Response
{
public:
    Response()
    {
    }
    Response(int re, string ret_info)
        : _result(re), _ret_info(ret_info)
    {
    }
    void Info()
    {
        cout << "result = "
<think>嗯,用户现在想了解如何设计和实现自定义应用层协议,之前他们问的是Java游戏模组开发,现在转向了网络协议设计,这可能是因为他们在开发模组时需要处理网络通信的问题。需要结合他们提供的三个引用内容,这些引用提到了自定义协议的必要性、序列化、TCP实现以及XML格式的例子。首先,我得明确用户的需求。他们可能是在开发游戏模组时需要让客户端和服务器通信,比如同步玩家位置、物品信息等,所以需要自定义协议。引用里提到了网络版计算器的例子,使用序列化和反序列化,还有JSON和XML的结构,这些可以作为参考。 接下来,我得整理设计自定义协议的步骤。根据引用内容,关键点包括协议结构设计(报文格式、字段定义)、序列化方法选择、传输层协议选择(如TCP)、错误处理等。需要分步骤讲解,比如确定需求、设计报文、选择序列化方式、实现编解码、测试等。 然后要考虑如何将引用中的例子融入进去。比如网络计算器使用JSON的例子,可以说明序列化的具体实现。还有XML格式的请求,展示不同格式的应用。可能需要比较不同序列化方式的优缺点,帮助用户选择合适的方法。用户可能遇到的问题包括如何处理粘包、数据校验、协议升级等,这些需要涵盖在实现注意事项里。另外,性能优化方面,比如压缩数据,也要提到。最后,生成相关问题,引导用户深入思考协议设计中的具体问题,比如如何选择序列化格式、处理粘包、设计校验机制等。需要确保问题与用户的实际开发场景相关,帮助他们解决可能遇到的挑战。</think>### 自定义应用层协议设计与实现指南 基于TCP实现自定义协议需要明确定义**报文结构**、**序列化方式**和**通信流程**。以下是关键步骤与示例: --- #### 一、协议设计核心要素 1. **报文格式** - **定界符**:使用特殊字符(如`<requset>`/`</requset>`)或长度前缀标识报文边界[^3] - **字段定义**:结构化数据(如用户ID、坐标位置),支持嵌套或键值对形式[^3] - **示例XML格式**: ```xml <request> <userId>1234</userId> <position>100 80</position> </request> ``` 2. **序列化与反序列化** - **二进制协议**:直接编码整数/浮点数(性能高但可读性差) - **文本协议**:JSON/XML(易调试,但传输效率较低)[^1] - **示例JSON序列化**: ```json { "userId": 1234, "position": [100, 80] } ``` 3. **传输层选择** - **TCP协议**:保证可靠传输,需处理粘包问题(通过长度字段或分隔符)[^2] - **UDP协议**:低延迟但需自行处理丢包重传 --- #### 二、实现步骤(以网络计算器为例) 1. **定义协议规范** - 请求格式:`{"op": "+", "a": 5, "b": 3}` - 响应格式:`{"result": 8}` 2. **实现序列化工具** ```java // 使用Jackson库实现JSON序列化 ObjectMapper mapper = new ObjectMapper(); String request = mapper.writeValueAsString(new CalculatorRequest("+", 5, 3)); byte[] data = request.getBytes(StandardCharsets.UTF_8); ``` 3. **封装Socket通信** ```python # TCP服务端接收逻辑(处理粘包) def recv_all(sock, length): data = b'' while len(data) < length: packet = sock.recv(length - len(data)) if not packet: return None data += packet return data ``` 4. **协议解析与处理** ```java // 反序列化并执行计算 CalculatorRequest req = mapper.readValue(inputStream, CalculatorRequest.class); double result = calculate(req.op, req.a, req.b); String response = mapper.writeValueAsString(new CalculatorResponse(result)); ``` --- #### 三、实现注意事项 1. **粘包处理** - 方案1:固定长度头部(如4字节表示后续数据长度) - 方案2:使用分隔符(如`\r\n\r\n`) 2. **数据校验** - 添加校验字段(如CRC32或MD5哈希) - 示例:`{"data": ..., "checksum": "a1b2c3"}` 3. **协议升级** - 版本号字段:`{"version": 2, ...}` - 向后兼容性设计 --- #### 四、性能优化技巧 1. **二进制协议优化** - 使用Protobuf/FlatBuffers替代JSON - 压缩算法(如gzip) 2. **TCP参数调优** - 设置合理的`SO_SNDBUF`和`SO_RCVBUF` - 启用Nagle算法(需权衡延迟与吞吐量) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CR0712

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值