socket api
的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的。其实,协议就是双方约定好的结构化的数据定义结构体来表示我们需要交互的信息; 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体; 这个过程叫做 “序列化” 和 “反序列化”。
只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是 ok 的.。这种约定, 就是 应用层协议。
TCP
连接既有发送缓冲区,又有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工;
tcp sockfd
读写都是它的原因;
TCP
控制,所以 TCP
叫做传输控制协议。
我们可以自定义一个协议方便客户端与服务器之间进行IO交互,例如使用json
库来进行序列化与反序列化,所以客户端向服务器发送的信息可能是这样子的:len\r\n{json}\r\n
,json序列长度len——方便我们读取完整的内容,以及分隔符\r\n
和json序列。服务器向客户端发送的信息也该和上述一致,只不过json序列中包含的应该是result
和错误码code
,而客户端向服务器发送的json序列中包含的应该是操作数x
和y
以及操作方法operate
可以是加、减、乘、除、取模等。
#pragma once
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
const std::string Sep = "\r\n";
// {json} -> len\r\n{json}\r\n
bool Encode(std::string &message)
{
if (message.size() == 0)
return false;
std::string package = std::to_string(message.size()) + Sep + message + Sep;
message = package;
return true;
}
// len\r\n{json}\r\n
// 123\r\n{json}\r\n -> {json}
// 123\r\n
// 123\r\n{json
// 123\r\n{json}\r
// 123\r\n{json}\r\n123\r\n{json}\r\n123\r\n{js
// 协议,请求和应答序列化和反序列化还要加报头
// 使用JSON序列化与反序列化
bool Decode(std::string &package, std::string *content)
{
if (package.empty())
return false;
auto pos = package.find(Sep);
if (pos == std::string::npos)
return false;
// 判断包是否完整
int len = std::stoi(package.substr(0, pos));
package = package.substr(pos + Sep.size());
if (package.size() < len + Sep.size())
return false;
*content = package.substr(0, len);
package = package.substr(len + Sep.size()); // 注意最后package要将已经解包的部分删除
return true;
}
class Request
{
public:
Request() : _x(0), _y(0), _operate(0)
{
}
Request(int x, int y,char operate) : _x(x), _operate(operate), _y(y)
{
}
bool Serialize(std::string &out_string)
{
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["operate"] = _operate;
out_string = root.toStyledString();
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 JSON: " << reader.getFormattedErrorMessages() << std::endl;
return false;
}
_x = root["x"].asInt(); // 注意要转化为整数
_y = root["y"].asInt();
_operate = root["operate"].asInt();
Print();
return true;
}
int X() const
{
return _x;
}
int Y() const
{
return _y;
}
char Operate() const
{
return _operate;
}
void Print()
{
std::cout<<"x: "<<_x<<std::endl;
std::cout<<"y: "<<_y<<std::endl;
std::cout<<"operate: "<<_operate<<std::endl;
}
private:
int _x;
int _y;
char _operate;
};
class Response
{
public:
Response() : _result(0), _code(0)
{
}
Response(int result, int code) : _result(result), _code(code)
{
}
bool Serialize(std::string &out_string)
{
Json::Value root;
root["result"] = _result;
root["code"] = _code;
out_string = root.toStyledString();
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 JSON: " << reader.getFormattedErrorMessages() << std::endl;
return false;
}
_result = root["result"].asInt(); // 注意要转化为整数
_code = root["code"].asInt();
return true;
}
void SetResult(int result)
{
_result = result;
}
void SetCode(int code)
{
_code = code;
}
int Result()
{
return _result;
}
int Code()
{
return _code;
}
void Print()
{
std::cout<<"result: "<<_result<<std::endl;
std::cout<<"code: "<<_code<<std::endl;
}
~Response()
{
}
private:
int _result;
int _code;
};
应答序列和请求序列尽管使用的方法类似,但是因为序列中包含的内容不一样,所以还是需要使用两个类
Request
和Response
;Decode和Encode方法在不同请求和应答中都是一样的,所以直接定义在类外即可。
这里仅仅实现+、-、*、/、%这五种方法,通过Request
对象来执行并返回Response
对象:
#pragma once
#include <iostream>
#include "Protocol.hpp"
class Calculator
{
public:
Calculator()
{
}
Response Execute(const Request &req)
{
// 我们拿到的都是结构化的数据,拿到的不就是类对象吗!!!
Response resp;
switch (req.Operate())
{
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 就是除0
}
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); // 3 用户发来的计算类型,无法识别
break;
}
resp.Print();
return resp;
}
~Calculator()
{
}
};
与之前实现的TCP通信代码类似,只是增加一个回调方法用来处理序列化与反序列化和计算:
#pragma once
#include <iostream>
#include <string.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <functional>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"
#include "ThreadPool.hpp"
#define BACKLOG 8
using namespace InetAddrModule;
using namespace LogModule;
using namespace ThreadPoolModule;
static const uint16_t defaultport = 8888;
using calculator_t = std::function<std::string(std::string&)>;
class TcpServer
{
using task_t = std::function<void()>;
struct ThreadData
{
int sockfd;
TcpServer *self;
};
public:
TcpServer(calculator_t calculator,uint16_t port = defaultport) :_calculator(calculator), _port(port), _listensockfd(-1), _isruning(false)
{
}
void InitServer()
{
// 1.创建Tcp套接字
_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::ERROR) << "InitServer socket fail ...";
Die(SOCKET_ERR);
}
// 填充信息
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = ::htons(_port); // aaa注意要转网络!!!!!!!!!!
serveraddr.sin_addr.s_addr = INADDR_ANY; // 表示可以接收任意地址的信息
// 2. bind;
int n = ::bind(_listensockfd, CONV(&serveraddr), sizeof(serveraddr));
if (n < 0)
{
LOG(LogLevel::ERROR) << "InitServer bind fail ...";
Die(BIND_ERR);
}
// 3.监听
int m = ::listen(_listensockfd, BACKLOG);
if (m < 0)
{
LOG(LogLevel::ERROR) << "InitServer listen fail ...";
Die(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "ServerInit success...";
}
void handler(int sockfd)
{
char buffer[4096];
std::string package;
while (true)
{
ssize_t n = ::read(sockfd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = 0;
LOG(LogLevel::INFO) << buffer;
package += buffer;
std::string result = _calculator(package);
::write(sockfd, result.c_str(), result.size());
}
else if (n == 0) // client 退出
{
LOG(LogLevel::INFO) << "client quit: " << sockfd;
break;
}
else
{
// 读取失败
break;
}
}
::close(sockfd); // fd泄漏问题!
}
void Start()
{
_isruning = true;
while (_isruning)
{
struct sockaddr_in peer;
socklen_t peerlen = sizeof(peer); // 这个地方一定要注意,要不然,会有问题!
LOG(LogLevel::DEBUG) << "accepting ...";
// 我们要获取client的信息:数据(sockfd)+client socket信息(accept || recvfrom)
int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);
if (sockfd < 0)
{
LOG(LogLevel::ERROR) << "StartServer accept fail ...";
continue; // 继续接收
}
LOG(LogLevel::INFO) << "ServerStart success...";
// version-3:线程池版本 比较适合处理短任务,或者是用户量少的情况
ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd]()
{ this->handler(sockfd); });
// 连接成功后就可以通信
}
_isruning = false;
}
~TcpServer()
{
}
private:
uint16_t _port;
int _listensockfd;
bool _isruning;
calculator_t _calculator;
};
服务器执行代码:
#include "TcpServer.hpp"
#include "Log.hpp"
#include "Calculator.hpp"
using namespace LogModule;
using cal_fun = std::function<Response(const Request &req)>;
class Parse
{
public:
Parse(cal_fun c) : _func(c)
{
}
std::string Entry(std::string &package) // 解码——反序列化——计算——序列化——编码
{
std::string reqstr;
std::string retstr;
// 1.解码
while (Decode(package, &reqstr))
{
LOG(LogLevel::DEBUG) << "Reqstr: \n"
<< reqstr;
if (reqstr.empty())
break;
// 2.反序列化
Request req;
req.Deserialize(reqstr);
// 3. 计算
Response res = _func(req);
// 4.序列化
std::string resstr;
res.Serialize(resstr);
LOG(LogLevel::DEBUG) << "resstr: \n"
<< resstr;
// 5.编码
Encode(resstr);
// 6.返回注意不是只返回一个,而是有多少返回多少
retstr += resstr;
}
return retstr;
}
private:
cal_fun _func;
};
int main()
{
ENABLE_FILE_LOG_STRATEGY();
Calculator cal;
Parse parse([&cal](const Request &req)
{ return cal.Execute(req); });
std::unique_ptr<TcpServer> tcpserver = std::make_unique<TcpServer>([&parse](std::string& package){return parse.Entry(package);});
tcpserver->InitServer();
tcpserver->Start();
return 0;
}
与服务器运行代码类似需要序列化与反序列化方法:
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include "Protocol.hpp" // 形成约定
// ./client_tcp server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cout << "Usage:./client_tcp server_ip server_port" << std::endl;
return 1;
}
std::string server_ip = argv[1]; // "192.168.1.1"
int server_port = std::stoi(argv[2]);
int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
std::cout << "create socket failed" << std::endl;
return 2;
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(server_port);
server_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());
// client 不需要显示的进行bind, tcp是面向连接的, connect 底层会自动进行bind
int n = ::connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (n < 0)
{
std::cout << "connect failed" << std::endl;
return 3;
}
// echo client
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 req(x, y, oper);
// 1. 序列化
req.Serialize(message);
// 2. Encode
Encode(message);
// 3. 发送
n = ::send(sockfd, message.c_str(), message.size(), 0);
if (n > 0)
{
char inbuffer[1024];
// 4. 获得应答
int m = ::recv(sockfd, inbuffer, sizeof(inbuffer), 0);
if (m > 0)
{
inbuffer[m] = 0;
std::string package = inbuffer;//TODO
std::string content;
// 4. 读到应答完整--暂定, decode
Decode(package, &content);
// 5. 反序列化
Response resp;
resp.Deserialize(content);
// 6. 得到结构化数据
std::cout << resp.Result() << "[" << resp.Code() << "]" << std::endl;
}
else
break;
}
else
break;
}
::close(sockfd);
return 0;
}
Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。Jsoncpp 是开源的,广泛用于各种需要处理 JSON 数据的 C++ 项目中。 特性:
当使用 Jsoncpp 库进行 JSON 的序列化和反序列化时,确实存在不同的做法和工具类可供选择。以下是对 Jsoncpp 中序列化和反序列化操作的详细介绍: 安装
C++
ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-deve
序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件中。Jsoncpp 提供了多种方式进行序列化:
#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" : "男"
}
#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" : "男"
}
#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":"男"}
#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" : "男"
}
反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。Jsoncpp 提供了以下方法进行反序列化:
#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: 北京