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

协议

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

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

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

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

我们之前在 C 语言中学习过 read、write 等系统调用,它们本质上是对文件描述符的读写操作。为了提高数据传输效率,系统在发送和接收数据时都会引入相应的缓冲区机制。因此,当进程通过 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

相关推荐
小羊没烦恼!3 天前
微服务化的基石——持续集成
java·大数据·word·powerpoint·.net
俊昭喜喜里3 天前
java中的继承和多态的区别
java
小羊没烦恼!3 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
未济3 天前
linux 配置环境变量
linux
傲世仙尊3 天前
目录即文件-Ext文件系统收尾篇
linux·c语言
譕痕3 天前
JSONObject与JSONArray封装数据格式区别
java·json
虎头金猫3 天前
4K 视频总卡在公网带宽?用 N1 + OpenList 把网盘播放链路重新理顺
运维·服务器·网络·python·容器·beautifulsoup·pandas
胡写代码3 天前
别再前后端各写一套表单校验了
java·后端
小鱼能吃糖3 天前
缺陷修复总览 · mall电商项目:5类缺陷,1个病根,4个业务域
java·电商
此时不提桶,更待何时3 天前
01-06-A-JVM排查实战详解
java·jvm