目录
协议定制
如果想要传输【结构化数据】,例如结构体的多个字段
cpp
class User
{
public:
std::string _url; // 头像url
std::string _time; // 发送时间
std::string _nickname; // 昵称
std::string _msg; // 消息
};
可以将多个字段通过固定格式生成一个字符串报文,例如这里每个字段用":"隔开,再将该字符串通过网络发出

另一端就会接受到该报文,再通过固定格式转换成User类的每个字段(前提是需要知道格式是什么)
在上面的操作中,将多个字段转换成一个报文字符串,就称为序列化
将报文字符串再转换成多个字段的过程,就称为反序列化
而User类就是我们定的业务协议!
但是,如果客户端发送了一段报文后,服务端没来得及接收,此时客户端又发送了几段报文,要怎么保证服务端收到的是**【一个】**完成的报文?
一般有三种做法:定长、特殊符号、自描述方式
例如,每个报文前面加上一段固定大小的报头,该报头内描述的是报文的长度

TCP是全双工通信,它内部分别有发送缓冲区和接收缓冲区,应用层的缓冲区即我们自己定义的buffer\[\]
在报头中可以添加对于整个报文的描述信息,例如有效载荷的大小,这样就可以确定一个完整的报文的边界在哪里
protocol.hpp:
cpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include "log.hpp"
const char *SEP = " "; // 序列化时的分隔符
const int SEP_LEN = strlen(SEP); // 分隔符长度
const char *SEP_LINE = "\r\n"; // 序列化时的行分隔符
const int SEP_LINE_LEN = strlen(SEP_LINE); // 行分隔符长度
// const char *LINE_SEP = "\r\n"; // 报文结尾
//"x op y" -> "有效载荷大小"\r\n"x op y"\r\n
bool AddHeader(std::string &payload) // 为 有效载荷 添加报头
{
std::string message = std::to_string(payload.size());
message += SEP_LINE + payload + SEP_LINE;
payload = message;
return true;
}
//"有效载荷大小"\r\n"x op y"\r\n -> "x op y"
bool DropHeader(std::string &package) // 为 报文 剥开报头
{
auto sep_line_pos = package.find(SEP_LINE);
if (sep_line_pos == std::string::npos)
return false;
int payload_len = stoi(package.substr(0, sep_line_pos)); // 有效载荷的长度
std::string message = package.substr(sep_line_pos + SEP_LINE_LEN, payload_len);
package = message;
return true;
}
class Request // 请求
{
public:
Request(int x = 0, char op = 0, int y = 0)
: _x(x), _y(y), _op(op)
{
}
bool serialization(std::string &out) // 序列化
{
// 序列化为 "x op y"的格式
std::string x_str = std::to_string(_x);
std::string y_str = std::to_string(_y);
out = x_str + SEP + _op + SEP + y_str;
return true;
}
bool deserialization(const std::string &in) // 反序列化
{
// x op y
// 第一个分隔符和第二个分隔符的迭代器位置
auto left_sep_pos = in.find(SEP);
auto right_sep_pos = in.rfind(SEP);
if (left_sep_pos == right_sep_pos)
return false;
_x = stoi(in.substr(0, left_sep_pos));
_op = in[left_sep_pos + SEP_LEN];
_y = stoi(in.substr(right_sep_pos + SEP_LEN));
return true;
}
public:
// x op y
int _x;
char _op;
int _y;
};
class Response // 响应
{
public:
Response(int exitcode = 0, int result = 0)
: _exitcode(exitcode), _result(result)
{
}
bool serialization(std::string &out) // 序列化
{
// exitcode result
std::string exitcode = std::to_string(_exitcode);
std::string result = std::to_string(_result);
out = exitcode + SEP + result;
return true;
}
bool deserialization(const std::string &in) // 反序列化
{
auto sep_pos = in.find(SEP);
if (sep_pos == std::string::npos)
return false;
_exitcode = stoi(in.substr(0, sep_pos));
_result = stoi(in.substr(sep_pos + SEP_LEN));
return true;
}
public:
int _exitcode; // 例如0表示成功,1表示除零,2表示....
int _result;
};
bool recvmessage(int sockfd, std::string &inbuffer, std::string &message) // 读取一个完整的报文
{
char buffer[1024] = {0};
while (true)
{
int n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
inbuffer += buffer;
// 判断此时inbuffer中有没有一个完整的报文(TCP是面向字节流,当读取时可能对方还没发送完)
auto sep_line_pos = inbuffer.find(SEP_LINE);
if (sep_line_pos == std::string::npos)
continue; // 继续读
std::string payload_len_str = inbuffer.substr(0, sep_line_pos);
int payload_len = stoi(payload_len_str);
int total_len = payload_len_str.size() + 2 * SEP_LINE_LEN + payload_len;
if (inbuffer.size() < total_len) // 还没读取到一个完整的报文,继续读
continue;
// 此时inbuffer中至少有一个完整的报文,提出来并在inbuffer中去掉
message = inbuffer.substr(0, total_len);
inbuffer.erase(0, total_len);
break;
}
else
return false;
}
return true;
}
服务端CalServer:
cpp
//CalServer.cpp:
#include <iostream>
#include "CalServer.hpp"
#include "log.hpp"
using namespace std;
using namespace Server;
void usage(string proc) // 使用手册
{
cout << GREEN << "\nUsage: \n\t" << ED << RED << proc << " [port]\n\n"
<< ED;
}
//根据req,计算出结果后填充到res中
void cal(const Request& req, Response& res)
{
switch(req._op)
{
case '+':
res._result = req._x + req._y;
break;
case '-':
res._result = req._x - req._y;
break;
case '*':
res._result = req._x * req._y;
break;
case '/':
if(req._y == 0)
res._exitcode = DEV_ZERO;
else
res._result = req._x / req._y;
break;
case '%':
if(req._y == 0)
res._exitcode = MOD_ZERO;
else
res._result = req._x % req._y;
break;
default:
res._exitcode = OP_ERR;
}
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);//字符串port转整数
CalServer server(port);
server.init();
server.start(cal);
return 0;
}
// CalServer.hpp:
#include <iostream>
#include <functional>
#include <string>
#include <cstring>
#include <cerrno>
#include <csignal>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"
#include "protocol.hpp"
namespace Server
{
// typedef function<void (std::string)> func_t;//回调函数类型
typedef std::function<void(const Request &req, Response &res)> func_t; // 将请求处理为响应
const int gbacklog = 5; // 全局的全连接队列长度
void handlerEnter(int sockfd, func_t func)
{
std::string inbuffer;
while (true)
{
// 读取一个完整的请求
std::string req_str;
if (!recvmessage(sockfd, inbuffer, req_str))
{
LogMessage(DEBUG, (char *)"客户端退出...");
break;
}
LogMessage(DEBUG, (char *)"请求(Request) 读取报文成功");
if (!DropHeader(req_str))
{
LogMessage(ERROR, (char *)"请求(Request) DropHeader失败,可能是报文格式不对!读取下一段报文...");
continue;
}
LogMessage(DEBUG, (char *)"请求(Request) 取出有效载荷成功");
// 反序列化
Request request;
if (!request.deserialization(req_str))
{
LogMessage(ERROR, (char *)"请求(Request) 有效载荷反序列化失败");
continue;
}
LogMessage(DEBUG, (char *)"请求(Request) 有效载荷反序列化成功");
#ifdef test // DEBUG时输出
std::cout << request._x << request._op << request._y << std::endl;
#endif
Response response;
func(request, response); // 计算器部分 x op y = result,得到结构化的响应;
// 序列化响应
std::string res_str;
response.serialization(res_str);
LogMessage(DEBUG, (char *)"响应(Response) 序列化成功");
// 添加报头
if (!AddHeader(res_str))
{
LogMessage(ERROR, (char *)"响应(Response) 为有效载荷添加报头失败,重新读取下一段报文...");
continue;
}
LogMessage(DEBUG, (char *)"响应(Response) 添加报头成功");
int n = send(sockfd, res_str.c_str(), res_str.size(), 0); //[TODO]有bug
if(n == -1)
{
LogMessage(ERROR, (char *)"响应(Response) send失败,重新读取下一段报文");
continue;
}
else
{
LogMessage(DEBUG, (char *)"响应(Response) 报文发送成功");
}
}
exit(0); //若break跳出,要关闭该孙子进程,否则会出函数继续accept等待连接
}
class CalServer
{
public:
CalServer(uint16_t port) : _port(port) {}
CalServer(std::string ip, uint16_t port)
: _ip(ip), _port(port)
{
}
void init()
{
// 创建监听套接字
_ListenSock = socket(AF_INET, SOCK_STREAM, 0);
if (_ListenSock == -1)
{
LogMessage(FATAL, (char *)"socket创建监听套接字失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
exit(SOCKET_ERR);
}
LogMessage(DEBUG, (char *)"socket创建监听套接字成功");
// bind绑定ip+port
struct sockaddr_in ServerAddr;
memset(&ServerAddr, 0, sizeof(ServerAddr));
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(_port);
if (inet_pton(AF_INET, _ip.c_str(), &ServerAddr.sin_addr) != 1)
{
LogMessage(FATAL, (char *)"点分十进制ip转网络序列失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
exit(INETPN_ERR);
}
LogMessage(DEBUG, (char *)"点分十进制ip转网络序列成功");
if (bind(_ListenSock, (struct sockaddr *)&ServerAddr, sizeof(ServerAddr)) != 0)
{
LogMessage(FATAL, (char *)"bind绑定失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
exit(BIND_ERR);
}
LogMessage(DEBUG, (char *)"bind绑定成功");
// 开启监听状态
if (listen(_ListenSock, gbacklog) != 0)
{
LogMessage(FATAL, (char *)"listen监听状态开启失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
exit(LISTEN_ERR);
}
LogMessage(DEBUG, (char *)"listen监听状态开启成功");
}
void start(func_t func)
{
signal(SIGCHLD, SIG_IGN); // OS自动回收子进程资源
while (true)
{
// 建立连接
struct sockaddr_in ClientAddr;
memset(&ClientAddr, 0, sizeof(ClientAddr));
socklen_t socklen = sizeof(ClientAddr);
int sockfd = accept(_ListenSock, (struct sockaddr *)&ClientAddr, &socklen);
if (sockfd == -1)
{
LogMessage(FATAL, (char *)"accept建立新连接失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
exit(ACCEPT_ERR);
}
LogMessage(DEBUG, (char *)"accept建立新连接成功,sockfd = %d", sockfd);
pid_t pid = fork();
if (pid == 0) // 子进程
{
close(_ListenSock); // 关掉无用文件描述符
if (fork() > 0) // 子进程本身退出
exit(0);
// 孙子进程,被OS领养,不等待也不会变成僵尸进程
handlerEnter(sockfd, func);
}
// close(sockfd); // 父进程关掉该文件描述符,防止文件描述符被用完
}
}
private:
int _ListenSock; // listen监听套接字
std::string _ip = "0.0.0.0"; // 默认接收所有ip
uint16_t _port; // 服务器端口号
// func_t _callback;
};
}
客户端:
cpp
//CalClient.cpp:
#include <iostream>
#include "CalClient.hpp"
using namespace std;
using namespace Client;
void usage(string proc) // 使用手册
{
cout << GREEN << "\nUsage:\n\t" ED RED << proc << " [ip] [port]\n\n" ED;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[2]);
CalClient client(argv[1], port);
client.init();
client.start();
return 0;
}
//CalClient.hpp:
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"
#include "protocol.hpp"
namespace Client
{
class CalClient
{
public:
CalClient(std::string ip, uint16_t port)
: _ip(ip), _port(port)
{
}
~CalClient()
{
}
void init()
{
// 创建套接字
_SocketFd = socket(AF_INET, SOCK_STREAM, 0);
if (_SocketFd == -1)
{
LogMessage(FATAL, (char *)"socket创建套接字失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
exit(SOCKET_ERR);
}
LogMessage(DEBUG, (char *)"socket创建套接字成功");
// 无需显式bind绑定
// 初始化服务端的addr
memset(&_ServerAddr, 0, sizeof(_ServerAddr));
_ServerAddr.sin_family = AF_INET;
_ServerAddr.sin_port = htons(_port);
if (inet_pton(AF_INET, _ip.c_str(), &_ServerAddr.sin_addr) != 1)
{
LogMessage(FATAL, (char *)"点分十进制ip转网络序列失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
exit(INETPN_ERR);
}
LogMessage(DEBUG, (char *)"点分十进制ip转网络序列成功");
}
void start()
{
// 向服务器申请建立连接
if (connect(_SocketFd, (struct sockaddr *)&_ServerAddr, sizeof(_ServerAddr)) != 0)
{
LogMessage(FATAL, (char *)"connect申请建立连接失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
std::cout << strerror(errno);
exit(CONNECT_ERR);
}
LogMessage(DEBUG, (char *)"connect申请建立连接成功");
// 收发消息
std::string message;
std::string inbuffer;
while (true)
{
// 写入
do
{
std::cout << RED "请输入表达式# " ED;
std::getline(std::cin, message);
} while (message.size() == 0); // 防止写入0字节数据
// 判断格式是否正确
if (!isFormat(message))
{
LogMessage(ERROR, (char *)"输入格式错误,请重新输入...");
continue;
}
// 将字符串格式化为Request
Request request = ParseMes(message);
// 序列化并添加报头(变为报文)后发送
std::string req_str;
request.serialization(req_str);
assert(AddHeader(req_str));
send(_SocketFd, req_str.c_str(), req_str.size(), 0); //[TODO]有Bug
// 读取服务端发过来的报文
if (!recvmessage(_SocketFd, inbuffer, message)) // 若还没有一个完整的报文,就回去继续读
continue;
LogMessage(DEBUG, (char*)"请求(Request) 读取报文成功");
// 到这里一定有至少一个完整的报文
if (!DropHeader(message))
{
LogMessage(ERROR, (char*)"请求(Request) DropHeader失败,请重新输入表达式...");
continue;
}
LogMessage(DEBUG, (char*)"请求(Request) 读取报文成功");
// exitcode result
Response response;
response.deserialization(message);
LogMessage(DEBUG, (char*)"请求(Request) 反序列化成功");
std::cout << "exitcode: " << response._exitcode << std::endl
<< "result: " << response._result << std::endl;
}
}
private:
Request ParseMes(const std::string message) // 将字符串解析为Request
{
int i = 0;
int status = 0;
std::string left, right; // 左操作数和右操作数
char op;
// 简易状态机
while (i < message.size())
{
switch (status)
{
case 0: // 左操作数部分
left += message[i++];
if (!isdigit(message[i]))
{
op = message[i++];
status = 1;
}
break;
case 1: // 右操作数部分
right += message[i++];
}
}
return Request(stoi(left), op, stoi(right));
}
bool isFormat(const std::string message) // 判断是否符合num op num的格式
{
int len = message.size();
if (len < 3)
return false; // 最小长度为3
int i = 0;
while (i < len)
{
if (isdigit(message[i]))
{
i++;
continue;
}
if (!isdigit(message[i]))
break;
}
if (i != 0 && i < len && !isdigit(message[i]))
i++;
else
return false; // 没有操作符
while (i < len)
{
if (isdigit(message[i++]))
continue;
else
return false; // 有操作数有非数字
}
return true;
}
private:
int _SocketFd; // 通信的套接字
std::string _ip; // 服务端的ip
uint16_t _port; // 服务端的端口号
struct sockaddr_in _ServerAddr; // 服务端的addr
};
}
该程序分为了三部分:
- 建立和断开连接部分
- 序列化/反序列化部分
- 协议定制部分
这正好对应着OSI模型的会话层、表示层、应用层 !而这三层合起来也就是TCP/IP的应用层。
如果要用OSI模型,就代表会话层/表示层也要交给OS内定,那么我们的建立连接和序列化/反序列化的部分都不能自己定制,所以OSI没有得以推广,而是用这三层都可以用户定制的TCP/IP模型
序列化/反序列化现成方案
上面的代码中,是自己实现的序列化/反序列化,麻烦且不好用
实际上已经有很多现成方案,例如:JSON、protobuf、xml
在本篇介绍JSON
JSON:
用下面命令安装jsoncpp库
bash
sudo yum install -y jsoncpp-devel
此时就可以看到jsoncpp的库文件和动态库文件了


在头文件处包含<json/jsoncpp.h>即可用
Json::Value对象用于存储Json的Key/Value键值对
要将结构化数据转换为Json字符串,需要用到Json::StreamWriterBuilder 对象和 Json::StreamWriter 对象
它们采用了建造者模式 ,前者用于配置后续生产出的StreamWriter对象的相关属性并负责生产StreamWriter对象
后者即用于将Json::Value对象转为字符串,相当于工厂与工人的关系
例如,下面代码定义了一个工厂:builder,将该工厂设置属性后,直接调用writeString接口,该接口用builder工厂生产一个临时工人(StreamWriter对象),并返回该工人所转化的string对象
cpp
Json::Value Data;
Data["left operand"] = _x;
Data["right operand"] = _y;
Data["oper"] = _op;
Json::StreamWriterBuilder builder; // 用于配置转换时的属性
builder["indentation"] = ""; // 设置缩进为空字符串,输出紧凑的单行 JSON
out = Json::writeString(builder, Data); // 返回Json的string格式
当反序列化时,需要用到CharReaderBuilder 对象和CharReader 对象,它们两者同样是建造者模式,前者用于配置后续生产的工人(CharReader)的相关属性,后者用于将字符串反序列化为Json::Value对象
例如,下面代码定义了一个工厂builder,没有需要配置的属性,该工厂的newCharReader可以在堆区中开辟一个CharReader对象,因此返回指针,这里用智能指针维护(这就是工厂生产出来的工人),后续由该工人负责解析字符串为Json::Value对象(调用parse接口)
cpp
Json::Value Data;
Json::CharReaderBuilder builder;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); // 用建造者生产一个读者对象
std::string errs; // 若reader.parse失败,需要向errs中写入错误信息
bool ok = reader->parse(in.c_str(), in.c_str() + in.size(), &Data, &errs);
if (ok) // 解析成功,进行反序列化
{
_x = Data["left operand"].asInt();
_y = Data["right operand"].asInt();
_op = Data["oper"].asInt();
}
else // 解析失败,打印失败信息
{
LogMessage(ERROR, &errs[0]); // &errs[0]表示char*类型的字符串
return false;
}
下面为用Json进行序列化/反序列化的protocol.hpp:
cpp
#pragma once
#include <iostream>
#include <memory>
#include <json/json.h>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include "log.hpp"
const char *SEP = " "; // 序列化时的分隔符
const int SEP_LEN = strlen(SEP); // 分隔符长度
const char *SEP_LINE = "\r\n"; // 序列化时的行分隔符
const int SEP_LINE_LEN = strlen(SEP_LINE); // 行分隔符长度
// const char *LINE_SEP = "\r\n"; // 报文结尾
//"x op y" -> "有效载荷大小"\r\n"x op y"\r\n
bool AddHeader(std::string &payload) // 为 有效载荷 添加报头
{
std::string message = std::to_string(payload.size());
message += SEP_LINE + payload + SEP_LINE;
payload = message;
return true;
}
//"有效载荷大小"\r\n"x op y"\r\n -> "x op y"
bool DropHeader(std::string &package) // 为 报文 剥开报头
{
auto sep_line_pos = package.find(SEP_LINE);
if (sep_line_pos == std::string::npos)
return false;
int payload_len = stoi(package.substr(0, sep_line_pos)); // 有效载荷的长度
std::string message = package.substr(sep_line_pos + SEP_LINE_LEN, payload_len);
package = message;
return true;
}
class Request // 请求
{
public:
Request(int x = 0, char op = 0, int y = 0)
: _x(x), _y(y), _op(op)
{
}
bool serialization(std::string &out) // 序列化
{
#ifdef MY_SERIALIZATION
// 序列化为 "x op y"的格式
std::string x_str = std::to_string(_x);
std::string y_str = std::to_string(_y);
out = x_str + SEP + _op + SEP + y_str;
return true;
#else // Json格式序列化
Json::Value Data;
Data["left operand"] = _x;
Data["right operand"] = _y;
Data["oper"] = _op;
Json::StreamWriterBuilder builder; // 用于配置转换时的属性
builder["indentation"] = ""; // 设置缩进为空字符串,输出紧凑的单行 JSON
out = Json::writeString(builder, Data); // 返回Json的string格式
return true;
#endif
}
bool deserialization(const std::string &in) // 反序列化
{
#ifdef MY_SERIALIZATION
// x op y
// 第一个分隔符和第二个分隔符的迭代器位置
auto left_sep_pos = in.find(SEP);
auto right_sep_pos = in.rfind(SEP);
if (left_sep_pos == right_sep_pos)
return false;
_x = stoi(in.substr(0, left_sep_pos));
_op = in[left_sep_pos + SEP_LEN];
_y = stoi(in.substr(right_sep_pos + SEP_LEN));
return true;
#else // Json格式反序列化
Json::Value Data;
Json::CharReaderBuilder builder;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); // 用建造者生产一个读者对象
std::string errs; // 若reader.parse失败,需要向errs中写入错误信息
bool ok = reader->parse(in.c_str(), in.c_str() + in.size(), &Data, &errs);
if (ok) // 解析成功,进行反序列化
{
_x = Data["left operand"].asInt();
_y = Data["right operand"].asInt();
_op = Data["oper"].asInt();
}
else // 解析失败,打印失败信息
{
LogMessage(ERROR, &errs[0]); // &errs[0]表示char*类型的字符串
return false;
}
return true;
#endif
}
public:
// x op y
int _x;
char _op;
int _y;
};
class Response // 响应
{
public:
Response(int exitcode = 0, int result = 0)
: _exitcode(exitcode), _result(result)
{
}
bool serialization(std::string &out) // 序列化
{
#ifdef MY_SERIALIZATION
// exitcode result
std::string exitcode = std::to_string(_exitcode);
std::string result = std::to_string(_result);
out = exitcode + SEP + result;
return true;
#else // Json格式序列化
Json::Value Data;
Data["exitcode"] = _exitcode;
Data["result"] = _result;
Json::StreamWriterBuilder writer; // 用于将Json::Value转为Json格式字符串
writer["indentation"] = ""; // 设置缩进为空字符串,输出紧凑的单行 JSON
out = Json::writeString(writer, Data); // 返回Json的string格式
return true;
#endif
}
bool deserialization(const std::string &in) // 反序列化
{
#ifdef MY_SERIALIZATION
auto sep_pos = in.find(SEP);
if (sep_pos == std::string::npos)
return false;
_exitcode = stoi(in.substr(0, sep_pos));
_result = stoi(in.substr(sep_pos + SEP_LEN));
return true;
#else // Json格式反序列化
Json::Value Data;
Json::CharReaderBuilder builder;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); // 用建造者生产一个读者对象
std::string errs; // 若reader.parse失败,需要向errs中写入错误信息
bool ok = reader->parse(in.c_str(), in.c_str() + in.size(), &Data, &errs);
if (ok) // 解析成功,进行反序列化
{
_exitcode = Data["exitcode"].asInt();
_result = Data["result"].asInt();
}
else // 解析失败,打印失败信息
{
LogMessage(ERROR, &errs[0]); // &errs[0]表示char*类型的字符串
return false;
}
return true;
#endif
}
public:
int _exitcode; // 例如0表示成功,1表示除零,2表示....
int _result;
};
bool recvmessage(int sockfd, std::string &inbuffer, std::string &message) // 读取一个完整的报文
{
char buffer[1024] = {0};
while (true)
{
int n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
inbuffer += buffer;
// 判断此时inbuffer中有没有一个完整的报文(TCP是面向字节流,当读取时可能对方还没发送完)
auto sep_line_pos = inbuffer.find(SEP_LINE);
if (sep_line_pos == std::string::npos)
continue; // 继续读
std::string payload_len_str = inbuffer.substr(0, sep_line_pos);
int payload_len = stoi(payload_len_str);
int total_len = payload_len_str.size() + 2 * SEP_LINE_LEN + payload_len;
if (inbuffer.size() < total_len) // 还没读取到一个完整的报文,继续读
continue;
// 此时inbuffer中至少有一个完整的报文,提出来并在inbuffer中去掉
message = inbuffer.substr(0, total_len);
inbuffer.erase(0, total_len);
break;
}
else
return false;
}
return true;
}