什么是自定义协议?
自定义协议 是指根据应用程序的需求,开发者定义的一种数据格式或传输规则。在网络通信中,协议用于描述双方通信时的数据结构、数据格式、传输顺序等。
-
定义:自定义协议是开发者为特定应用或场景设计的协议,用来满足应用之间的数据交换需求。与标准协议(如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 //编译的时候可以 用-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); };
2061

被折叠的 条评论
为什么被折叠?



