linux——自定义协议

什么是自定义协议?

自定义协议 是指根据应用程序的需求,开发者定义的一种数据格式或传输规则。在网络通信中,协议用于描述双方通信时的数据结构、数据格式、传输顺序等。

  • 定义:自定义协议是开发者为特定应用或场景设计的协议,用来满足应用之间的数据交换需求。与标准协议(如HTTP、TCP/IP)不同,自定义协议不依赖于任何标准,而是根据具体需求自行设计。

  • 用途例如,在一个客户端与服务器之间进行通信时,客户端发送特定格式的数据请求,服务器根据预定义的协议格式解析并返回响应数据。自定义协议的设计通常需要考虑数据的序列化、反序列化、传输效率和安全性。

  • 示例:比如,设计一个简单的数学计算协议,客户端发送请求(比如两个数字和一个运算符),服务器进行处理,返回结果。自定义协议在此场景中的格式可以是:数字1 运算符 数字2

    // 请求结构
    struct Request {
        int num1;
        int num2;
        char op; // 操作符(如 +,-,*,/)
    };
    
    // 响应结构
    struct Response {
        int result;
        int code; // 0: 成功,1: 错误
    };
    

    通信双方根据这个格式进行数据的传递和解析,即构成了自定义协议.

自定义协议 是属于 应用层 的概念。

  • 应用层 是 OSI 模型中的最上层,也是计算机网络协议栈中最接近用户的层。它定义了应用程序之间如何交换数据,涵盖了各种应用协议,如 HTTP、FTP、SMTP,以及你提到的自定义协议。

  • 自定义协议是应用层的一部分,通常由应用程序开发者定义,用于特定应用的需求。自定义协议可以定义数据格式、通信规则、如何打包和解包数据等。它通常使用其他协议(如 TCP/IP、UDP)来实现数据的传输。

说到自定义协议,我们还要谈到序列化和反序列化这两个概念。

序列化与反序列化简介

序列化反序列化 是计算机程序在处理数据传输和存储时常用的两个概念它们用于将数据从一种格式转化为另一种格式通常在网络通信、文件存储和跨平台数据交换中广泛应用。

1. 序列化(Serialization)

序列化 是将程序中的对象、数据结构或内存中的数据转换为可存储或传输的格式的过程。常见的格式有 JSON、XML、二进制数据流等。

  • 目的将内存中的复杂数据结构转换为字节流或其他格式,使其能够被存储在文件、数据库中或通过网络传输。

  • 应用场景

    • 网络通信:将对象或数据结构转化为字节流或字符串,在客户端与服务器之间传输。

    • 数据持久化:将对象序列化为文件或数据库记录,便于后续读取。

    • 跨平台通信:将数据转换为跨平台通用格式,支持不同系统之间的通信。

2. 反序列化(Deserialization)

反序列化将序列化后的数据(如字节流或字符串)转换回程序能够理解的对象、数据结构或实体的过程。

  • 目的:将传输或存储的字节流转换回原始数据结构,使程序可以对其进行操作。

  • 应用场景

    • 网络通信:将接收到的数据字节流还原为原始对象,供程序进一步处理。

    • 数据恢复:从存储中恢复对象数据,供程序重新使用。

将数据转化为字节流而不是直接传输结构体本身,主要是出于以下几个原因:

  • 跨平台兼容性:不同平台的内存布局差异会导致直接传输结构体时的兼容性问题。

  • 灵活性:序列化为字节流或标准化格式,可以提高程序的灵活性、可扩展性,并支持版本控制。

  • 网络传输效率:通过序列化,你可以优化数据的大小,减少带宽浪费,并更容易适应不同的网络协议。

  • 调试与可读性:序列化格式(如 JSON)更加易于调试和查看,对于错误处理和日志记录也更为方便。

  • 安全性:序列化后的数据更容易进行验证,保证数据的完整性和安全性。

TCP的全双工通信总结

全双工通信 是指通信双方可以同时进行数据的发送和接收。在 TCP(传输控制协议)中,全双工通信 是其核心特性之一,支持客户端和服务器之间在同一个连接上同时发送和接收数据。

TCP全双工通信的关键要点:

  1. 独立的发送与接收缓冲区

    • 每一端(客户端和服务器)都有两个独立的缓冲区:一个用于发送数据(发送缓冲区),另一个用于接收数据(接收缓冲区)。

    • 发送和接收操作互不干扰,可以同时进行。

  2. 同时发送和接收数据

    • 在一个TCP连接上,客户端和服务器可以同时发送和接收数据。例如,客户端可以在发送请求的同时接收服务器的响应,服务器也可以在接收客户端请求时,立即返回数据。

  3. 数据流双向传输

    • TCP连接中的数据流是双向的。每个方向上都可以独立进行数据传输,而不会相互阻塞。

  4. 基于缓冲区的管理

    • 发送数据时,数据从应用层的缓冲区拷贝到 发送缓冲区,然后通过网络传输给接收方。

    • 接收方的数据从网络传输到接收缓冲区,应用层可以从接收缓冲区读取数据。

    • 这两个缓冲区(发送缓冲区和接收缓冲区)的独立性保证了全双工通信的流畅性。

  5. 流量控制与拥塞控制

    • TCP采用 流量控制拥塞控制,确保在通信过程中,双方的发送速率匹配,避免数据丢失或网络拥塞。

  6. 阻塞I/O操作

    • TCP的I/O函数(如 readwrite)通常是阻塞的,确保数据的同步处理。发送和接收操作在每个缓冲区内独立进行,但为了避免数据冲突或丢失,它们通过阻塞机制进行同步。

网络版计算器

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

约定方案一:

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

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

两个数字之间会有一个字符是运算符, 运算符只能是 + ;数字和运算符之间没有空格;

约定方案二:

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

发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;这个过程叫做 "序列化" 和“反序列化”。

1.Socket.hpp
#pragma once

#include <iostream>
#include <string>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include "log.hpp"

const int backlog =5;
extern Log lg;

enum
{
    SOCKERR = 1,
    BINDERR = 2,
    LISTENERR
};

class Sock
{
public:
    Sock()
    {
    }
    ~Sock()
    {
    }

public:
    void Socket()
    {
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd_ < 0)
        {
            lg("fatal", "socket create fail errno:%d strerrno:%s", errno, strerror(errno));
            exit(1);
        }
    }

    void Bind(uint16_t port)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));

        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr=INADDR_ANY;

        socklen_t len = sizeof(local);
        if (bind(sockfd_, (struct sockaddr *)&local, len) < 0)
        {
            lg("fatal", "bind fail errno:%d,strerror:%s\n", errno, strerror(errno));
            exit(BINDERR);
        }
    }

    void Listen() // 变为监听窗口,错了就别玩了
    {
        if (listen(sockfd_, backlog) < 0)
        {
            lg("fatal", "Listen fail errno:%d,strerror:%s\n", errno, strerror(errno));
            exit(LISTENERR);
        }
    }

    int Accpect(string*ip,uint16_t* port)//输出型参数,谁链接的我,对方的端口,ip
    {

        struct sockaddr_in temp;
        socklen_t len=sizeof(temp);
        
        int newfd=accept(sockfd_,(struct sockaddr*)&temp,&len);
        if(newfd<0)
        {
            lg("fatal", "accpect fail errno:%d,strerror:%s\n", errno, strerror(errno));
            return -1;
        }
        
        *port=ntohs(temp.sin_port);
        
        string ipstr=inet_ntoa(temp.sin_addr);
        *ip=ipstr;

        return newfd;
    }

    bool Connect(string& ip,uint16_t port)//服务器的端口,ip
    {
        struct sockaddr_in server;
        bzero(&server,0);
        server.sin_family=AF_INET;
        server.sin_port=htons(port);
        server.sin_addr.s_addr=inet_addr(ip.c_str());

        int ret=connect(sockfd_,(struct sockaddr*)&server,sizeof(server));
        if(ret<0)
        {
            lg("info", "Connect fail errno:%d,strerror:%s\n", errno, strerror(errno));
            return false;
        }
        return true;
    }

    void Close()
    {
        close(sockfd_);
    }
public:
    int sockfd_;
};
2.Protocal.hpp
#pragma once

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

// #define Myself 1   //编译的时候可以 用-DMyself

using namespace std;

const string blank_sep = " ";
const string protocal_sep = "\n";

//"x op y" ->
//"len"/n"x op y"/n
std::string Encode(string &content)
{
    int len = content.size();
    string package = to_string(len);
    package += protocal_sep;
    package += content;
    package += protocal_sep;
    return package;
}

//"len"/n"x op y"/n ->  解析可能错误
//"x op y"
bool Decode(string &package, string *content)
{
    int pos = package.find(protocal_sep);
    if (pos == string::npos)
        return false;

    string len_str = package.substr(0, pos);
    int len = stoi(len_str);

    int to_len = len_str.size() + 2 + len;
    if (package.size() < to_len)
        return false;

    *content = package.substr(pos + 1, len); // 位置 加 长度

    package.erase(0, to_len); // 解析的这一段,从缓冲区里去掉
    return true;
}

class Request
{
public:
    Request(int x, int y, char op) : x_(x), y_(y), op_(op)
    {
    }

    Request()
    {
    }

    ~Request()
    {
    }

public:
    bool Serialize(string *content) // 序列化为 字节流
    {
#ifdef Myself
        string s = to_string(x_);
        s += blank_sep;
        s += op_;
        s += blank_sep;
        s += to_string(y_);

        *content = s;
        return true;
#else
        Json::Value root;

        root["x"] = x_;
        root["y"] = y_;
        root["op"] = op_;

        Json::FastWriter w;
        *content = w.write(root);
        return true;
#endif
    }

    bool Deserialize(string &in) // 反序列化 将字节流解析
    {
#ifdef Myself
        int l = in.find(blank_sep);
        if (l == string::npos)
            return false;
        string x = in.substr(0, l); // 左闭,右开

        int r = in.rfind(blank_sep);
        if (r == string::npos)
            return false;
        string y = in.substr(r + 1);

        if (l + 2 != r)
            return false;

        x_ = stoi(x);
        y_ = stoi(y);
        op_ = in[l + 1];
        return true;
#else
        Json::Reader R;
        Json::Value root;

        bool ret = R.parse(in.c_str(), root);
        if (!ret)
            return false;
        x_ = root["x"].asInt();
        y_ = root["y"].asInt();
        op_ = root["op"].asInt();
        return true;

#endif
    }

    void Printf()
    {
        cout << "任务请求" << x_ << op_ << y_ << "=?" << endl;
    }

public:
    int x_;
    int y_;
    char op_;
};

class Respone
{
public:
    Respone(int res, int c) : result_(res), code_(c)
    {
    }

    Respone()
    {
    }
    ~Respone()
    {
    }

public:
    bool Serialize(string *out) // 序列化 形成字节流
    {
#ifdef Myself
        string s = to_string(result_);
        s += blank_sep;
        s += to_string(code_);
        *out = s;
        return true;
#else
        Json::Value root;

        root["result"] = result_;
        root["code"] = code_;

        Json::FastWriter w;
        *out = w.write(root);
        return true;
#endif
    }

    bool Deserialize(string &in) // 反序列化 字节流解析
    {
#ifdef Myself
        int pos = in.find(blank_sep);
        if (pos == string::npos)
            return false;
        string x = in.substr(0, pos);
        int result = stoi(x);

        string y = in.substr(pos + 1);
        int code = stoi(y);

        result_ = result;
        code_ = code;
        return true;
#else
        Json::Reader R;
        Json::Value root;

        bool ret = R.parse(in.c_str(), root);
        if (!ret)
            return false;
        result_=root["result"].asInt();
        code_=root["code"].asInt();
        return true;
#endif
    }

    void Printf()
    {
        cout << "result " << result_ << "  code " << code_ << endl;
    }

public:
    int result_;
    int code_;
};
3.Server.cal
#pragma once

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

// #define Myself 1   //编译的时候可以 用-DMyself

using namespace std;

const string blank_sep = " ";
const string protocal_sep = "\n";

//"x op y" ->
//"len"/n"x op y"/n
std::string Encode(string &content)
{
    int len = content.size();
    string package = to_string(len);
    package += protocal_sep;
    package += content;
    package += protocal_sep;
    return package;
}

//"len"/n"x op y"/n ->  解析可能错误
//"x op y"
bool Decode(string &package, string *content)
{
    int pos = package.find(protocal_sep);
    if (pos == string::npos)
        return false;

    string len_str = package.substr(0, pos);
    int len = stoi(len_str);

    int to_len = len_str.size() + 2 + len;
    if (package.size() < to_len)
        return false;

    *content = package.substr(pos + 1, len); // 位置 加 长度

    package.erase(0, to_len); // 解析的这一段,从缓冲区里去掉
    return true;
}

class Request
{
public:
    Request(int x, int y, char op) : x_(x), y_(y), op_(op)
    {
    }

    Request()
    {
    }

    ~Request()
    {
    }

public:
    bool Serialize(string *content) // 序列化为 字节流
    {
#ifdef Myself
        string s = to_string(x_);
        s += blank_sep;
        s += op_;
        s += blank_sep;
        s += to_string(y_);

        *content = s;
        return true;
#else
        Json::Value root;

        root["x"] = x_;
        root["y"] = y_;
        root["op"] = op_;

        Json::FastWriter w;
        *content = w.write(root);
        return true;
#endif
    }

    bool Deserialize(string &in) // 反序列化 将字节流解析
    {
#ifdef Myself
        int l = in.find(blank_sep);
        if (l == string::npos)
            return false;
        string x = in.substr(0, l); // 左闭,右开

        int r = in.rfind(blank_sep);
        if (r == string::npos)
            return false;
        string y = in.substr(r + 1);

        if (l + 2 != r)
            return false;

        x_ = stoi(x);
        y_ = stoi(y);
        op_ = in[l + 1];
        return true;
#else
        Json::Reader R;
        Json::Value root;

        bool ret = R.parse(in.c_str(), root);
        if (!ret)
            return false;
        x_ = root["x"].asInt();
        y_ = root["y"].asInt();
        op_ = root["op"].asInt();
        return true;

#endif
    }

    void Printf()
    {
        cout << "任务请求" << x_ << op_ << y_ << "=?" << endl;
    }

public:
    int x_;
    int y_;
    char op_;
};

class Respone
{
public:
    Respone(int res, int c) : result_(res), code_(c)
    {
    }

    Respone()
    {
    }
    ~Respone()
    {
    }

public:
    bool Serialize(string *out) // 序列化 形成字节流
    {
#ifdef Myself
        string s = to_string(result_);
        s += blank_sep;
        s += to_string(code_);
        *out = s;
        return true;
#else
        Json::Value root;

        root["result"] = result_;
        root["code"] = code_;

        Json::FastWriter w;
        *out = w.write(root);
        return true;
#endif
    }

    bool Deserialize(string &in) // 反序列化 字节流解析
    {
#ifdef Myself
        int pos = in.find(blank_sep);
        if (pos == string::npos)
            return false;
        string x = in.substr(0, pos);
        int result = stoi(x);

        string y = in.substr(pos + 1);
        int code = stoi(y);

        result_ = result;
        code_ = code;
        return true;
#else
        Json::Reader R;
        Json::Value root;

        bool ret = R.parse(in.c_str(), root);
        if (!ret)
            return false;
        result_=root["result"].asInt();
        code_=root["code"].asInt();
        return true;
#endif
    }

    void Printf()
    {
        cout << "result " << result_ << "  code " << code_ << endl;
    }

public:
    int result_;
    int code_;
};
4.Tcpserver.hpp
#pragma once
#include <functional>
#include <signal.h>
#include "log.hpp"
#include "Socket.hpp"
#include "ServerCal.hpp"
#include "Protocal.hpp"

using func_t=function<string(string&)>;
extern Log lg;

class Server
{
public:
    Server(uint16_t port,func_t calback):port_(port) ,calback_(calback)
    {}

    bool Init()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
        lg("info","init server .... done");
        return true;
    }

    void Start()
    {
        signal(SIGCHLD, SIG_IGN);
        signal(SIGPIPE, SIG_IGN);
        while(true)
        {
            string clientip;
            uint16_t clientport;

            int sockfd=listensock_.Accpect(&clientip,&clientport);
            if(sockfd<0)
            {
                lg("fatal","accpect fail");
                break;
            }
            
            lg("info","accpect success");
            if(fork()==0)
            {
                listensock_.Close();
                string inbuffer_stream;
                while(true)
                {
                    char buffer[1024];
                    int n=read(sockfd,buffer,sizeof(buffer));
                    if(n>0)
                    {
                        buffer[n]=0;
                        inbuffer_stream+=buffer;
                        lg("Debug", "debug:\n%s", inbuffer_stream.c_str());

                        while(true)
                        {
                            string ret=calback_(inbuffer_stream);
                            if(ret.empty())
                            break;

                            lg("Dedug","debug:\n%s",ret.c_str());
                            lg("Dedug","debug:\n%s",inbuffer_stream.c_str());

                            write(sockfd,ret.c_str(),ret.size());
                        }
                    }
                    else 
                    {
                        cout<<"错误了"<<endl;
                        break;
                    }
                }
                exit(0);
            }
            close(sockfd);
        }
    }

private:
    Sock listensock_;
    uint16_t port_;
    func_t calback_;
};
5.Tcpserver.cpp
#include "Tcpserver.hpp"
#include "ServerCal.hpp"


void Usage(string prco)
{
    cout<<prco<<" [1024++]"<<endl;
}

int main(int argc,char*argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(0);
    }

    uint16_t port=stoi(argv[1]);
    ServerCal calculator;

    Server* tsrv=new Server(port,bind(&ServerCal::Calculator,&calculator,placeholders::_1));//第一个是this指针
    //不用bind的话可以创建一个ServerCal的对象。
    tsrv->Init();
    tsrv->Start();
    return 0;
}

其中用到的bind 是 C++11 引入的一个函数模板,用于创建一个新的可调用对象,将函数的某些参数“绑定”到具体值,然后返回一个可以稍后调用的函数。

基本用法:

std::bind(function, arg1, arg2, ..., argN);
  • function:要绑定的函数。

  • arg1, arg2, ...:绑定的参数,可以是具体值或占位符(std::placeholders)。

#include <iostream>
#include <functional>

int add(int x, int y) {
    return x + y;
}

int main() {
    // 绑定第一个参数为 5,第二个参数用占位符
    auto add5 = std::bind(add, 5, std::placeholders::_1);

    std::cout << add5(3) << std::endl;  // 输出 8 (5 + 3)
    return 0;
}
  • 简化函数调用std::bind 可以将函数和参数绑定,生成新函数,便于后续调用。

  • 灵活性:支持部分绑定函数参数,允许在调用时提供剩余的参数。

lambda 比较:

// 使用 std::bind
auto add5 = std::bind(add, 5, std::placeholders::_1);

// 使用 lambda
auto add5 = [](int x) { return add(5, x); };

评论 48
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

藤椒味的火腿肠真不错

感谢您陌生人对我求学之路的支持

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

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

打赏作者

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

抵扣说明:

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

余额充值