Linux之socket编程(七)----序列化与反序列化(1)

协议

再谈 "协议" 协议是一种 "约定". socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们 要传输一些 "结构化的数据" 怎么办呢?

协议就是双方约定好的结构化的数据

重新理解tcp为什么支持全双 工

write,read里面到底再做什么吗??

我们之前在 C 语言中学习过 readwrite 等系统调用,它们本质上是对文件描述符的读写操作。为了提高数据传输效率,系统在发送和接收数据时都会引入相应的缓冲区机制。因此,当进程通过 write 发送数据时,数据并不是直接立刻发送到另一台主机,而是先写入发送缓冲区;当进程通过 read 接收数据时,也不是直接从网络中读取数据,而是从接收缓冲区中读取数据。

在主机之间进行数据读写的过程中,也存在一些类似 "管道" 的问题。例如,如果一方发送数据的速度较快,而另一方接收数据的速度较慢,接收方的缓冲区可能会被填满,此时发送方继续发送数据就会受到阻塞。反之,如果接收方读取数据的速度较快,而发送方发送数据的速度较慢,接收方也可能因为缓冲区中没有数据而进入等待状态。

这种现象类似于现实生活中的生产者 --- 消费者模型:发送方相当于生产者,接收方相当于消费者,而缓冲区则相当于一个临时存放数据的队列。当生产者生产速度过快时,队列会被填满;当消费者消费速度过快时,队列又可能为空。网络通信中的缓冲区机制,本质上就是在协调发送方和接收方之间的数据速度差异。

  • 对于任意一台主机上的 TCP 连接,内核会同时维护一套发送缓冲区和接收缓冲区。依靠这两套独立缓冲区,TCP 能够做到一边发送数据、一边接收数据,该特性称为全双工
  • 正是因为存在两套独立缓冲区,同一个 TCPsockfd文件描述符,既能调用read/recv读取对端发来的数据,也能调用write/send向外发送数据。
  • 应用层只需要把数据交给内核缓冲区即可,至于数据包什么时候发、一次发多大、丢包之后如何重传,全部由 TCP 协议负责控制,这就是 "传输控制协议" 名字的含义。

对于之前实现的tcp,有没有考虑过通过网络去进行传输的时候,如果没有一次性将我们的字符串穿过去要怎么办呢???,发生粘包现象该如何处理

TCP 粘包问题:一次 send 不一定一次 read 读完

TCP 是字节流协议 ,没有消息边界。你调用一次send("hello world"),对端recv()不一定一次就把完整字符串读出来;也可能多次 send 的数据粘在一起被一次读到,这就是粘包。

  • send/write:只是把数据拷贝到内核发送缓冲区,不代表数据立刻全部发出去;
  • recv/read:只是从内核接收缓冲区拿字节,不对应发送方的一次 send 调用;
  • 应用层没有消息边界,内核不知道你的一条消息到底多长。

我们要设计报文和解包的逻辑进行解决


代码

要求

网络版计算器 例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器 进行计算, 最后再把结果返回给客户端

约定方案一:直接约定文本格式

• 客户端发送一个形如"1+2"的字符串;

• 这个字符串中有两个操作数, 都是整形;

• 两个数字之间会有一个字符是运算符, 运算符只能是 + ;

• 数字和运算符之间没有空格;

约定方案二:

• 定义结构体来表示我们需要交互的信息;

• 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字 符串转化回结构体;

• 这个过程叫做 "序列化" 和 "反序列化"

用方案一,不用处理大小端对齐,但字符串解析繁琐,数据量大时效率低。

用方案二的化,传输结构体,会有**内存对齐的问题,**不同编译器、编译选项,结构体中间填充的占位字节不一样,两边结构体内存布局不一样。

我们采用两者相互结合的方式

文件 作用说明
Socket.hpp 对原生 socket 系统调用做 C++ 面向对象封装。封装socket/bind/listen/accept/connect,封装文件描述符管理、地址结构体,对外提供类接口,不用到处写原生 C 系统调用。
TcpServer.hpp TCP 服务端高层封装。内部持有 listen 监听 socket,提供Init()Start()启动服务;循环 accept 获取新连接,对接业务逻辑。
Calculate.hpp 计算器业务逻辑。接收运算请求,完成加减等计算,返回运算结果,只做数学计算,不碰网络。
Protocol.hpp 自定义通信协议(核心!序列化 / 反序列化在这里) 。1. 定义消息结构体;2. 实现序列化:结构体转字节流;3. 实现反序列化:字节流还原结构体;4. 实现ReadN/WriteN循环读写,处理长度头,解决 TCP 粘包拆包;网络两端共用这一份协议头,保证客户端、服务端报文格式完全一致。

socket

整体设计:

  1. Socket抽象基类,把 socket 所有行为定义成虚函数。把监听、连接的通用逻辑写在基类普通成员函数。
  2. TcpSocket:继承Socket,重写全部虚函数,完成 TCP 真正系统调用。

在这里我们就只实现TCP的部分

socket 能干什么?

  1. 创建 socket 文件描述符 socket()
  2. bind 绑定端口
  3. listen 监听(服务端用)
  4. accept 接收新连接(服务端用)
  5. connect 连接服务器(客户端用)
  6. 获取 / 设置 fd,关闭 fd
  7. recv 接收数据、send 发送数据

先一步一步来

基类

Tpc派生类实现

cpp 复制代码
void CreateSocketOrDie() override
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket false";
            exit(10);
        }
        else
        {
            LOG(LogLevel::INFO) << "socket success !!!  fd:" << _sockfd;
        }
    }
    void BindSocketOrDie(uint16_t port) override
    {
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_port = htons(port);
        local.sin_addr.s_addr = htonl(INADDR_ANY);
        local.sin_family = AF_INET;
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind false";
            cerr << "bind error->" << strerror(errno) << " (errno: " << errno << ")" << endl;
            exit(10);   
        }
        else
        {
            LOG(LogLevel::INFO) << "bind success ";
        }
        
    }
    void ListenSocketOrDie(int backlog=defaultval) override
    {
        int n=listen(_sockfd, backlog);
        if(n<0)
        {
            LOG(LogLevel::FATAL) << "listen false";
            exit(10);
        }
        else
        {
            LOG(LogLevel::INFO) << "listen success";
        }
    }

Protocol

Calculate

相关推荐
秋名RG1 小时前
Java 工程师学习路线
java·学习方法
秋名RG1 小时前
Java 基础语法
java·开发语言
java1234_小锋1 小时前
MyBatis-Plus 3.5.15 已全面支持 Spring Boot 4.0 及 Jackson 3.0
java·spring boot·mybatis
2501_937860941 小时前
Java 文件操作与IO(下):字节流、字符流实战IO读写
java·开发语言·python
xixingzhe21 小时前
spring boot项目接口访问慢问题解决方案
java·数据库·spring boot
zerwave1 小时前
从零实现简化 DDS 完整技术文档(含模块作用与详细注释)
linux·c++·机器人
MetaLite1 小时前
SpringBoot整合FastJson2数据脱敏-接口日志与失败降级
java·spring boot·后端
驭渊的小故事1 小时前
java抽奖项目-奖品上传中传递参数类型的错误
java·开发语言
段一凡-华北理工大学1 小时前
高炉智能布料技术与炉料分布优化~系列文章03:布料矩阵解码:环位、角度、圈数与料流的量化控制
java·网络·人工智能·矩阵·高炉智能化·高炉布料矩阵优化·高炉智能布料技术