1. 协议
在之前我们谈到,协议就是一种"约定",socket api接口,在读写数据时,都是按照"字符串"的方式来发送接收的,那么我们要传输一些"结构化"数据时怎么办呢?,比如说一个结构体 eg:
struct message{
string url;
string time;
string id;
string msg;
};
我们可以将数据,变为一个字符串(有效载荷),并为其添加报头(包含数据的一些属性),最后形成一个报文,这个过程就是序列化的过程 再将这个报文发送到网络中;另一台主机从网络中接收到该数据,将其取报头,并且将字符串转换为我们上面的结构化数据,这个过程就是反序列化。
业务结构化数据在发送到玩过中时先序列化再发送,收到的数据一定是序列字节流,要先进行反序列化,然后才能使用。
2. 自定义协议
下面我们实现一个网络版本的服务端和客户端,你并且自定义一个协议,实现序列化反序列化的过程。
makefile文件:
cc=g++
LD=-DMYSELF
.PHONY:all
all:calServer calClient
calServer:calServer.cc
$(cc) -o $@ $^ -std=c++11 -ljsoncpp ${LD}
calClient:calClient.cc
$(cc) -o $@ $^ -std=c++11 -ljsoncpp ${LD}
.PHONY:clean
clean:
rm -f calClient calServer
log.hpp(日志):
#pragma once
#include <iostream>
#include <cstring>
#include <string>
#include <stdarg.h>
#include <ctime>
#include <unistd.h>
using namespace std;
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3 // 出错可运行
#define FATAL 4 // 致命错误
const char* to_levelStr(int level)
{
switch ((level))
{
case DEBUG: return "DEBUG";
case NORMAL: return "NORMAL";
case WARNING: return "WARNING";
case ERROR: return "ERROR";
case FATAL: return "FATAL";
default: return nullptr;
}
}
void logMessage(int level, const char* format, ...) // ... 可变参数列表
{
#define NUM 1024
char logPreFix[NUM];
snprintf(logPreFix, sizeof(logPreFix), "[%s][%ld][pid: %d]", to_levelStr(level), (long int)time(nullptr), getpid());
char logContent[NUM];
va_list arg;
va_start(arg, format);
vsnprintf(logContent, sizeof(logContent), format, arg);
cout << logPreFix << logContent << endl;
}
protocol.hpp:
在这个文件中,定义了请求和响应类,并在类中实现了请求和响应的序列化(serialize)和反序列化(deserialize)操作;并且实现了添加报头(enLength)和去报头的操作(deLength)
#pragma once
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <sys/socket.h>
#include <sys/types.h>
#include <jsoncpp/json/json.h>
#define SEP " "
#define SEP_LEN strlen(SEP) // 不能使用sizeof()
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP)
enum { OK = 0, DIV_ZERO, MOD_ZERO, OP_ERROR };
// "_exitcode _result" -> "content_len"\r\n"_exitcode _result"\r\n
// "_x _op _y\r\n" -> "content_len"\r\n"__x _op _y"\r\n
std::string enLength(const std::string& text)
{
std::string send_string = std::to_string(text.size());
send_string += LINE_SEP;
send_string += text;
send_string += LINE_SEP;
return send_string;
}
// "content_len"\r\n"_x _op _y"\r\n -> "_x _op _y"
bool deLength(const std::string& package, std::string* text)
{
auto pos = package.find(LINE_SEP);
if(pos == std::string::npos) return false;
std::string text_len_string = package.substr(0, pos);
int text_len = std::stoi(text_len_string);
*text = package.substr(pos+LINE_SEP_LEN, text_len);
return true;
}
// 没有人规定我们的网络通信的时候 只能有一种协议
// 我们如何让系统知道我们用的哪一种协议?
// "协议编号"\r\n"content_len"\r\n"_exitcode _result"\r\n
class Request
{
public:
Request()
:_x(0)
,_y(0)
,_op(0)
{}
Request(int x, int y, char op)
:_x(x)
,_y(y)
,_op(op)
{}
// 序列化 自己写、现成的
bool serialize(std::string* out)
{
*out = "";
// 结构化 -> "_x _op _y\r\n" 一个请求就一行
std::string x_string = std::to_string(_x);
std::string y_string = std::to_string(_y);
*out = x_string;
*out +=