📢博客主页:https://blog.csdn.net/2301_779549673
📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨


文章目录
- 🏳️🌈一、protocol.hpp
-
- [1.1 Request类](#1.1 Request类)
-
- [1.1.1 基本结构](#1.1.1 基本结构)
- [1.1.2 构造、析构函数](#1.1.2 构造、析构函数)
- [1.1.3 序列化函数](#1.1.3 序列化函数)
- [1.1.4 反序列化函数](#1.1.4 反序列化函数)
- [1.1.5 其他函数](#1.1.5 其他函数)
- 1.2、Response类
-
- [1.2.1 基本结构](#1.2.1 基本结构)
- [1.2.2 构造析构函数](#1.2.2 构造析构函数)
- [1.2.3 序列化函数](#1.2.3 序列化函数)
- [1.2.4 反序列化函数](#1.2.4 反序列化函数)
- [1.2.5 打印结果](#1.2.5 打印结果)
- [1.3 Factory类](#1.3 Factory类)
- [1.4 报头](#1.4 报头)
-
- [1.4.1 添加报头](#1.4.1 添加报头)
- [1.4.2 解析报头](#1.4.2 解析报头)
- 🏳️🌈二、Service.hpp
-
- [2.1 方法回调](#2.1 方法回调)
- [2.2 成员变量 + 构造](#2.2 成员变量 + 构造)
- [2.3 IOExcute](#2.3 IOExcute)
- 🏳️🌈三、NetCal.hpp
- 🏳️🌈四、TcpClient.cpp
- 🏳️🌈五、整体代码
-
- [5.1 protocol.hpp](#5.1 protocol.hpp)
- [5.2 Service.hpp](#5.2 Service.hpp)
- [5.3 Socket.hpp](#5.3 Socket.hpp)
- [5.4 TcpServer.cpp](#5.4 TcpServer.cpp)
- [5.5 TcpServer.hpp](#5.5 TcpServer.hpp)
- [5.6 NetCal.hpp](#5.6 NetCal.hpp)
- [5.7 TcpClient.cpp](#5.7 TcpClient.cpp)
- [5.8 TcpClient.hpp](#5.8 TcpClient.hpp)
- [5.9 Makefile](#5.9 Makefile)
- 👥总结
🏳️🌈一、protocol.hpp
该文件实现序列化与反序列使用到的类和相关函数(加报头解报头)!
1.1 Request类
1.1.1 基本结构
该类有三个成员变量,_x,_y,_oper(运算符号),序列化,反序列化,构造,析构及其他获取成员变量与打印的函数!
namespace protocol{
class Request{
public:
Request(){}
// 序列化 将结构化转成字符串
bool Serialize(std::string& out);
// 反序列化 将字符串转成结构化
bool Deserialize(const std::string& in);
void Print();
int X();
int Y();
char Oper();
~Request(){}
private:
int _x;
int _y;
char _oper; // 计算符号
};
}
1.1.2 构造、析构函数
为了方便后面的使用,此处实现两个构造函数,一个无参,一个带参函数,析构函数无需处理!
// 构造函数 - 无参
Request() {}
// 构造函数 - 有参
Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper) {}
// 析构函数
~Request(){}
1.1.3 序列化函数
序列化即将结构化转成字符串,并将字符串以输出型参数输出
// 序列化 将结构化转成字符串
bool Serialize(std::string* out){
// 使用现成的库,xml,json,protobuf等
// 这里使用json库
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["oper"] = _oper;
Json::FastWriter writer;
std::string s = writer.write(root);
*out = s;
return true;
}
1.1.4 反序列化函数
反序列化即将字符串转成结构化,参数传入字符串!
// 反序列化 将字符串转成结构化
bool Deserialize(const std::string& in) {
Json::Value root;
Json::Reader reader;
bool res = reader.parse(in, root);
_x = root["x"].asInt();
_y = root["y"].asInt();
_oper = root["oper"].asInt();
return true;
}
1.1.5 其他函数
void Print() {
std::cout << _x << std::endl;
std::cout << _y << std::endl;
std::cout << _oper << std::endl;
}
int X() { return _x; }
int Y() { return _y; }
char Oper() { return _oper; }
void SetValue(int x, int y, char oper) {
_x = x;
_y = y;
_oper = oper;
}
1.2、Response类
1.2.1 基本结构
这个类我们需要组织发送给客户端的响应,需要三个成员变量,_result(计算结果),_code(自定义错误码),_desc(错误码描述)
内部主要是 序列化(发送)
和 反序列化(接受)
函数!
class Response {
public:
Response() : _result(0), _code(0), _desc("success") {}
bool Serialize(std::string* out);
bool Deserialize(const std::string& in);
~Response() {}
public:
int _result;
int _code; // 错误码 0 success, 1 fail, 2 fatal
std::string _desc; // 错误码描述
};
1.2.2 构造析构函数
构造函数 直接手动初始化(结果和错误码初始化为0,描述默认初始化为success)
析构函数无需处理!
Response() : _result(0), _code(0), _desc("success") {}
~Response() {}
1.2.3 序列化函数
这里和 request
类如出一辙,只需要构造相应的JSON结果,然后利用紧凑方法,构造出JSON风格的字符串就行了
bool Serialize(std::string* out) {
Json::Value root;
root["result"] = _result;
root["code"] = _code;
root["desc"] = _desc;
Json::FastWriter writer;
std::string s = writer.write(root);
*out = s;
return true;
}
1.2.4 反序列化函数
反序列化即将字符串转成结构化,参数传入字符串!
// 反序列化 - 将JSON字符串反序列化成成员变量
bool Deserialize(const std::string& in) {
Json::Value root;
Json::Reader reader;
// parse 的作用是将 JSON 字符串解析成 Json::Value 对象
bool res = reader.parse(in, root);
_result = root["result"].asInt();
_code = root["code"].asInt();
_desc = root["desc"].asString();
return true;
}
1.2.5 打印结果
将成员变量以字符串形式打印出来即可!
void PrintResult(){
std::cout << "result: " << _result << ", code: " << _code
<< ", desc: " << _desc << std::endl;
}
1.3 Factory类
-
因为
Request类
和Response类
可能频繁创建,因此我们可以设计一个工厂类 ,内部设计两个创建类的静态函数(没有this指针,外部直接调用函数即可)!class Factory {
public:
static std::shared_ptr<Request> BuildRequestDefault() {
return std::make_shared<Request>();
}
static std::shared_ptr<Response> BuildResponseDefault() {
return std::make_shared<Response>();
}
};
1.4 报头
在实际的网络通信中,传的不仅仅是序列化后的字符串,还有报头信息,此处我们也设计一下报头信息
"len"\r\n"{json}"\r\n
-- 完整的报文- len 有效荷载长度
\r\n
(第一个):区分 len 和 json 串\r\n
(第二个):暂时没用
1.4.1 添加报头
// 添加报头
std::string AddHeader(const std::string& jsonstr) {
int len = jsonstr.size();
std::string lenstr = std::to_string(len);
return lenstr + sep + jsonstr + sep;
}
1.4.2 解析报头
注意:可能没有一个有效信息或者有多个有效信息!
// 解析报头
// 去掉前面的长度和分隔符与有效信息后面的分隔符
std::string ParseHeader(std::string& jsonstr) {
// 分析
auto pos = jsonstr.find(sep); // 在json风格字符串中找第一个分隔符
if (pos == std::string::npos)
return std::string(); // 找不到分隔符,返回空字符串
// 获取 len
std::string lenstr = jsonstr.substr(0, pos); // 取出长度字符串
int len = std::stoi(lenstr); // 转成整数
// 计算一个完整的报文应该是多长
int totallen = lenstr.size() + len + 2 * sep.size();
// 若传进来的字符串长度小于报文总长,说明没有一个完整的有效信息,返回空
if (jsonstr.size() < totallen)
return std::string();
// 取出有效信息
std::string validstr = jsonstr.substr(pos + sep.size(), len);
// 去掉最后的分隔符
jsonstr.erase(0, totallen);
return validstr;
}
🏳️🌈二、Service.hpp
我们需要在这里处理响应的请求和进行响应
2.1 方法回调
因此我们需要提供一个方法回调,来处理json化的请求和响应
参数是请求类的指针,返回值是应答类的指针!
// 参数是请求类的指针,返回值是应答类的指针!
using process_t = std::function<std::shared_ptr<Response>(std::shared_ptr<Request>)>;
2.2 成员变量 + 构造
所以我们就需要添加一个方法回调的成员变量,并且在构造中赋值
2.3 IOExcute
IOExcute()
函数进行客户端与服务端 的通信,并处理发送过来的信息(调用执行方法),1、接收消息
2、报文解析(保证获取至少获得一条有效信息,没有则继续接受消息)
3、反序列化(将字符串转成结构化)
4、业务处理(调用构造函数传入的回调函数)
5、序列化应答
6、添加len长度(报头)
7、发送回去
// 处理请求并给出响应
void IOExcute(SockPtr sock, InetAddr& addr) {
std::string message; // 写在while外,存储信息
while (true) {
// 1. 负责读取
ssize_t n = sock->Recv(&message);
if (n == 0) {
LOG(LogLevel::INFO)
<< "client " << addr.AddrStr().c_str() << " disconnected";
break;
} else if (n < 0) {
LOG(LogLevel::ERROR)
<< "recv error for client: " << addr.AddrStr().c_str();
break;
}
std::cout << "----------------------------------------" << std::endl;
std::cout << "client " << addr.AddrStr().c_str()
<< " send message: " << message << std::endl
<< std::endl;
// 2. 负责解析,提取报头和有效荷载
// 但此时我们仍然无法保证读到的是完整报文
std::string package = ParseHeader(message);
if (package.empty())
continue;
auto req = Factory::BuildRequestDefault();
std::cout << "package: " << package << std::endl;
// 3. 反序列化
req->Deserialize(package);
// 4. 业务处理
auto resp = _process(req);
// 5. 序列化
std::string respjson;
resp->Serialize(&respjson);
std::cout << "respjson: " << respjson << std::endl;
// 6. 添加报头
std::string respstr = AddHeader(respjson);
std::cout << "respstr: " << respstr << std::endl;
// 7. 负责写回
sock->Send(respstr);
}
}
因为我们是循环获取报文,所以可能一次获取的报文不完整,因此我们需要保证recv过来的message能够保留
ssize_t Recv(std::string* out) override {
char inbuffer[4096];
ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);
if (n > 0) {
inbuffer[n] = 0;
*out += inbuffer; // 可能一次读取不成功
}
return n;
}
🏳️🌈三、NetCal.hpp
这个类就是来处理计算机的具体过程的那个回调函数的类 ,要传进一个请求类,返回一个响应类
他不需要成员变量,构造、析构为空即可
std::shared_ptr<Response> Calculator(std::shared_ptr<Request> req){}
std::shared_ptr<Response> Calculator(std::shared_ptr<Request> req) {
auto rsp = Factory::BuildResponseDefault();
switch (req->Oper()) {
case '+':
rsp->_result = req->X() + req->Y();
break;
case '-':
rsp->_result = req->X() - req->Y();
break;
case '*':
rsp->_result = req->X() * req->Y();
break;
case '/':
if (req->Y() == 0) {
rsp->_result = -1;
rsp->_desc = "division by zero";
} else {
rsp->_result = req->X() / req->Y();
}
break;
case '%':
if (req->Y() == 0) {
rsp->_result = -1;
rsp->_desc = "mod by zero";
} else {
rsp->_result = req->X() % req->Y();
}
default:
rsp->_result = -1;
rsp->_desc = "unknown operator";
break;
}
return rsp;
}
🏳️🌈四、TcpClient.cpp
该文件用户创建TcpServer类对象,并调用执行函数运行客户端!
通信操作主要包括以下七步:
1、序列化
2、添加长度报头字段
3、发送数据
4、读取应答,response
5、报文解析,提取报头和有效载荷
6、反序列化
7、打印结果
#include "TcpServer.hpp"
#include "Service.hpp"
#include "NetCal.hpp"
using namespace TcpServerModule;
int main(int argc, char* argv[]){
if(argc != 2){
std::cerr << "Usage: " << argv[0] << " port" << std::endl;
Die(1);
}
uint16_t port = std::stoi(argv[1]);
NetCal netcal;
IOService service(
[&netcal](std::shared_ptr<Request> req) {
return netcal.Calculator(req);
}
);
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(
std::bind(&IOService::IOExcute, &service, std::placeholders::_1, std::placeholders::_2), port);
tsvr->Loop();
return 0;
}

🏳️🌈五、整体代码
5.1 protocol.hpp
#include <iostream>
#include <jsoncpp/json/json.h>
namespace protocol{
// `"len"\r\n"{json}"\r\n` -- 完整的报文
static const std::string sep = "\r\n"; // 分隔符
// 添加报头
std::string AddHeader(const std::string& jsonstr){
int len = jsonstr.size();
std::string lenstr = std::to_string(len);
return lenstr + sep + jsonstr + sep;
}
// 解析报头
// 去掉前面的长度和分隔符与有效信息后面的分隔符
std::string ParseHeader(std::string& jsonstr){
// 分析
auto pos = jsonstr.find(sep); // 在json风格字符串中找第一个分隔符
if(pos == std::string::npos)
return std::string(); // 找不到分隔符,返回空字符串
// 获取 len
std::string lenstr = jsonstr.substr(0, pos); // 取出长度字符串
int len = std::stoi(lenstr); // 转成整数
// 计算一个完整的报文应该是多长
int totallen = lenstr.size() + len + 2 * sep.size();
// 若传进来的字符串长度小于报文总长,说明没有一个完整的有效信息,返回空
if(jsonstr.size() < totallen)
return std::string();
// 取出有效信息
std::string validstr = jsonstr.substr(pos + sep.size(), len);
// 去掉最后的分隔符
jsonstr.erase(0, totallen);
return validstr;
}
class Request{
public:
// 构造函数 - 无参
Request(){}
// 构造函数 - 有参
Request(int x, int y, char oper): _x(x), _y(y), _oper(oper){}
// 序列化 将结构化转成字符串
bool Serialize(std::string* out){
// 使用现成的库,xml,json,protobuf等
// 这里使用json库
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["oper"] = _oper;
Json::FastWriter writer;
std::string s = writer.write(root);
*out = s;
return true;
}
// 反序列化 将字符串转成结构化
bool Deserialize(const std::string& in){
Json::Value root;
Json::Reader reader;
// parse 的作用是将 JSON 字符串解析成 Json::Value 对象
bool res = reader.parse(in, root);
_x = root["x"].asInt();
_y = root["y"].asInt();
_oper = root["oper"].asInt();
return true;
}
void Print(){
std::cout << _x << std::endl;
std::cout << _y << std::endl;
std::cout << _oper << std::endl;
}
int X(){ return _x;}
int Y(){ return _y;}
char Oper(){ return _oper;}
void SetValue(int x, int y, char oper){
_x = x;
_y = y;
_oper = oper;
}
~Request(){}
private:
int _x;
int _y;
char _oper; // 计算符号
};
class Response{
public:
Response():_result(0), _code(0), _desc("success"){}
// 序列化 - 将成员变量结构化成JSON字符串
bool Serialize(std::string* out){
Json::Value root;
root["result"] = _result;
root["code"] = _code;
root["desc"] = _desc;
// 使用 FastWriter 快速生成紧凑的 JSON 字符串
Json::FastWriter writer;
std::string s = writer.write(root);
// 将结果通过指针参数返回
*out = s;
return true;
}
// 反序列化 - 将JSON字符串反序列化成成员变量
bool Deserialize(const std::string& in){
Json::Value root;
Json::Reader reader;
// parse 的作用是将 JSON 字符串解析成 Json::Value 对象
bool res = reader.parse(in, root);
_result = root["result"].asInt();
_code = root["code"].asInt();
_desc = root["desc"].asString();
return true;
}
void PrintResult(){
std::cout << "result: " << _result << ", code: " << _code << ", desc: " << _desc << std::endl;
}
~Response(){}
public:
int _result;
int _code; // 错误码 0 success, 1 fail, 2 fatal
std::string _desc; // 错误码描述
};
class Factory{
public:
static std::shared_ptr<Request> BuildRequestDefault(){
return std::make_shared<Request>();
}
static std::shared_ptr<Response> BuildResponseDefault(){
return std::make_shared<Response>();
}
};
}
5.2 Service.hpp
#pragma once
#include <iostream>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"
#include "Socket.hpp"
#include "protocol.hpp"
using namespace LogModule;
using namespace SocketModule;
using namespace protocol;
class IOService{
// 参数是请求类的指针,返回值是应答类的指针!
using process_t = std::function<std::shared_ptr<Response>(std::shared_ptr<Request>)>;
public:
IOService(process_t process) : _process(process){}
// 处理请求并给出响应
void IOExcute(SockPtr sock, InetAddr& addr){
std::string message; // 写在while外,存储信息
while(true){
// 1. 负责读取
ssize_t n = sock->Recv(&message);
if(n == 0){
LOG(LogLevel::INFO) << "client " << addr.AddrStr().c_str() << " disconnected";
break;
}
else if(n < 0){
LOG(LogLevel::ERROR) << "recv error for client: " << addr.AddrStr().c_str();
break;
}
std::cout << "----------------------------------------" << std::endl;
std::cout << "client " << addr.AddrStr().c_str() << " send message: " << message << std::endl;
// 2. 负责解析,去掉报头和有效荷载
// 但此时我们仍然无法保证读到的是完整报文
std::string package = ParseHeader(message);
if(package.empty()) continue;
auto req = Factory::BuildRequestDefault();
std::cout << "package: " << package << std::endl;
// 3. 反序列化
// 此时 req 里面存储着 x,y,oper
req->Deserialize(package);
std::cout << "x: " << req->X() << " y: " << req->Y() << " oper: " << req->Oper() << std::endl;
// 4. 业务处理
auto resp = _process(req);
// 5. 序列化
std::string respjson;
resp->Serialize(&respjson);
std::cout << "respjson: " << respjson << std::endl;
// 6. 添加报头
std::string respstr = AddHeader(respjson);
std::cout << "respstr: " << respstr << std::endl;
// 7. 负责写回
sock->Send(respstr);
}
}
~IOService(){}
private:
process_t _process;
};
5.3 Socket.hpp
#pragma once
#include <iostream>
#include <cstring>
#include <memory>
#include <netinet/in.h>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"
using namespace LogModule;
const int gbacklog = 8;
namespace SocketModule{
class Socket;
using SockPtr = std::shared_ptr<Socket>;
class Socket{
public:
virtual void CreateSocketOrDie() = 0; // 创建套接字
virtual void BindOrDie(uint16_t port) = 0; // 绑定套接字
virtual void ListenOrDie(int backlog = gbacklog) = 0; // 监听套接字
virtual SockPtr Accepter(InetAddr* cli) = 0; // 获取链接
virtual bool Connector(const std::string& serverip, uint16_t serverport) = 0; // 简历连接
virtual int Sockfd() = 0;
virtual void Close() = 0;
virtual ssize_t Recv(std::string* out) = 0; // 接收数据
virtual ssize_t Send(const std::string& in) = 0; // 发送数据
public:
// 创建监听套接字
void BuildListenSocket(uint16_t port){
CreateSocketOrDie(); // 创建
BindOrDie(port); // 绑定
ListenOrDie(); // 监听
}
// 创建客户端套接字
void BuildConnectorSocket(const std::string& serverip, uint16_t serverport){
CreateSocketOrDie(); // 创建
Connector(serverip, serverport); // 连接
}
};
class TcpSocket : public Socket{
public:
TcpSocket(){}
TcpSocket(int sockfd) : _sockfd(sockfd){ }
// 创建套接字
void CreateSocketOrDie() override{
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(_sockfd < 0){
LOG(LogLevel::ERROR) << "create socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::DEBUG) << "sockfd create success : " << _sockfd;
}
// 绑定套接字
void BindOrDie(uint16_t port) override{
// sockaddr_in 的头文件是 #include <netinet/in.h>
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
int n = ::bind(_sockfd, CONV(&local), sizeof(local));
if(n < 0){
LOG(LogLevel::ERROR) << "bind socket error";
exit(BIND_ERR);
}
LOG(LogLevel::DEBUG) << "bind success";
}
// 监听套接字
void ListenOrDie(int backlog = gbacklog) override{
int n = ::listen(_sockfd, backlog);
if(n < 0){
LOG(LogLevel::ERROR) << "listen socket error";
exit(LISTEN_ERR);
}
LOG(LogLevel::DEBUG) << "listen success";
}
// 获取链接
SockPtr Accepter(InetAddr* cli) override{
struct sockaddr_in client;
socklen_t clientlen = sizeof(client);
// accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
// 返回一个新的套接字,该套接字与调用进程间接地建立了连接。
int sockfd = ::accept(_sockfd, CONV(&client), &clientlen);
if(sockfd < 0){
LOG(LogLevel::ERROR) << "accept socket error";
return nullptr;
}
*cli = InetAddr(client);
LOG(LogLevel::DEBUG) << "get a new connection from " << cli->AddrStr().c_str() << ", sockfd : " << sockfd;
return std::make_shared<TcpSocket>(sockfd);
}
// 建立连接
bool Connector(const std::string& serverip, uint16_t serverport) override{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET; // IPv4协议
server.sin_port = htons(serverport); // 端口号
// 这句话表示将字符串形式的IP地址转换为网络字节序的IP地址
// inet_pton函数的作用是将点分十进制的IP地址转换为网络字节序的IP地址
// 这里的AF_INET表示IPv4协议
// 这里的serverip.c_str()表示IP地址的字符串形式
// &server.sin_addr表示将IP地址存储到sin_addr成员变量中
::inet_pton(AF_INET, serverip.c_str(), &server.sin_addr); // IP地址
int n = ::connect(_sockfd, CONV(&server), sizeof(server));
if(n < 0){
LOG(LogLevel::ERROR) << "connect socket error" ;
return false;
}
LOG(LogLevel::DEBUG) << "connect success";
return true;
}
// 获取套接字描述符
int Sockfd() override{ return _sockfd; }
// 关闭套接字
void Close() override{ if(_sockfd >= 0) ::close(_sockfd); }
// 接收数据
ssize_t Recv(std::string* out) override{
char inbuffer[4096];
ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);
if(n > 0){
inbuffer[n] = 0;
*out += inbuffer; // 可能一次读取不成功
}
return n;
}
// 发送数据
ssize_t Send(const std::string& in) override{
return ::send(_sockfd, in.c_str(), in.size(), 0);
}
~TcpSocket(){}
private:
int _sockfd;
};
}
5.4 TcpServer.cpp
#include "TcpServer.hpp"
#include "Service.hpp"
#include "NetCal.hpp"
using namespace TcpServerModule;
int main(int argc, char* argv[]){
if(argc != 2){
std::cerr << "Usage: " << argv[0] << " port" << std::endl;
Die(1);
}
uint16_t port = std::stoi(argv[1]);
NetCal netcal;
IOService service(
[&netcal](std::shared_ptr<Request> req) {
return netcal.Calculator(req);
}
);
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(
std::bind(&IOService::IOExcute, &service, std::placeholders::_1, std::placeholders::_2), port);
tsvr->Loop();
return 0;
}
5.5 TcpServer.hpp
#pragma once
#include <iostream>
#include <memory>
#include <functional>
#include <sys/wait.h>
#include "Thread.hpp"
#include "InetAddr.hpp"
#include "Socket.hpp"
namespace TcpServerModule{
using namespace SocketModule;
using namespace LogModule;
using service_t = std::function<void(SocketModule::SockPtr, InetAddr&)>;
class TcpServer{
public:
TcpServer(service_t service, uint16_t port)
: _port(port), _listensock(std::make_shared<TcpSocket>()),
_isrunning(false), _service(service)
{
_listensock->BuildListenSocket(port);
}
void Loop(){
_isrunning = true;
while(_isrunning){
InetAddr client;
// 获取客户端连接
SockPtr cli = _listensock->Accepter(&client);
if(cli == nullptr) continue;
LOG(LogLevel::DEBUG) << "get a new connection from " << client.AddrStr().c_str();
// 获取成功
pthread_t tid;
// ThreadData 的头文件是
ThreadData* td = new ThreadData(cli, this, client);
pthread_create(&tid, nullptr, Execute, td); // 新线程分离
}
}
// 线程函数参数对象
class ThreadData{
public:
SockPtr _sockfd;
TcpServer* _self;
InetAddr _addr;
public:
ThreadData(SockPtr sockfd, TcpServer* self, const InetAddr& addr)
: _sockfd(sockfd), _self(self), _addr(addr)
{}
};
// 线程函数
static void* Execute(void* args){
ThreadData* td = static_cast<ThreadData*>(args);
// 子线程结束后由系统自动回收资源,无需主线程调用 pthread_join
pthread_detach(pthread_self()); // 分离新线程,无需主线程回收
td->_self->_service(td->_sockfd, td->_addr);
delete td;
return nullptr;
}
~TcpServer(){}
private:
uint16_t _port;
SockPtr _listensock;
bool _isrunning;
service_t _service;
};
}
5.6 NetCal.hpp
这里头文件可以直接使用 Service.hpp
,因为我们要使用 protocol.hpp
类的请求和相应类,如果 NetCal.hpp
中也包含 protocol.hpp
以及 using namespace protocol
就会导致 protocol
命名空间中的 Request
和 Response
类定义不清晰
#pragma once
#include "Service.hpp"
// 构建处理请求的方法类,接收请求类,返回响应类
class NetCal{
public:
NetCal(){}
std::shared_ptr<Response> Calculator(std::shared_ptr<Request> req){
auto rsp = Factory::BuildResponseDefault();
switch(req->Oper()){
case '+':
rsp->_result = req->X() + req->Y();
break;
case '-':
rsp->_result = req->X() - req->Y();
break;
case '*':
rsp->_result = req->X() * req->Y();
break;
case '/':
if(req->Y() == 0){
rsp->_result = -1;
rsp->_desc = "division by zero";
}else{
rsp->_result = req->X() / req->Y();
}
break;
case '%':
if(req->Y() == 0){
rsp->_result = -1;
rsp->_desc = "mod by zero";
}else{
rsp->_result = req->X() % req->Y();
}
break;
default:
rsp->_result = -1;
rsp->_desc = "unknown operator";
break;
}
return rsp;
}
~NetCal(){}
};
5.7 TcpClient.cpp
#include "TcpClient.hpp"
int main(int argc, char* argv[]){
if(argc != 3){
std::cerr << "Usage: " << argv[0] << " <server_ip> <server_port>" << std::endl;
Die(1);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
// 1. 创建套接字 并 建立连接
SockPtr sock = std::make_shared<TcpSocket>();
sock->BuildConnectorSocket(serverip, serverport);
// srand需要的头文件是 cstdlib
srand(time(nullptr) ^ getpid());
const std::string opers = "+-*/%";
std::string packmessage;
while(true){
// 构建数据
int x = rand() % 10;
usleep(x * 1000);
int y = rand() % 10;
usleep(x * y * 100);
char oper = opers[rand() % 4];
// 构建请求
auto req = Factory::BuildRequestDefault();
req->SetValue(x, y, oper);
// 1. 序列化
std::string reqstr;
req->Serialize(&reqstr);
// 2. 添加报头字段
reqstr = AddHeader(reqstr);
std::cout << "##################################" << std::endl;
std::cout << "Request String: " << reqstr << std::endl;
// 3. 发送请求
sock->Send(reqstr);
while(true){
// 4. 读取响应
ssize_t n = sock->Recv(&packmessage);
if(n <= 0)
break;
// 5. 解析响应
std::string package = ParseHeader(packmessage);
if(package.empty())
continue;
std::cout << "Response String: " << package << std::endl;
// 6. 反序列化
auto rsp = Factory::BuildResponseDefault();
rsp->Deserialize(package);
rsp->PrintResult();
break;
}
sleep(10);
}
sock->Close();
return 0;
}
5.8 TcpClient.hpp
#pragma once
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <string>
#include <cerrno>
#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 <time.h>
#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#include "Socket.hpp"
#include "protocol.hpp"
using namespace LogModule;
using namespace SocketModule;
using namespace protocol;
5.9 Makefile
.PHONY: all
all:server_tcp client_tcp
server_tcp:TcpServer.cpp
g++ -o $@ $^ -std=c++17 -lpthread -ljsoncpp
client_tcp:TcpClient.cpp
g++ -o $@ $^ -std=c++17 -lpthread -ljsoncpp
.PHONY: clean
clean:
rm -f server_tcp client_tcp
👥总结
本篇博文对 【Linux网络】打造初级网络计算器 - 从协议设计到服务实现 做了一个较为详细的介绍,不知道对你有没有帮助呢
觉得博主写得还不错的三连支持下吧!会继续努力的~