代码仓库地址:https://gitee.com/tgwTTT/linux-learning-dai/tree/master/netCal

计算机网络协议

计算机网络协议指在计算机网络中进行数据交换时所遵循的一系列规则、标准或约定。,它们定义了数据如何在网络中传输、如何被接收和解释

例如:在日常学习中我们接触的最多的就是IP,TCP,DUP,HTTP等网络协议

在Tcp中我都知道数据是以字符串的形式接收发的,但是我们需要传输某些“结构化的数据”需要怎么做呢?

协议的本质就是双方约定好的数据结构,双方只需要按照约定好的进行操做即可

下面作者将以一个网络计算器为例介绍:

我们需要客⼾端把要计算的两个加数发过去, 然后由服务器进⾏计算, 最后再把结果返回给客⼾端.

约定⽅案⼀:

• 客⼾端发送⼀个形如”1+2″的字符串;

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

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

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

约定⽅案⼆:

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

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

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

序列化和反序列化

序列化:把内存中的对象变成字节流/字符串/二进制等可存储或可传输的格式。

反序列化:把字节流/字符串/二进制重新还原成内存中的对象

⽆论我们采⽤⽅案⼀, 还是⽅案⼆, 还是其他的⽅案, 只要保证, ⼀端发送时构造的数据, 在另⼀端能够正确的进⾏解析, 就是ok的. 这种约定, 就是 应⽤层协议 但是,为了让我们深刻理解协议,我们打算⾃定义实现⼀下协议的过程。

• 我们采⽤⽅案2,我们也要体现协议定制的细节 • 我们要引⼊序列化和反序列化,只不过我们直接采⽤现成的⽅案 — jsoncpp库

• 我们要对socket进⾏字节流的读取处理

举例:read、write、recv、send和tcp为什么⽀持全双⼯?

由上图可知: 在任何⼀台主机上,TCP连接既有发送缓冲区,⼜有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双⼯,这就是为什么⼀个tcp sockfd读写都是它的原因 , 实际数据什么时候发,发多少,出错了怎么办,由TCP控制,所以TCP叫做传输控制协议

Jsoncpp的安装:

Jsoncpp 是⼀个⽤于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。Jsoncpp 是开源的,⼴泛⽤于各种需要处理 JSON 数据的 C++ 项⽬中。特性

1. 简单易⽤:Jsoncpp 提供了直观的 API,使得处理 JSON 数据变得简单。

2. ⾼性能:Jsoncpp 的性能经过优化,能够⾼效地处理⼤量 JSON 数据。

3. 全⾯⽀持:⽀持 JSON 标准中的所有数据类型,包括对象、数组、字符串、数字、布尔值和 null。

4. 错误处理:在解析 JSON 数据时,Jsoncpp 提供了详细的错误信息和位置,⽅便开发者调试。 当使⽤Jsoncpp库进⾏JSON的序列化和反序列化时,确实存在不同的做法和⼯具类可供选择。以下是对Jsoncpp中序列化和反序列化操作的详细介绍:

安装

ubuntu:sudo apt-get install libjsoncpp-dev

Centos: sudo yum install jsoncpp-devel

开始实现

在这个系统中我们最需要关注的就是协议的定制,但是在这之前,我们也需要先封装好Socket

Socket的封装:

#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"Log.hpp"
#include"Common.hpp"
using namespace LogMoudle;
namespace SocketMoudle
{
    const static int defaulted=-1;
    class Socket
    {
        // 基类套接字
    public:
        Socket():_sockfd(defaulted){}
        Socket(int sockfd):_sockfd(sockfd){}
        virtual int SocketOrDie() = 0;
        virtual bool BindOrDie(uint16_t port) = 0;
        virtual bool ListenOrDie(int backlog=10) = 0;
        virtual std::shared_ptr Accept(NetAddr*client) = 0;
        virtual int Recv(std::string *in)=0;
        virtual void Close() = 0;
        virtual bool Send(const std::string &out)=0;
        virtual int Connect(std::string& ip,uint16_t port)=0;
        void BuildclientSocket(std::string& ip,uint16_t port){
            //创建套接字
            SocketOrDie();
        }
        int GetSockFd() const { return _sockfd; }
    public:
        void BuildListenSocket(uint16_t port,int backlog=10)
        {
            SocketOrDie();
            BindOrDie(port);
            ListenOrDie(backlog);
        }
        void BuildUdpSocket(uint16_t port,int backlog=10)
        {
            SocketOrDie();
            BindOrDie(port);
        }
    private:
        int _sockfd;
    };
    class TcpSocket : public Socket
    {
    public:
        TcpSocket(int sockfd):_sockfd(sockfd){};
        TcpSocket():_sockfd(defaulted){};
        TcpSocket(std::string& ip,uint16_t port){BuildclientSocket(ip,port);}
        int SocketOrDie() override
        {
            _sockfd = socket(AF_INET, SOCK_STREAM, 0);
           if(_sockfd < 0){
            LOG(LogLevel::ERROR)<<"套接字创建失败";
            exit(1);
           }
           LOG(LogLevel::INFO)<<"套接字创建成功";
            return 0;
        }
        int Connect(std::string& ip,uint16_t port)override{
            NetAddr server(ip,port);
           return connect(_sockfd,(struct sockaddr*)&server.getAddr(),server.getAddrLen());
        }
        ~TcpSocket() {}
        bool BindOrDie(uint16_t port)override
        {
            NetAddr _addr(port);
            int ret=bind(_sockfd,(struct sockaddr*)&_addr.getAddr(),_addr.getAddrLen());
            if(ret < 0){
                LOG(LogLevel::ERROR)<<"套接字绑定失败";
                return false;
            }
            LOG(LogLevel::INFO)<<"套接字绑定成功";
            return true;
        }
        bool ListenOrDie(int backlog=10)override
        {
            int ret=listen(_sockfd,backlog);
            if(ret < 0){
                LOG(LogLevel::ERROR)<<"套接字监听失败";
                return false;
            }
            LOG(LogLevel::INFO)<<"套接字监听成功";
            return true;
        };
        std::shared_ptr Accept(NetAddr*client)override
        {
            struct sockaddr_in client_addr;
            socklen_t client_len=sizeof(client_addr);
            int fd=accept(_sockfd,(struct sockaddr*)&client_addr,&client_len);
            if(fd < 0){
                //LOG(LogLevel::ERROR)<<"accept失败...";
                return nullptr;
            }
            client->SetAddr(client_addr);
            return std::make_shared(fd);
        };
        void Close()override
        {
            if(_sockfd > 0) close(_sockfd);
        }
        int Recv(std::string *in)override
        {
            char buffer[1024];
            ssize_t n=recv(_sockfd,buffer,sizeof(buffer)-1,0);
            // if(n < 0){
            //     if(errno==EINTR||errno==EAGAIN){
            //         return "";
            //     }
            //     LOG(LogLevel::ERROR)<<"接收数据失败";
            //     return false;
            // }else if(n==0){
            //     LOG(LogLevel::INFO)<<"客户端断开连接";
            //     return false;
            // }
            if(n>0){
            buffer[n]=0;
            *in+=buffer;
            }
            return n;
        };
        bool Send(const std::string &out)override{
            ssize_t n=send(_sockfd,out.c_str(),out.size(),0);
            if(n < 0){
                if(errno==EINTR||errno==EAGAIN){
                    return "";
                }
                LOG(LogLevel::ERROR)<<"发送数据失败";
                return false;
            }else if(n==0){
                LOG(LogLevel::INFO)<<"客户端断开连接";
                return false;
            }
            return true;
        }
    private:
        int _sockfd;//listenfd,sockfd
        
    };
    // class UdpSocket:public Socket{

    // };
}

上面tcpserver 的操作在前面都有所介绍,如有不懂,请看上一篇文章,本文不过多赘述

protocol的封装

需求:

  • 功能需求:客户端发送两个整数和一个运算符,服务器返回运算结果和状态码。
  • 技术需求:
    • 请求和响应需序列化为字符串(JSON 格式)。
    • 需解决 TCP 粘包、拆包问题,保证一次读取到完整报文。

1. Request 类

用于封装客户端的请求数据,包括两个操作数和一个运算符。

class Request {
private:
    int _x, _y;
    char _oper;
public:
    Request(int x, int y, char oper): _x(x), _y(y), _oper(oper) {}
    std::string serialize();      // 序列化为 JSON 字符串
    bool deserialize(std::string& in); // 从 JSON 字符串反序列化
    // ...getter方法...
};

2. Response 类

用于封装服务器的响应数据,包括计算结果和状态码。

class Response {
private:
    int _result;
    int _code;
public:
    Response(int result, int code): _result(result), _code(code) {}
    std::string serialize();
    bool deserialize(std::string& in);
    // ...getter/setter方法...
};

协议处理类 Protocol

1. Encode/Decode

  • Encode:将 JSON 字符串加上长度和分隔符,生成完整报文。
  • Decode:从缓冲区中解析出一个完整报文,处理粘包/拆包。

2. GetRequest/GetResponse

  • GetRequest:服务器端循环读取数据,解析出完整请求,处理后返回响应。
  • GetResponse:客户端读取响应,解析出完整报文。

3. BuildRequeststring

  • 构造请求报文,便于客户端调用。

完整代码请参考仓库代码

今天的更新就到这里,我么下次见,如有错误欢迎指出