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全双工通信的关键要点:
独立的发送与接收缓冲区:
每一端(客户端和服务器)都有两个独立的缓冲区:一个用于发送数据(发送缓冲区),另一个用于接收数据(接收缓冲区)。
发送和接收操作互不干扰,可以同时进行。
同时发送和接收数据:
在一个TCP连接上,客户端和服务器可以同时发送和接收数据。例如,客户端可以在发送请求的同时接收服务器的响应,服务器也可以在接收客户端请求时,立即返回数据。
数据流双向传输:
TCP连接中的数据流是双向的。每个方向上都可以独立进行数据传输,而不会相互阻塞。
基于缓冲区的管理:
发送数据时,数据从应用层的缓冲区拷贝到 发送缓冲区,然后通过网络传输给接收方。
接收方的数据从网络传输到接收缓冲区,应用层可以从接收缓冲区读取数据。
这两个缓冲区(发送缓冲区和接收缓冲区)的独立性保证了全双工通信的流畅性。
流量控制与拥塞控制:
TCP采用 流量控制 和 拥塞控制,确保在通信过程中,双方的发送速率匹配,避免数据丢失或网络拥塞。
阻塞I/O操作:
TCP的I/O函数(如
read
和write
)通常是阻塞的,确保数据的同步处理。发送和接收操作在每个缓冲区内独立进行,但为了避免数据冲突或丢失,它们通过阻塞机制进行同步。
网络版计算器
例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.
约定方案一:
客户端发送一个形如"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 //编译的时候可以 用-DMyselfusing 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 Myselfstring s = to_string(x_);s += blank_sep;s += op_;s += blank_sep;s += to_string(y_);*content = s;return true;
#elseJson::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 Myselfint 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;
#elseJson::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 Myselfstring s = to_string(result_);s += blank_sep;s += to_string(code_);*out = s;return true;
#elseJson::Value root;root["result"] = result_;root["code"] = code_;Json::FastWriter w;*out = w.write(root);return true;
#endif}bool Deserialize(string &in) // 反序列化 字节流解析{
#ifdef Myselfint 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;
#elseJson::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 //编译的时候可以 用-DMyselfusing 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 Myselfstring s = to_string(x_);s += blank_sep;s += op_;s += blank_sep;s += to_string(y_);*content = s;return true;
#elseJson::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 Myselfint 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;
#elseJson::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 Myselfstring s = to_string(result_);s += blank_sep;s += to_string(code_);*out = s;return true;
#elseJson::Value root;root["result"] = result_;root["code"] = code_;Json::FastWriter w;*out = w.write(root);return true;
#endif}bool Deserialize(string &in) // 反序列化 字节流解析{
#ifdef Myselfint 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;
#elseJson::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); };