大家好,我们上次学完了有关TCP的socket编程,今天我们来继续学习,今天要学习的内容是应用层自定义协议与序列化 ,那么我们开始今天的学习:
目录
[1. 应用层](#1. 应用层)
[1.1 再谈 "协议"](#1.1 再谈 "协议")
[1.2 网络版计算器](#1.2 网络版计算器)
[1.3 序列化 和 反序列化](#1.3 序列化 和 反序列化)
[2. 重新理解 read、write、recv、send 和 tcp 为什么支持全双工](#2. 重新理解 read、write、recv、send 和 tcp 为什么支持全双工)
[3. 网络计算器实现](#3. 网络计算器实现)
[Inet Addr.hpp:](#Inet Addr.hpp:)
[4. Jsoncpp](#4. Jsoncpp)
[4.1 特性](#4.1 特性)
[4.2 安装](#4.2 安装)
[4.3 序列化](#4.3 序列化)
[4.3.1 使用 Json::Value 的 toStyledString 方法:](#4.3.1 使用 Json::Value 的 toStyledString 方法:)
[4.3.2 使用 Json::StreamWriter:](#4.3.2 使用 Json::StreamWriter:)
[4.3.3 使用 Json::FastWriter:](#4.3.3 使用 Json::FastWriter:)
[4.4 反序列化](#4.4 反序列化)
[4.4.1 使用 Json::Reader:](#4.4.1 使用 Json::Reader:)
[4.5 总结](#4.5 总结)
[5. Json::Value](#5. Json::Value)
[5.1 构造函数](#5.1 构造函数)
[5.2 访问元素](#5.2 访问元素)
[5.3 类型检查](#5.3 类型检查)
[5.4 赋值和类型转换](#5.4 赋值和类型转换)
[5.5 数组和对象操作](#5.5 数组和对象操作)
[6. 网络计算器实现](#6. 网络计算器实现)
应用层自定义协议与序列化
1. 应用层
我们程序员写的一个个解决我们实际问题 , 满足我们日常需求的网络程序 , 都是在应用层.
1.1 再谈 "协议"
协议是一种 " 约定 ". socket api 的接口 , 在读写数据时 , 都是按 " 字符串 " 的方式来发送接收的. 如果我们要传输一些 " 结构化的数据 " 怎么办呢 ?
比如我们想传输图片,音乐,视频等等,肯定不能在以"字符串"的形式来传输,所以需要我们来自定义协议
其实,协议就是双方约定好的结构化的数据
1.2 网络版计算器
例如 , 我们需要实现一个服务器版的加法器 . 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算 , 最后再把结果返回给客户端
约定方案一 :
- 客户端发送一个形如 "1+1" 的字符串 ;
- 这个字符串中有两个操作数 , 都是整形 ;
- 两个数字之间会有一个字符是运算符 , 运算符只能是 + - * / ;
- 数字和运算符之间没有空格 ;
约定方案二 :- 定义结构体来表示我们需要交互的信息 ;
- 发送数据时将这个结构体按照一个规则转换成字符串 , 接收到数据的时候再按照相同的规则把字符串转化回结构体;
- 这个过程叫做 " 序列化 " 和 " 反序列化 "
1.3 序列化 和 反序列化

无论我们采用方案一 , 还是方案二 , 还是其他的方案 , 只要保证 , 一端发送时构造的数据 , 在另一端能够正确的进行解析, 就是 ok 的 . 这种约定 , 就是 应用层协议
那为什么会有序列化和反序列化呢?
主要是保持报文的完整性,方便网络传输
所以为了深刻理解协议,我们来自定义实现一下协议的过程
我们采用方案 2 ,我们也要体现协议定制的细节
我们要引入序列化和反序列化,只不过我们课堂直接采用现成的方案 -- jsoncpp库
我们要对 socket 进行字节流的读取处理
当然在正式开始实现之前,我们再来学习一些知识:
2. 重新理解 read、write、recv、send 和 tcp 为什么支持全双工

在TCP协议中,我们可以使用read,write,recv,send来进行数据的发送和接收,我们在发送数据时,我们的操作并不是直接将数据发送到对方主机中,而是发送给TCP的发送缓冲区,而什么时候发送,发送多少数据,发送时出现错误怎么办,都由TCP协议来决定,接受时同理,这也就说明了TCP可以全双工的原因,也是为什么一个 tcp sockfd 读写都可以进行的原因
同时也就有了新的问题,由于TCP协议是面向字节流的,所以我们每次发送和读取的数据大小是不确定的,有可能我们的数据会被分几次发送和接受,我们实现的时候主要实现读取时的分段问题。
3. 网络计算器实现
首先我们先对socket套接字进行封装:
Common.hpp:
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <cstring>
#include <unistd.h>
enum EXITCODE
{
OK = 0,
USAGE_ERR,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR,
FORK_ERR
};
class NoCopy
{
public:
NoCopy() {}
~NoCopy() {}
NoCopy(const NoCopy &) = delete;
const NoCopy &operator=(const NoCopy &) = delete;
};
#define CONV(addr) ((struct sockaddr *)&addr)
Inet Addr.hpp:
#pragma once
#include "Common.hpp"
class InetAddr
{
public:
InetAddr() {}
InetAddr(struct sockaddr_in &addr)
{
SetAddr(addr);
}
InetAddr(const std::string &ip, uint16_t port)
: _port(port), _ip(ip)
{
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_port = ntohs(_port);
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
}
InetAddr(uint16_t port)
: _port(port)
{
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_port = ntohs(_port);
_addr.sin_addr.s_addr = INADDR_ANY;
}
void SetAddr(struct sockaddr_in &addr)
{
_addr = addr;
_port = htons(addr.sin_port);
char buffer[64];
inet_ntop(AF_INET, &addr.sin_addr, buffer, sizeof(buffer));
_ip = buffer;
}
uint16_t Port() { return _port; }
std::string &Ip() { return _ip; }
const struct sockaddr_in &NetAddr() { return _addr; }
const struct sockaddr *NetAddrPtr() { return CONV(_addr); }
socklen_t NetAddrLen() { return sizeof(_addr); }
bool operator==(const InetAddr &addr) { return addr._port == _port && addr._ip == _ip; }
std::string ToString() { return _ip + ":" + std::to_string(_port); }
private:
struct sockaddr_in _addr;
uint16_t _port;
std::string _ip;
};
Socket.hpp:
#pragma once
#include "Common.hpp"
#include "InetAddr.hpp"
#include "Log.hpp"
#include <cstdlib>
namespace SocketModule
{
using namespace LogModule;
const static int gbacklog = 16;
class Socket
{
public:
virtual ~Socket() {}
virtual void SocketOrDie() = 0;
virtual void BindOrDie(uint16_t port) = 0;
virtual void ListenOrDie(int backlog) = 0;
virtual std::shared_ptr<Socket> Accept(InetAddr *client) = 0;
virtual void Close() = 0;
virtual int Recv(std::string *out) = 0;
virtual int Send(const std::string &message) = 0;
virtual int Connect(const std::string server_ip, uint16_t server_port) = 0;
public:
void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog)
{
SocketOrDie();
BindOrDie(port);
ListenOrDie(backlog);
}
void BuildTcpClientSocketMethod()
{
SocketOrDie();
}
};
const static int defultsockfd = -1;
class TcpSocket : public Socket
{
public:
TcpSocket()
: _sockfd(defultsockfd)
{
}
TcpSocket(int fd)
: _sockfd(fd)
{
}
~TcpSocket()
{
}
void SocketOrDie() override
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success";
}
void BindOrDie(uint16_t port) override
{
InetAddr localaddr(port);
int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success";
}
void ListenOrDie(int backlog) override
{
int n = ::listen(_sockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success";
}
std::shared_ptr<Socket> Accept(InetAddr *client) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int fd = ::accept(_sockfd, CONV(peer), &len);
if (fd < 0)
{
LOG(LogLevel::WARNING) << "accept warning ...";
return nullptr; // TODO
}
client->SetAddr(peer);
return std::make_shared<TcpSocket>(fd);
}
int Recv(std::string *out) override
{
char buffer[1024];
ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
*out += buffer;
}
return n;
}
int Send(const std::string &message) override
{
return send(_sockfd, message.c_str(), message.size(), 0);
}
void Close() override
{
if (_sockfd > 0)
close(_sockfd);
}
int Connect(const std::string server_ip, uint16_t server_port) override
{
InetAddr server(server_ip, server_port);
return connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen());
}
private:
int _sockfd;
};
}
随后,我们要实现关于计算器的协议:
Protocol.hpp:
#pragma once
#include "Socket.hpp"
#include <jsoncpp/json/json.h>
#include <memory>
#include <functional>
using namespace SocketModule;
class Request
{
public:
Request()
{
}
Request(int x, int y, char oper)
: _x(x), _y(y), _oper(oper)
{
}
std::string Serialize()
{
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["oper"] = _oper;
Json::FastWriter writer;
std::string s = writer.write(root);
return s;
}
bool Deserialize(std::string &in)
{
Json::Value root;
Json::Reader reader;
bool ok = reader.parse(in, root);
if (ok)
{
_x = root["x"].asInt();
_y = root["y"].asInt();
_oper = root["oper"].asInt();
}
return ok;
}
~Request()
{
}
int X() { return _x; }
int Y() { return _y; }
char Oper() { return _oper; }
private:
int _x;
int _y;
char _oper;
};
class Response
{
public:
Response()
{
}
Response(int result, int code)
: _result(result), _code(code)
{
}
std::string Serialize()
{
Json::Value root;
root["result"] = _result;
root["code"] = _code;
Json::FastWriter writer;
return writer.write(root);
}
bool Deserialize(std::string &in)
{
Json::Value root;
Json::Reader reader;
bool ok = reader.parse(in, root);
if (ok)
{
_result = root["result"].asInt();
_code = root["code"].asInt();
}
return ok;
}
~Response()
{
}
void SetResult(int res)
{
_result=res;
}
void SetCode(int code)
{
_code=code;
}
void ShowResult()
{
std::cout << "计算结果是: " << _result << "[" << _code << "]" << std::endl;
}
private:
int _result;
int _code;
};
const std::string sep="\r\n";
using func_t=std::function<Response(Request& req)>;
class Protocol{
public:
Protocol()
{}
Protocol(func_t func)
:_func(func)
{}
std::string EnCode(const std::string& jsonstr)
{
std::string len=std::to_string(jsonstr.size());
return len+sep+jsonstr+sep;
}
bool DeCode(std::string& buffer,std::string* package)
{
ssize_t pos=buffer.find(sep);
if(pos==std::string::npos)
{
return false;
}
std::string package_len_str=buffer.substr(0,pos);
int package_len_int=std::stoi(package_len_str);
int target_len=package_len_str.size() + package_len_int + 2*sep.size();
if(buffer.size()<target_len)
{
return false;
}
*package=buffer.substr(pos + sep.size(),package_len_int);
buffer.erase(0,target_len);
return true;
}
bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buffer, Response *resp)
{
while(true)
{
int n = client->Recv(&resp_buffer);
if(n > 0)
{
std::string Json_package;
while(DeCode(resp_buffer,&Json_package))
{
resp->Deserialize(Json_package);
}
return true;
}
else if(n == 0)
{
std::cout << "server quit " << std::endl;
return false;
}
else
{
std::cout << "recv error" << std::endl;
return false;
}
}
}
void GetRequest(std::shared_ptr<Socket>& sock,InetAddr& client)
{
std::string buffer_queue;
while(true)
{
int n = sock->Recv(&buffer_queue);
if(n>0)
{
std::string json_package;
bool ret=DeCode(buffer_queue,&json_package);
if(!ret)
{
continue;
}
Request req;
bool ok=req.Deserialize(json_package);
if(!ok)
{
continue;
}
Response resp=_func(req);
std::string json_str=resp.Serialize();
std::string send_str=EnCode(json_str);
sock->Send(send_str);
}
else if(n == 0)
{
LOG(LogLevel::INFO) << "client:" << client.ToString() << " Quit!";
break;
}
else
{
LOG(LogLevel::WARNING) << "client:" << client.ToString() << ", recv error";
break;
}
}
}
std::string BuildRequestString(int x,int y,char oper)
{
Request req(x,y,oper);
std::string Json_req = req.Serialize();
return EnCode(Json_req);
}
~Protocol()
{}
private:
func_t _func;
};
关于流式数据的处理
由于我们发送和接受时可能会导致数据分段,所以我们要在数据之间加上分隔符:

并将有效数据的长度记录下来,防止当前数据被分段,当我们读取的数据长度大于等于一个完整数据的长度时,我们就可以进行接受,否则就要等待下一次读取。
下面就是该部分的代码:
std::string Encode(const std::string &jsonstr)
{
// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
std::string len = std::to_string(jsonstr.size());
return len + sep + jsonstr + sep; // 应用层封装报头
}
// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
// 5
// 50
// 50\r
// 50\r\n
// 50\r\n{"x": 10, "
// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n50\r\n{"x": 10, "y" : 20, "ope
//.....
// packge故意是指针
// 1. 判断报文完整性
// 2. 如果包含至少一个完整请求,提取他, 并从移除它,方便处理下一个
bool Decode(std::string &buffer, std::string *package)
{
ssize_t pos = buffer.find(sep);
if (pos == std::string::npos)
return false; // 让调用方继续从内核中读取数据
std::string package_len_str = buffer.substr(0, pos);
int package_len_int = std::stoi(package_len_str);
// buffer 一定有长度,但是一定有完整的报文吗?
int target_len = package_len_str.size() + package_len_int + 2 * sep.size();
if (buffer.size() < target_len)
return false;
// buffer一定至少有一个完整的报文!
*package = buffer.substr(pos + sep.size(), package_len_int);
buffer.erase(0, target_len);
return true;
}
4. Jsoncpp
我们在序列化和反序列化的时候,使用到了Json库,Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。Jsoncpp 是开源的,广泛用于各种需要处理 JSON 数据的 C++ 项目中
下面我们来了解一下Json库的内容:
4.1 特性
- 简单易用:Jsoncpp 提供了直观的 API,使得处理 JSON 数据变得简单。
- 高性能:Jsoncpp 的性能经过优化,能够高效地处理大量 JSON 数据。
- 全面支持:支持 JSON 标准中的所有数据类型,包括对象、数组、字符串、数字、布尔值和 null。
- 错误处理:在解析 JSON 数据时,Jsoncpp 提供了详细的错误信息和位置,方便
开发者调试。
当使用 Jsoncpp 库进行 JSON 的序列化和反序列化时,确实存在不同的做法和工具类可供选择。以下是对 Jsoncpp 中序列化和反序列化操作的详细介绍:
4.2 安装
ubuntu : sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel
4.3 序列化
序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件中。Jsoncpp 提供了多种方式进行序列化:
4.3.1 使用Json::Value 的 toStyledString 方法:
优点:将 Json::Value 对象直接转换为格式化的 JSON 字符串。
示例:
#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" : "男"
}
4.3.2 使用Json::StreamWriter:
优点:提供了更多的定制选项,如缩进、换行符等。
示例:
#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;
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" : "男"
}
4.3.3 使用Json::FastWriter:
优点:比 StyledWriter 更快,因为它不添加额外的空格和换行符。
示例:
#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;
std::string s = writer.write(root);
std::cout << s << std::endl;
return 0;
}
$ ./test.exe
{"name":"joe","sex":"男"}
下面是Json::StyledWriter的使用示例:
#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::StyledWriter writer;
std::string s = writer.write(root);
std::cout << s << std::endl;
return 0;
}
$ ./test.exe
{
"name" : "joe",
"sex" : "男"
}
4.4 反序列化
反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。 Jsoncpp 提供了以下方法进行反序列化:
4.4.1 使用Json::Reader:
优点:提供详细的错误信息和位置,方便调试。
示例:
#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: 北京
4.5 总结
- toStyledString、StreamWriter 和 FastWriter 提供了不同的序列化选项,你可以根据具体需求选择使用。
- Json::Reader 和 parseFromStream 函数是 Jsoncpp 中主要的反序列化工具,它们提供了强大的错误处理机制。
3, 在进行序列化和反序列化时,请确保处理所有可能的错误情况,并验证输入和输出的有效性。
5. Json::Value
Json::Value 是 Jsoncpp 库中的一个重要类,用于表示和操作 JSON 数据结构。以下是一些常用的 Json::Value 操作列表:
5.1 构造函数
Json::Value():默认构造函数,创建一个空的 Json::Value 对象。
Json::Value(ValueType type, bool allocated = false):根据给定的ValueType(如 nullValue, intValue, stringValue 等)创建一个 Json::Value 对象
5.2 访问元素
Json::Value& operator[](const char* key):通过键(字符串)访问对象中的元素。如果键不存在,则创建一个新的元素。
Json::Value& operator[](const std::string& key):同上,但使用std::string 类型的键。
Json::Value& operator[](ArrayIndex index):通过索引访问数组中的元素。如果索引超出范围,则创建一个新的元素。
Json::Value& at(const char* key):通过键访问对象中的元素,如果键不存在则抛出异常。
Json::Value& at(const std::string& key):同上,但使用 std::string类型的键。
5.3 类型检查
bool isNull():检查值是否为 null。
bool isBool():检查值是否为布尔类型。
bool isInt():检查值是否为整数类型。
bool isInt64():检查值是否为 64 位整数类型。
bool isUInt():检查值是否为无符号整数类型。
bool isUInt64():检查值是否为 64 位无符号整数类型。
bool isIntegral():检查值是否为整数或可转换为整数的浮点数。
bool isDouble():检查值是否为双精度浮点数。
bool isNumeric():检查值是否为数字(整数或浮点数)。
bool isString():检查值是否为字符串。
bool isArray():检查值是否为数组。
bool isObject():检查值是否为对象(即键值对的集合)。
5.4 赋值和类型转换
Json::Value& operator=(bool value):将布尔值赋给 Json::Value 对象。
Json::Value& operator=(int value):将整数赋给 Json::Value 对象。
Json::Value& operator=(unsigned int value):将无符号整数赋给Json::Value 对象。
Json::Value& operator=(Int64 value):将 64 位整数赋给 Json::Value对象。
Json::Value& operator=(UInt64 value):将 64 位无符号整数赋给Json::Value 对象。
Json::Value& operator=(double value):将双精度浮点数赋给Json::Value 对象。
Json::Value& operator=(const char* value):将 C 字符串赋给Json::Value 对象。
Json::Value& operator=(const std::string& value):将 std::string赋给 Json::Value 对象。
bool asBool():将值转换为布尔类型(如果可能)。
int asInt():将值转换为整数类型(如果可能)。
Int64 asInt64():将值转换为 64 位整数类型(如果可能)。
unsigned int asUInt():将值转换为无符号整数类型(如果可能)。
UInt64 asUInt64():将值转换为 64 位无符号整数类型(如果可能)。
double asDouble():将值转换为双精度浮点数类型(如果可能)。
std::string asString():将值转换为字符串类型(如果可能)。
5.5 数组和对象操作
size_t size():返回数组或对象中的元素数量。
bool empty():检查数组或对象是否为空。
void resize(ArrayIndex newSize):调整数组的大小。
void clear():删除数组或对象中的所有元素。
void append(const Json::Value& value):在数组末尾添加一个新元素。
Json::Value& operator[](const char* key, const Json::Value&
defaultValue = Json::nullValue):在对象中插入或访问一个元素,如果键不存在则使用默认值。
Json::Value& operator[](const std::string& key, const
Json::Value& defaultValue = Json::nullValue):同上,但使用 std::string类型的
6. 网络计算器实现
下面是关于该网络计算器的剩余代码:
NetCal.hpp:
#pragma once
#include "Protocol.hpp"
#include <iostream>
class Cal
{
public:
Response Execute(Request &req)
{
Response resp(0, 0); // code: 0表示成功
switch (req.Oper())
{
case '+':
resp.SetResult(req.X() + req.Y());
break;
case '-':
resp.SetResult(req.X() - req.Y());
break;
case '*':
resp.SetResult(req.X() * req.Y());
break;
case '/':
{
if (req.Y() == 0)
{
resp.SetCode(1); // 1除零错误
}
else
{
resp.SetResult(req.X() / req.Y());
}
}
break;
case '%':
{
if (req.Y() == 0)
{
resp.SetCode(2); // 2 mod 0 错误
}
else
{
resp.SetResult(req.X() % req.Y());
}
}
break;
default:
resp.SetCode(3); // 非法操作
break;
}
return resp;
}
};
#include "Socket.hpp"
#include "Common.hpp"
#include "Protocol.hpp"
#include <iostream>
#include <string>
#include <memory>
using namespace SocketModule;
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " server_ip server_port" << std::endl;
}
void GetDataFromStdin(int *x, int *y, char *oper)
{
std::cout << "Please Enter x: ";
std::cin >> *x;
std::cout << "Please Enter y: ";
std::cin >> *y;
std::cout << "Please Enter oper: ";
std::cin >> *oper;
}
// ./tcpclient server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
std::shared_ptr<Socket> client = std::make_shared<TcpSocket>();
client->BuildTcpClientSocketMethod();
if (client->Connect(server_ip, server_port) != 0)
{
// 失败
std::cerr << "connect error" << std::endl;
exit(CONNECT_ERR);
}
std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>();
std::string resp_buffer;
// 连接服务器成功
while (true)
{
// 1. 从标准输入当中获取数据
int x, y;
char oper;
GetDataFromStdin(&x, &y, &oper);
// 2. 构建一个请求-> 可以直接发送的字符串
std::string req_str = protocol->BuildRequestString(x, y, oper);
// std::cout << "-----------encode req string-------------" << std::endl;
// std::cout << req_str << std::endl;
// std::cout << "------------------------------------------" << std::endl;
// 3. 发送请求
client->Send(req_str);
// 4. 获取应答
Response resp;
bool res = protocol->GetResponse(client, resp_buffer, &resp);
if(res == false)
break;
// 5. 显示结果
resp.ShowResult();
}
client->Close();
return 0;
}
#include"NetCal.hpp"
#include"Protocol.hpp"
#include"TcpServer.hpp"
#include<memory>
void Usage(std::string proc)
{
std::cout<<"Usage: "<<proc<<"server_port"<<std::endl;
}
int main(int argc,char* argv[])
{
if(argc!=2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
std::unique_ptr<Cal> cal = std::make_unique<Cal>();
std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>([&cal](Request& req)->Response{
return cal->Execute(req);
});
std::unique_ptr<TcpServer> tcpserver = std::make_unique<TcpServer>((std::stoi(argv[1])),
[&protocol](std::shared_ptr<Socket>& sock,InetAddr& client){
protocol->GetRequest(sock,client);
});
tcpserver->Start();
}
TcpServer.hpp:
#pragma once
#include "Socket.hpp"
#include <memory>
#include <sys/wait.h>
#include <functional>
using namespace LogModule;
using namespace SocketModule;
using ioservice_t = std::function<void(std::shared_ptr <Socket> & sock, InetAddr & client)>;
class TcpServer
{
public:
TcpServer(uint16_t port, ioservice_t service)
: _port(port)
, _listensockptr(std::make_unique<TcpSocket>())
, _isrunning(false)
, _service(service)
{
_listensockptr->BuildTcpSocketMethod(_port);
}
void Start()
{
_isrunning = true;
while (_isrunning)
{
InetAddr client;
auto sock = _listensockptr->Accept(&client);
if (sock == nullptr)
{
continue;
}
LOG(LogLevel::DEBUG) << "accept success ...";
pid_t id = fork();
if (id == 0)
{
_listensockptr->Close();
if (fork() > 0)
{
exit(OK);
}
_service(sock, client);
sock->Close();
exit(OK);
}
else if (id < 0)
{
LOG(LogLevel::FATAL) << "fork error ...";
exit(FORK_ERR);
}
else
{
sock->Close();
pid_t rid = waitpid(id, nullptr, 0);
(void)rid;
}
}
_isrunning = false;
}
~TcpServer()
{
}
private:
uint16_t _port;
std::unique_ptr<Socket> _listensockptr;
bool _isrunning;
ioservice_t _service;
};
以上,就是今天的全部内容,觉得有帮助的话还请多多点赞收藏,我们下次再见!