相关推荐recommended
Linux学习之自定义协议
作者:mmseoamin日期:2024-04-01

前言:

首先对于Tcp的socket的通信,我们已经大概了解了,但是其中其实是由一个小问题,我们目前是不得而知得,在我们客户端与服务端连接成功后,服务端读取来自客户端得消息,我们使用的是函数read,通过来读取accept之后的文件,从而获取信息,可是我们怎么去知道每次读取都是一个完成的报文呢?

在用户端与服务端通信的时候,用户首先写信息到发送缓冲区当中,经过网络推送到接收缓冲区,之后服务端从接收缓冲区中读取数据。在这个过程中,其实都是有Tcp自主控制的,这也就是他为什么叫传输控制协议,tcp会处理在发送过程中所有遇到的问题:发什么,什么时候发,出错了怎么办?这里我们用到的接口,如write,read,accept等都是实现用户到内核的数据拷贝。

Linux学习之自定义协议,第1张

如何保证,数据的发送是准确无误的,这就取决于tcp,而tcp怎么保证?这就需要协议的定制。

目录

前言:

协议的定制

套接字文件

protocol.hpp

Calcultor.hpp

客户端:

服务端:

主运行函数:


协议的定制

协议是一种约定,在进行socket通信时,读写都是用字符串,那么如果要传输的数据是结构化数据呢?

我们以接受发消息为例:

我们平常发的消息,不仅仅只有我们的消息的内容,其实还有时间,名字,和消息。

一般,用户发出消息,系统会将该消息写到一个固定的结构体里,在将该结构化数据转化为字符串,再通过网络发送出去,接受的时候,还是需要将字符串先转化为结构数据从,之后再访问其中成员获取消息给用户。

Linux学习之自定义协议,第2张

数据的结构化的过程我们可以简单的理解为协议的封装。

现在我们就通过编写一个网络计算机为例,将所有的知识结合起来:

套接字文件

首先是套接字文件,里面包含了客户端与服务端需要直接调用的方法,例如创建套接字,绑定,监听,连接,接受等。

//网络接口
#include
#include
#include
#include
#include
#include 
#include
const int backlog=10;
const int defaultfd=-1;
enum 
{
    CREATEERROR=1,
    BINDERROR=2,
    LISTENERROR=3,
    ACCEPTERROR=4
};
class Sock
{
      public:
            Sock(const int socket=defaultfd):_sockfd()
            {}
            void Createsockfd()
            {
                //1.创建套接字文件描述符
                _sockfd=socket(AF_INET,SOCK_STREAM,0);
                if(_sockfd<0)
                {
                    std::cout<<"创建失败"<<",错误码:"< 

我们来看看协议封装的头文件:

protocol.hpp

主要是序列化与反序列化

#pragma once
#include 
#include 
using namespace std;
const std::string blankspace = " ";
const string protocol_str = "/n";
//这里我们计算的字符串 10 * 10 会被Encode为 5/n10 * 10/n
std::string Encode(std::string &content)//编码格式为 len/nx op y/n
{
    std::string package = std::to_string(content.size());
    package += protocol_str;
    package += content;
    package += protocol_str;
    return package;
}
//反之去掉/n 与长度 变为 x op y格式
bool Decode(std::string &package, std::string *content)
{
    std::size_t pos = package.find(protocol_str);
    if(pos == std::string::npos) return false;
    std::string len_str = package.substr(0, pos);
    std::size_t len = std::stoi(len_str);
    // package = len_str + content_str + 2
    std::size_t total_len = len_str.size() + len + 2;
    if(package.size() < total_len) return false;
    *content = package.substr(pos+1, len);
    // earse 移除报文 package.erase(0, total_len);
    package.erase(0, total_len);
    return true;
}
// 定制协议,这里我们打算搞得是网络计算器
// 请求
class Request
{
public:
    Request(int data1, int data2,char c) : _x(data1), _y(data2),_op(c)
    {
    }
    // 报文的读格式为 len/n a op b
    bool Serialize(string *out) // 序列化
    {
        // 转化为字符串
        // 构建报文的有效载荷
        std::string s = std::to_string(_x);
        s += blankspace;
        s += _op;
        s += blankspace;
        s += std::to_string(_y);
        *out = s;
        return true;
        //序列化  将已知的x,op,y进行第一层封装为 x op y 格式的字符串
        
    }
    bool Deserialize(const string &package) // 反序列化   已经定义好了是 len\nx op y
    {
            size_t pos=package.find(blankspace);
            if(pos==std::string::npos)
            {
                return false;
            }
            std::string x=package.substr(0,pos);
            size_t pot=package.rfind(blankspace);
            if(pot==std::string::npos)
            {
                return false;
            }
            std::string y=package.substr(pot+1);
            if(pos+2!=pot)
            {
                return false;
            }
            _op=package[pos+1];
            _x=std::stoi(x.c_str());
            _y=std::stoi(y.c_str());
            //通过反序列化取得 字符 x,y,op 并转化相应类型
    }
    ~Request()
    {
    }
public:
    int _x;
    int _y;
    char _op; // + = * / %
};
// 应答
class Response
{
public:
        Response(int result, int code) : _result(result), _code(code)
        {
        }
        ~Response()
        {
        }
    // 序列化
        bool Serialize(string *out)
        {
            //"len"\n"result code"
            // 构建有效的报文载荷
            std::string s = std::to_string(_result);
            s += blankspace;
            s += std::to_string(_code);
            *out = s;
            return true;
        }
    // 反序列化
        bool Deserialize(const std::string &in) // len\n result code 
        {
            std::size_t pos = in.find(blankspace);
             if (pos == std::string::npos)
            return false;
            std::string part_left = in.substr(0, pos);
            std::string part_right = in.substr(pos+1);
            _result = std::stoi(part_left);
            _code = std::stoi(part_right);
            return true;
        }
public:
    int _result;
    int _code; // 错误码,非0具体实际表明错误原因
};

之后根据我们初始化构造传入的参数,进行编码,传入到定制对象Request中,进行序列化,之后完成序列化,取得定制对象并传入 函数Calculator中进行计算,再根据返回的结果在进行Response对象的定制,序列化,再编码,作为报文字符串可以进行发了,服务端之后接受到到再解码,反序列化,即可。

计算转化头文件

Calcultor.hpp

主要是将传入的字符串,编码,序列化,之后调用计算,再解码,反序列化。实现计算过程。

#pragma once
#include
#include"protocol.hpp"
class ServerCal{
    public:
    ServerCal()
    {}
    Response CalculatorOperator(const Request &req)
    {
        Response resp(0, 0);
        switch (req.op)
        {
        case '+':
            resp.result = req.x + req.y;
            break;
        case '-':
            resp.result = req.x - req.y;
            break;
        case '*':
            resp.result = req.x * req.y;
            break;
        case '/':
        {
            if (req.y == 0)
                resp.code = Div_Zero;
            else
                resp.result = req.x / req.y;
        }
        break;
        case '%':
        {
            if (req.y == 0)
                resp.code = Mod_Zero;
            else
                resp.result = req.x % req.y;
        }
        break;
        default:
            resp.code = Other_Oper;
            break;
        }
        return resp;
    }
    std::string Calculator(std::string &package)
    {
        //首先把内容转成报文格式
        std::string content;
        bool can=Decode(package,&content);//把传进来的字符解码 len/n x op y
        if(!can) 
            return ;
        Request req;
        can=req.Deserialize(content);//反序列化 x op y
        if(!can)
        return ;
        content="";
        Response resp=CalculatorOperator(req);//进行计算req.x,req.y,req.op
        resp.Serialize(&content);//变为 result code
        content=Encode(content);//变为 len/n result code
        return content;
      
    }
    private:
};

协议的定制就是约定,无论客户端还是服务端都要以这种方式来接受发报文,不符合该要求的是不会接收的。

可以看到序列与反序列化基本上都是字符串操作,每次这样写有点麻烦,实际上,再开发过程中,有现成的序列与反序列化的方法供我们使用,一般就是json,protobuf.

//json
bool Serialize(std::string *out)
    {
        Json::Value root;
        root["x"] = x;
        root["y"] = y;
        root["op"] = op;
        // Json::FastWriter w;
        Json::StyledWriter w;
        *out = w.write(root);
        return true;
    }
bool Deserialize(const std::string &in) // "x op y"
    {
       Json::Value root;
        Json::Reader r;
        r.parse(in, root);
        x = root["x"].asInt();
        y = root["y"].asInt();
        op = root["op"].asInt();
        return true;
    }
bool Serialize(std::string *out)
    {
        Json::Value root;
        root["result"] = result;
        root["code"] = code;
        // Json::FastWriter w;
        Json::StyledWriter w;
        *out = w.write(root);
        return true;
    }
    bool Deserialize(const std::string &in) // "result code"
    {
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);
        result = root["result"].asInt();
        code = root["code"].asInt();
        return true;
    }

客户端:

#include
#include
#include
#include
#include
#include"Protocol.hpp"
#include"Socket.hpp"
void USage(char *s)
{
    std::cout<<"Usage:"< "< 0)
        {
            buffer[n] = 0;
            inbuffer_stream += buffer; // "len"\n"result code"\n
            std::cout << inbuffer_stream << std::endl;
            std::string content;
            bool r = Decode(inbuffer_stream, &content); // "result code"
            assert(r);
            Response resp;
            r = resp.Deserialize(content);
            assert(r);
            cout<<"接受字符串:"< 
 

服务端:

#pragma once
#include
#include
#include
#include 
#include"Socket.hpp"
using namespace std;
const uint16_t defaultport=8080;
const string defaultip="127.0.0.1";
const int defaultsockfd=-1;
using func_t = std::function;
class Tcpserver
{
  public:
         Tcpserver(uint16_t port ,func_t callback) : _port(port), _callback(callback)
         {}
         void InitServer()
         {
            _listensock.Createsockfd();
            _listensock.Bind(_port);
            _listensock.Listen();
         }
         void Start()
         {
            
            signal(SIGCHLD,SIG_IGN); 
            signal(SIGPIPE,SIG_IGN); 
            while(true)
            {
                string clientip;
                uint16_t clientport;
                int sockfd=_listensock.Accept(&clientip,&clientport);//回调初始化地址与端口号
                if(sockfd<0)
                {
                    continue;
                }   
                //孙子进程提供服务,可并发访问。
                if(fork()==0)
                {
                        //关闭文件描述符不影响文件缓冲区,防止文件描述符不够用
                        _listensock.Close(); 
                        //service
                        std::string buffoutput;
                        while(true)
                        {
                            char buff[1024];
                            ssize_t n=read(sockfd, buff, sizeof(buff));
                            if(n==0)
                            {
                                std::cout<<"读取消息失败"<<"错误码"<0)
                            {
                                std::cout<<"读取消息成功:"< 

主运行函数:

#include"Calculator.hpp"
#include"ServerCalculator.hpp"
#include 
void Hlper(char *s)
{
    std::cout<<"please enter correct command in '"<InitServer();
    Cal->Start();
                                     
    return 0;
}