应⽤层协议
我们程序员写的⼀个个解决我们实际问题, 满⾜我们⽇常需求的⽹络程序, 都是在应⽤层.
应⽤层协议可以概括为计算机之间通信的 "通用语言规则",核心是让不同设备的应用程序能看懂彼此传的数据、知道怎么交互。
tcp是面向字节流的的特点,就代表了它传输数据并不是一股脑将数据全部传输过去,而是一段一段的,故我们上层对数据进行提取时可能就会得不到完整的数据。也就是粘包(多个包粘一起)和半包(一个包只传了一部分)
下面我简单写了个应用层协议解决该问题:
EnCode方法明确约定了数据包结构:
长度字符串 + sep + JSON数据 + sep(比如:127\n\r{json}\n\r)
cpp
// 添加长度报头字段
// 127\n\r{json}\n\r
bool EnCode(std::string &out_buffer)
{
if (out_buffer.size() == 0)
return false;
std::string result = std::to_string(out_buffer.size()) + sep + out_buffer + sep;
out_buffer = result;
return true;
}
而Decode则可以先通过sep找到长度字段,再通过长度计算完整包的总长度,解决 TCP 字节流无边界问题。
cpp
// 解析长度报头字段!
bool DeCode(std::string &package, std::string *content)
{
std::string length_str;
int pos = package.find(sep);
if (pos == std::string::npos)
return false;
length_str = package.substr(0, pos);//得出结构化数据的长度
int length = std::stoi(length_str);
int full_length = length + 2 * sep.size() + length_str.size();//该段结构数据的全部长度
if (package.size() < full_length)
{
return false;
}
*content = package.substr(pos + sep.size(), length);
package.erase(0, full_length); // 127\n\r{json}\n\r127\n\r{json}\n\r处理这种情况
return true;
}
UDP虽然是面向数据报的,但它一次能够传输的数据也是有上限的,也会面临数据截断的问题。UDP的解决方案有:应用层主动预分片,和各种成熟的上层协议;想了解的可以去了解下。
序列化和反序列化
序列化:
- 把代码里有结构、能直接用的数据,转换成字节流 的过程。
反序列化:
- 把收到字节流 ,还原成代码里能直接操作的结构化数据的过程。
为什么要有序列化 / 反序列化
在计算机的底层只认字节,并不认结构化数据。
倘若传入的是一段结构化数据,应用层或者服务器要怎么读取它呢!
你可能会说用相同的结构体去接受就好了。但我们要知道在不同的OS下,结构体的内存布局是不一样的。首先就是 Windows和Linux下内存对齐规则不同。并且32位系统下int是4字节,但在64位系统下的int可能就是8字节,等等...
我们需要知道,直接传结构体二进制是 "绑死内存布局",序列化是 "只传数据本身"。
故序列化为了让数据能传 ,反序列化为了让数据能用。
为了解决序列化 / 反序列化问题,C++直接引入了Jsoncpp库,jsoncpp 本质是帮我们做了 3 件关键事:
- 屏蔽内存布局差异
- 简化序列化 / 反序列化逻辑
- 处理 JSON 语法细节,避免手写的语法错误。
Jsoncpp
特性:
- 简单易⽤:Jsoncpp 提供了直观的 API,使得处理 JSON 数据变得简单。
⾼性能:Jsoncpp 的性能经过优化,能够⾼效地处理⼤量 JSON 数据。
- 全⾯⽀持:⽀持 JSON 标准中的所有数据类型,包括对象、数组、字符串、数字、布尔值和null。
- 错误处理:在解析 JSON 数据时,Jsoncpp 提供了详细的错误信息和位置,⽅便开发者调试。
Jsoncpp的安装
cpp
ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel
Jsoncpp提供了多种⽅式进⾏序列化:
序列化
Json::Value
使⽤ Json::Value 的 toStyledString ⽅法:
- 优点:将 Json::Value 对象直接转换为格式化的JSON字符串。
cpp
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";
std::string s = root.toStyledString();
std::cout << s << std::endl;
return 0;
}
./test.exe//封装后效果
{
"name" : "joe",
"sex" : "男"
}
Json::StreamWriter
优点:提供了更多的定制选项,如缩进、换⾏符等。
cpp
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";
Json::StreamWriterBuilder wbuilder; // StreamWriter的⼯⼚
std::unique_ptr<Json::StreamWriter>
writer(wbuilder.newStreamWriter());
std::stringstream ss;
writer->write(root, &ss);
std::cout << ss.str() << std::endl;
return 0;
}
./test.exe
{
"name" : "joe",
"sex" : "男"
}
Json::FastWriter
优点:⽐ StyledWriter 更快,因为它不添加额外的空格和换⾏符。
cpp
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";
// Json::FastWriter writer;
Json::FastWriter writer;
std::string s = writer.write(root);
std::cout << s << std::endl;
return 0;
}
./test.exe
{"name" : "joe", "sex" : "男"}
反序列化
Json::Reader
优点:提供详细的错误信息和位置,⽅便调试。
cpp
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
// JSON 字符串
std::string json_string = "{\"name\":\"张三\", \"age\":30,
\"city\":\"北京\"}";
// 解析 JSON 字符串
Json::Reader reader;
Json::Value root;
// 从字符串中读取 JSON 数据
bool parsingSuccessful = reader.parse(json_string, root);
if (!parsingSuccessful)
{
// 解析失败,输出错误信息
std::cout << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;
return 1;
}
// 访问 JSON 数据
std::string name = root["name"].asString();
int age = root["age"].asInt();
std::string city = root["city"].asString();
// 输出结果
std::cout << "Name: " << name << std::endl;
std::cout << "Age: " << age << std::endl;
std::cout << "City: " << city << std::endl;
return 0;
}
./test.exe
Name: 张三
Age: 30
City: 北京
网络计算器完整代码
先将日志代码倒入:
Mutex.hpp
cpp
#pragma once
#include<iostream>
#include<pthread.h>
namespace LockModule
{
class Mutex
{
Mutex(const Mutex& m) = delete;//互斥锁是不可拷贝的资源
Mutex& operator =(const Mutex& m) = delete;//一个互斥锁对象对应操作系统内核中的一个锁实例,拷贝它意味着创建 "两个对象对应同一个内核锁"
public:
Mutex()
{
int n = pthread_mutex_init(&_mutex, nullptr);
(void)n;
}
void lock()
{
int n = pthread_mutex_lock(&_mutex);
(void)n;
}
void unlock()
{
int n = pthread_mutex_unlock(&_mutex);
(void)n;
}
pthread_mutex_t* mutexptr()
{
return &_mutex;
}
~Mutex()
{
int n = pthread_mutex_destroy(&_mutex);
(void)n;
}
private:
pthread_mutex_t _mutex;
};
//RAII风格,自动析构,无需手动释放资源
class LockGuard
{
public:
LockGuard(Mutex& mutex):_mutex(mutex)
{
_mutex.lock();
}
~LockGuard()
{
_mutex.unlock();
}
private:
Mutex& _mutex;
};
}
Log.hpp
cpp
#pragma once
#include <sstream> //stringstream
#include <unistd.h>
#include <filesystem>
#include "Mutex.hpp"
#include <iostream>
#include <string>
#include <time.h>
#include <fstream>
#include <memory> //智能指针
namespace LogModule
{
using namespace LockModule;
std::string CurrentTime() // 获取时间
{
time_t time = ::time(nullptr);
struct tm st;
localtime_r(&time, &st);
char buff[1024] = {0};
snprintf(buff, sizeof(buff), "%4d-%02d-%02d %02d:%02d:%02d",
st.tm_year + 1900,
st.tm_mon + 1,
st.tm_mday,
st.tm_hour,
st.tm_min,
st.tm_sec);
return buff;
}
enum class LogLevel
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
// 将日志等级转化成字符串
std::string leveltostring(LogLevel level)
{
switch (level)
{
case LogLevel::DEBUG:
return "DEBUG";
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
default:
return "None";
}
}
// 刷新策略(基类)
class LogStrategy
{
public:
virtual ~LogStrategy() = default;
virtual void SyncLog(std::string &message) = 0;
};
// 控制台策略
class ConsoleLogStrategy : public LogStrategy //(继承LogStrategy)
{
public:
ConsoleLogStrategy() {}
void SyncLog(std::string &message)
{
LockGuard lockguard(mutex);
std::cout << message << std::endl;
}
~ConsoleLogStrategy() {}
private:
Mutex mutex;
};
const std::string defaultlpathname = "./log";
const std::string defaultlfilename = "/log.txt";//这里需注意加上/符号
// 文件策略
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(std::string lpathname = defaultlpathname, std::string lfilename = defaultlfilename)
: _lpathname(lpathname),
_lfilename(lfilename)
{
LockGuard lockguard(mutex);
if (std::filesystem::exists(lpathname))
{
return;
}
try
{
std::filesystem::create_directories(lpathname); // 没有该目录就尝试创造,失败则抛出异常
}
catch (std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << "\n";
}
}
void SyncLog(std::string &message)
{
LockGuard lockguard(mutex);
std::string log = _lpathname + _lfilename;
std::ofstream out(log, std::ios::app); // 以追加方式打开
if (!out.is_open())
{
return;
}
out << message << "\n";
out.close(); //// 强制刷新缓冲区到文件,并释放资源
}
~FileLogStrategy() {}
private:
std::string _lpathname;
std::string _lfilename;
Mutex mutex;
};
// 日志类: 构建日志字符串, 根据策略,进行刷新
class Logger
{
public:
Logger()
{
_strategy = std::make_shared<ConsoleLogStrategy>(); // 默认控制台策略
}
void EnableConsoleLog()
{
_strategy = std::make_shared<ConsoleLogStrategy>(); // 手动控制台策略
}
void EnableFileLog()
{
_strategy = std::make_shared<FileLogStrategy>(); // 手动文件(磁盘)策略
}
//[可读性很好的时间] [⽇志等级] [进程pid] [打印对应⽇志的⽂件名][⾏号] - 消息内容,⽀持可变参数
class LogMessage
{
public:
LogMessage(LogLevel level, const std::string &filename, int line, Logger& logger)
: _level(level),
_filename(filename),
_line(line),
_pid(getpid()),
_currtime(CurrentTime()),
_logger(logger)
{
std::stringstream ssbuffer;
ssbuffer << "[" << _currtime << "] "
<< "[" << leveltostring(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _filename << "] "
<< "[" << _line << "] - ";
_loginfo = ssbuffer.str();
}
template<class T>
LogMessage& operator<<(const T& info)
{
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if(_logger._strategy)
{
_logger._strategy->SyncLog(_loginfo);
}
}
private:
std::string _currtime; // 该日志时间
pid_t _pid; // 该进程pid
LogLevel _level; // 日志等级
const std::string &_filename; // 源文件名称
int _line; // 消息的行号
std::string _loginfo; // 一条完整的日志记录
Logger& _logger; // 负责根据不同的策略进行刷新
};
LogMessage operator()(LogLevel level, const std::string &filename, int line)
{
return LogMessage(level, filename, line, *this);
}
~Logger() {}
private:
std::shared_ptr<LogStrategy> _strategy; // 刷新方案指针
};
Logger logger;
#define LOG(level) logger(level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
}
序列化反序列化与应用层协议:
Protocol.hpp
cpp
#pragma once
#include <iostream>
#include <jsoncpp/json/json.h>
#include <string>
const std::string sep = "\n\r";
// 添加长度报头字段
// 127\n\r{json}\n\r
bool EnCode(std::string &out_buffer)
{
if (out_buffer.size() == 0)
return false;
std::string result = std::to_string(out_buffer.size()) + sep + out_buffer + sep;
out_buffer = result;
return true;
}
// 解析长度报头字段!
bool DeCode(std::string &package, std::string *content)
{
std::string length_str;
int pos = package.find(sep);
if (pos == std::string::npos)
return false;
length_str = package.substr(0, pos);//得出结构化数据的长度
int length = std::stoi(length_str);
int full_length = length + 2 * sep.size() + length_str.size();//该段结构数据的全部长度
if (package.size() < full_length)
{
return false;
}
*content = package.substr(pos + sep.size(), length);
package.erase(0, full_length); // 127\n\r{json}\n\r127\n\r{json}\n\r处理这种情况
return true;
}
class Request
{
public:
Request() : _x(0), _y(0), _oper(0)
{
}
Request(int x, int y, char oper)
: _x(x),
_y(y),
_oper(oper)
{
}
bool Serialize(std::string &out_string)
{
Json::Value root;
root["_x"] = _x;
root["_y"] = _y;
root["_oper"] = _oper;
Json::StreamWriterBuilder wb;
std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());
std::stringstream ss;
w->write(root, &ss);
out_string = ss.str();
return true;
}
bool Deserialize(std::string &in_string)
{
Json::Value root;
Json::Reader reader;
std::cout<<"原始数据:"<<in_string<<std::endl;
bool parsingSuccessful = reader.parse(in_string, root);
if (!parsingSuccessful)
{
std::cout << "Failed to parse RequestJSON: " << reader.getFormattedErrorMessages() << std::endl;
return false;
}
_x = root["_x"].asInt();
_y = root["_y"].asInt();
_oper = root["_oper"].asInt();
return true;
}
~Request()
{
}
int X()const { return _x; }; // 被const修饰的对象只能调用被const修饰的成员函数
int Y()const { return _y; };
char OPER()const {return _oper;};
private:
int _x;
int _y;
char _oper;
};
class Response
{
public:
Response() : _result(0)
{
}
Response(int result, int reval = 0) : _result(result), _reval(reval)
{
}
bool Serialize(std::string &out_string)
{
Json::Value root;
root["_result"] = _result;
root["_reval"] = _reval;
Json::StreamWriterBuilder wb;
std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());
std::stringstream ss;
w->write(root, &ss);
out_string = ss.str();
return true;
}
bool Deserialize(std::string &in_string)
{
Json::Value root;
Json::Reader reader;
bool parsingSuccessful = reader.parse(in_string, root);
if (!parsingSuccessful)
{
std::cout << "Failed to parse ResponseJSON: " << reader.getFormattedErrorMessages() << std::endl;
return false;
}
_result = root["_result"].asInt();
_reval = root["_reval"].asInt();
return true;
}
int Result() const { return _result; }
int REVAL() const { return _reval; }
~Response()
{
}
private:
int _result;
int _reval;
};
Calculator.hpp
cpp
#pragma once
#include "Protocol.hpp"
class Calculator
{
public:
Calculator() {}
Response Execute(const Request &reque)
{
int x = reque.X();
int y = reque.Y();
char oper = reque.OPER();
Response resp;
switch (oper)
{
case '+':
resp = Response(x + y);
break;
case '-':
resp = Response(x - y);
break;
case '*':
resp = Response(x * y);
break;
case '/':
{
if(y == 0)
resp = Response(0, 1);
else
resp = Response(x / y);
}
break;
case '%':
{
if(y == 0)
resp = Response(0, 2);
else
resp = Response(x % y);
}
break;
default:
resp = Response(0, 3);
break;
}
return resp;
}
~Calculator() {}
};
TcpServer.hpp
cpp
#pragma once
#include <unistd.h>
#include <iostream>
#include <string>
#include <unistd.h>
#include <strings.h>
#include <cstring>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include "Log.hpp"
using namespace ThreadPoolModule;
using namespace LogModule;
#define BACKLOG 8
const static int gsockfd = -1;
const static std::string gip = "0.0.0.0"; // 表示所有ip
const static uint16_t gport = 8888;
using handler_t = std::function<std::string(std::string command)>;
#define DIE(num) \
do \
{ \
exit(num); \
} while (0)
class TcpServer
{
struct TcpData
{
int sockfd;
TcpServer *self;
};
public:
TcpServer(handler_t handler, uint16_t port = gport, std::string ip = gip)
: _handler(handler),
_listensockfd(gsockfd),
_port(gport),
_ip(ip),
_isrunning(false)
{
}
void Init()
{
_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::FATAL) << "socket: " << strerror(errno);
DIE(1);
}
LOG(LogLevel::INFO) << "socket success, sockfd: " << _listensockfd;
// 允许端口复用,当TCP服务器崩溃或重启时,端口不会立即释放,而是会处于TIME_WAIT状态(默认约2分钟)
int opt = 1;
setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = ::htons(_port);
local.sin_addr.s_addr = ::inet_addr(_ip.c_str());
int n = ::bind(_listensockfd, (struct sockaddr *)&local, sizeof(local));
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind: " << strerror(errno);
DIE(2);
}
LOG(LogLevel::INFO) << "bind success";
// 设置TCP服务器开始监听客户端连接
n = ::listen(_listensockfd, BACKLOG);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen: " << strerror(errno);
DIE(3);
}
LOG(LogLevel::INFO) << "listen success";
}
void HandlerRequest(int sockfd)
{
LOG(LogLevel::INFO) << "HandlerRequest...";
// 接收客户端数据
char rebuffer[1024];
while (true)
{
int n = ::recv(sockfd, rebuffer, sizeof(rebuffer) - 1, 0);
if (n > 0)
{
rebuffer[n] = 0;
LOG(LogLevel::INFO) << rebuffer;
std::string message = _handler(rebuffer);
::send(sockfd, message.c_str(), message.size(), 0);
}
else if (n == 0)
{
LOG(LogLevel::INFO) << "client close...";
break;
}
else
{
LOG(LogLevel::ERROR) << "recv: " << strerror(errno);
break;
}
}
}
static void *ThreadEntry(void *args)
{
pthread_detach(pthread_self());//避免主线程阻塞等待,故将线程分离
struct TcpData * Data = (struct TcpData* )args;
Data->self->HandlerRequest(Data->sockfd);
return nullptr;
}
void Start()
{
_isrunning = true;
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer); // 必须设定
LOG(LogLevel::DEBUG) << "accept ing ...";
// tcp服务端客户端是 "一对一" 的专属连接 udp服务端↔多个客户端是 "一对多" 的无关联通信
// "一对一" 的专属连接: 每新增一个客户端进行来链接tcp就会监听到并连接生成新的sockfd
int sockfd = ::accept(_listensockfd, (struct sockaddr *)&peer, &len);
if (sockfd < 0)
{
LOG(LogLevel::ERROR) << "accept: " << strerror(errno);
continue;
}
LOG(LogLevel::INFO) << "accept success, sockfd: " << sockfd;
std::string ip = inet_ntoa(peer.sin_addr);
uint16_t port = ntohs(peer.sin_port);
LOG(LogLevel::INFO) << "client ip: " << ip << ", port: " << port;
//多进程
pid_t id = fork();
if(id == 0)
{
::close(_listensockfd);
if(fork() == 0)
{
//由孙子进程来执行操作,让父进程直接回收子进程
//孙子进程就会直接由PID 1 进程接管,进行资源回收
HandlerRequest(sockfd);
exit(0);
}
exit(0);
}
::close(sockfd);
pid_t mid = waitpid(id, nullptr, 0);
if(mid < 0)
{
LOG(LogLevel::WARNING) << "waitpid error";
}
}
_isrunning = false;
}
~TcpServer()
{
if (_listensockfd > gsockfd)
::close(_listensockfd);
}
private:
handler_t _handler;
int _listensockfd;
uint16_t _port; // 服务器未来的端口号
std::string _ip; // 服务器所对应的IP
bool _isrunning; // 服务器运行状态
};
TcpServerMain.cc
cpp
#include <memory>
#include <iostream>
#include <string>
#include <arpa/inet.h>
#include "Calculator.hpp"
#include "TcpServer.hpp"
#include <functional>
#include "Protocol.hpp"
#include "Log.hpp"
using namespace LogModule;
// package一定会有完整的报文吗??不一定
// 不完整->继续读
// 完整-> 提取 -> 反序列化 -> Request -> 计算模块,进行处理
using calfun = std::function<Response(Request &)>;
class Phrase
{
public:
Phrase(calfun cal) : _cal(cal)
{
}
std::string Entry(std::string &package)
{
std::string content;
std::string ret;
// 解析长度报头字段!
while (DeCode(package, &content))
{
LOG(LogLevel::ERROR) << "content: \n"
<< content;
if (content.empty())
{
break;
}
Request res;
// 反序列化
if (!res.Deserialize(content))
{
break;
}
LOG(LogLevel::ERROR) << "res反序列化: ";
// 计算
Response resp = _cal(res);
// 序列化
resp.Serialize(content);
LOG(LogLevel::ERROR) << "resp序列化: ";
// 添加长度报头字段
EnCode(content);
LOG(LogLevel::ERROR) << "EnCode: 添加长度报头字段";
ret += content;
}
return ret;
}
private:
calfun _cal;
};
int main()
{
Calculator mycal;
std::unique_ptr<Phrase> phrase = std::make_unique<Phrase>(
[&mycal](const Request &reque)
{ return mycal.Execute(reque); });
std::unique_ptr<TcpServer> svr = std::make_unique<TcpServer>(
[&phrase](std::string package)
{ return phrase->Entry(package); });
svr->Init();
svr->Start();
return 0;
}
TcpClientMain.cc
cpp
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include "Protocol.hpp"
void Usage(std::string proc)
{
std::cout << "Usage: " << proc << " port[1024]"
<< std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(0);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
struct sockaddr_in server;
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
socklen_t len = sizeof(server);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
std::cout << "socker error" << std::endl;
return 1;
}
//客户端发起连接
int n = ::connect(sockfd, (struct sockaddr*)&server, len);
if(n < 0)
{
std::cout<<"connect fail..."<<std::endl;
return 3;
}
std::string message;
while (true)
{
int x, y;
char oper;
std::cout << "input x: ";
std::cin >> x;
std::cout << "input y: ";
std::cin >> y;
std::cout << "input oper: ";
std::cin >> oper;
//序列化
Request reque(x, y, oper);
reque.Serialize(message);
//添加长度报头字段
EnCode(message);
int m = ::send(sockfd, message.c_str(), message.size(), 0);
char inbuffer[1024];
if(m > 0)
{
std::string package;
int n = ::recv(sockfd, inbuffer, sizeof(inbuffer) - 1, 0);
if(n > 0)
{
inbuffer[n] = 0;
package = inbuffer;
std::string content;
DeCode(package, &content);
Response resp;
resp.Deserialize(content);
std::cout<< resp.Result() <<"[" << resp.REVAL() <<"]"<<std::endl;
}
else
break;
}
else
break;
}
close(sockfd);
return 0;
}