前言:本文默认读者已掌握 TCP 协议相关网络接口知识,将聚焦于应用层协议的设计与剖析,有关底层通信机制及业务逻辑部分仅作简要概述,不再展开详述。
目录
服务器
一、通信
二、协议
1.序列化与反序列化
2. 封包与解包
三、业务
客户端
四、源码
本文将基于TCP协议构建一个网络计算器服务。业务逻辑相对弱化一些,目的是完整演示服务端开发的核心流程。而把重点放在应用层协议的设计过程,包括请求/响应报文结构定义、数据传输机制等关键实现细节;同时讲解Socket通信层的工程化封装,通过抽象连接建立、数据收发、资源管理等基础操作,构建可扩展的网络通信模块。本文形成的协议设计方法论与组件封装方案,可直接复用于各类TCP服务端开发场景。
服务器
框架设计
TCP协议作为面向字节流的传输层协议,其不维护报文边界的特点可能导致粘包/半包问题。为此我们需要设计应用层协议,通过报文头部标识、数据完整性校验等机制确保可靠通信。若您对以上内容很懵这很正常,到下文解决自定义协议时会细讲。
首先我们梳理本文要完成的核心文件及功能,如下:
1.通信
- TcpServer.hpp:服务器相关的类以及类方法的实现——主要完成通信功能。
- TcpServer.cc:服务器主函数(main)的实现——对服务器接口的调用,即启动服务器。
- TcpClient.cc:客户端主函数(main)的实现——启动客户端,并与服务器通信。
2.协议
- Protocol.hpp:自定义协议,完成序列化、反序列化、封包,解包等。
3.业务
- NetCal.hpp:网络计算器的实现。
一、通信
由于C++标准库未原生提供网络通信支持,而网络编程中连接管理、数据收发等底层操作虽遵循固定模式却存在大量重复劳动,因此我们将首先实现一个高内聚的Socket封装类。该模块通过抽象TCP通信的核心流程,统一处理连接建立维护、收发数据等基础功能,为后续业务开发提供稳定可靠的通信基础设施。
这里我们使用模板方法模式,即基类Socket大部分方法都是纯虚方法,让Tcp协议类和Udp类作为子类进行继承。
创建Socket.hpp文件,实现类的声明,如下:
static const int gbacklog = 8;//允许8个客户端连接
namespace SocketMoudule
{
class Socket
{
public:
virtual void SocketOrDie() = 0; //打开网络文件
virtual void BindOrDie(uint16_t) = 0; //绑定端口
virtual void ListenOrDie(int) = 0; //监听
virtual shared_ptr<Socket> Accept(InetAddr *) = 0; //接收请求
virtual void Close() = 0; //关闭网络文件
virtual int Recv(string *) = 0; //收数据
virtual int Send(string &) = 0; //发数据
virtual void Connect(const std::string &, uint16_t) = 0; //与服务器建立连接
public:
void BuildTcpServerMoudule(uint16_t port, int backlog = gbacklog)
{
//初始化
SocketOrDie();
BindOrDie(port);
ListenOrDie(backlog);
}
void BuildUdpServerMoudule(uint16_t port)
{
void SocketOrDie();
void BindOrDie(port);
}
};
//Tcp协议通信
class TcpSocket : public Socket
{
public:
private:
int _socketfd;
};
//Udp协议通信
class UdpSocket : public Socket
{
public:
private:
};
}
其中InetAddr类是对sockaddr_in等相关信息的封装,如下:
class InetAddr
{
public:
InetAddr() {}
InetAddr(sockaddr_in &peer)
: _addr(peer)
{
_port = ntohs(peer.sin_port);
char buffer[32];
inet_ntop(AF_INET, &peer.sin_addr, buffer, sizeof(peer));
_ip = buffer;
}
InetAddr(uint16_t port)
: _port(port), _ip(to_string(INADDR_ANY))
{
_addr.sin_family = AF_INET;
_addr.sin_port = htons(_port);
_addr.sin_addr.s_addr = INADDR_ANY;
}
InetAddr(uint16_t port, string ip)
: _port(port), _ip(ip)
{
_addr.sin_family = AF_INET;
//主机序->网络序
_addr.sin_port = htons(_port);
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
//_addr.sin_addr.s_addr = inet_addr(_ip.c_str());
}
string tostring_port()
{
return to_string(_port);
}
string tostring_ip()
{
return _ip;
}
bool operator==(InetAddr addr)
{
return _port == addr._port && _ip == addr._ip;
}
sockaddr *getaddr()
{
return (sockaddr *)&_addr;
}
socklen_t getlen()
{
return sizeof(_addr);
}
string stringaddr()
{
return tostring_ip() + ":" + tostring_port() + " ";
}
private:
uint16_t _port;
string _ip;
sockaddr_in _addr;
};
为什么要用OrDie后来缀命名呢?
OrDie:源自C/C++开发中的assert_or_die()等函数命名范式,表示关键资源必须成功初始化,否则程序应立即终止的严格错误处理策略。常见于Google等公司的内部代码库(如ParseOrDie()),形成了一种防御性编程的文化符号。
表示:"宁可程序立刻崩溃暴露问题,也不允许在错误状态下苟延残喘"。
注:以上接口基本没人能一次性想出来,想出来了参数也不一定设对,而应该在开发过程中根据需求或发现问题,而进行添加或修改。
函数具体实现很简单,一一调用对应的网络接口即可,这里就不再讲解,文末会给出源码。
完成TcpServer.hpp文件
创建TcpServer类,它主要完成打开网络文件,端口绑定,监听,接收请求。这些功能我们都在Socket中封装了,我们实例化出TcpSocket对象,然后对它的接口就行调用即可。
所以TcpServer类成员,要包含两个成员变量。
- unique_ptr<Socket> _listensockptr:指向一个TcpSocket对象。
- 回调数据处理方法:这个成员到后文再设计。
在实际中会有很多客户端与服务器进行连接,通常需要并发的处理客户端需求,可以使用多进程、多线程、线程池、进程池等。这里我们就做简单一点,使用多进程完成并发功能。
而子进程默认情况下是需要父进程进行等待的,这就会造成主进程阻塞,无法接收其他客户的请求,和单执行流没区别了。基于这样的问题有两种解决方法:
如果不理解第1点,可以看一下文章,然后锁定到特殊信号的SIGCHIL信号进行学习:
Linux信号的诞生与归宿:内核如何管理信号的生成、阻塞和递达?_内核是如何产生信号的-CSDN博客z
这里我们用方法2解决,如下:
using namespace SocketMoudule;
class TcpServer
{
public:
TcpServer(uint16_t port)
: _listensockptr(make_unique<TcpSocket>())
{
//进行初始化,同时启动服务器
_listensockptr->BuildTcpServerMoudule(_port);
Start();
}
void Start()
{
while(true)
{
InetAddr addr;
auto sock = _listensockptr->Accept(&addr);
if(sock == nullptr) continue;
LOG(Level::INFO)<<addr.stringaddr()<<"accept success…";
pid_t pid = fork();
if(pid < 0)
{
LOG(Level::FATAL)<<"fork fail";
exit(FORK_ERRO);
}
if(pid == 0)
{
if(fork()>0)
exit(SUCCESS);
//回调方法
//……
sock->Close();
exit(SUCCESS);
}
else
{
sock->Close();
}
}
}
private:
unique_ptr<Socket> _listensockptr;
//回调函数
};
说明:FORK_ERRO、SUCCESS是退出码,本质是枚举类型,在文件Common.hpp中定义,LOG是我写的一个打印日志的接口, 大家把它当作cout理解就行,当然需要日志源码的可以私信我。
二、协议
TCP协议作为面向字节流的传输层协议,可能导致粘包/半包问题。
粘包:发送方连续发送多个独立数据包,接收方可能一次性读取到合并数据。比如发送6和7,接收到67。
半包:发送方传输大尺寸数据包,接收方首次读取到部分数据。比如发送hello,接收到he和llo。
1.序列化与反序列化
粘包问题
对于粘包问题,可以在数据之间添加一些标识符来区分它们,比如": ",当接收到完整的数据包后根据这些标识符就可以区分出它们。这个操作就是序列化和反序列化。
数据包以什么格式传递是通信双方(服务器和客户端)约定好的,即协议。序列化和反序列化就是协议的一部分。
注意:TCP协议和UDP协议是传输层协议,这里解决粘包/半包问题的是应用层协议,不要混淆。
我们的业务网络计数器,需要客户端传入两个运算对象和一个运算符。而服务器给客户端返回的是一个运算结果,和一个错误码(用来标识运算结果是否有效,比如除0或其他非法操作需要标识错误)。即有两类数据。
在文件Protocol中创建两个类,Request和Response分别对这两类数据进行序列化和反序列化,如下:
class Request
{
public:
Request() {}
Request(int x, int y, char oper)
: _x(x), _y(y), _oper(oper)
{}
//序列化
string Serialize();
//反序列化
void DeSerialize(string &message);
private:
int _x;
int _y;
char _oper;
};
class Response
{
public:
Response() {}
Response(int result, int code)
: _result(result), _code(code)
{}
string Serialize();
void DeSerialize(string &message);
private:
int _result;
int _code;
};
序列化和反序列化的具体操作我们不用自己做,我们使用Json::Value,它是 JsonCpp库(最流行的C++ JSON处理库之一)的核心数据类型。
JsonCpp库的安装:
ubuntu: sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel
Jsoncpp 提供了多种方式进行序列化方法,如下:
FastWriter | 体积最小 | 无定制能力 | 机器间数据传输 |
StyledWriter | 可读性强 | 性能较差 | 调试/配置文件 |
StreamWriterBuilder | 可定制性强,官方推荐 | 配置稍复杂 | 所有生产环境 |
直接写文件流 | 适合大文件处理 | 需管理文件流 | 持久化存储 |
这里我们简单一点使用FastWrite,数据是以key:value的方式存储。如下:
string Serialize()
{
Json::Value data;
data["x"] = _x;
data["y"] = _y;
data["oper"] = _oper;
Json::FastWriter writer;
return writer.write(data);
}
如果传入 8,7,* 被序列化为:
{"oper":42,"x":7,"y":8}
反序列化:
Json::Reader用来把字符串转化为Json::Value类型,再从Json::Value中提取到各个元素,这个过程和反序列化很类似,其中要指明数据类型,如.asInt()。反序列化相当于对Request成员变量初始化。
代码示例:
void DeSerialize(string &message)
{
Json::Value data;
Json::Reader reader;
reader.parse(message, data);
_x = data["x"].asInt();
_y = data["y"].asInt();
_oper = data["oper"].asInt();
}
对于Response同样,如下:
class Response
{
public:
Response() {}
Response(int result, int code)
: _result(result), _code(code)
{}
string Serialize()
{
Json::Value data;
data["result"] = _result;
data["code"] = _code;
Json::FastWriter writer;
return writer.write(data);
}
void DeSerialize(string &message)
{
Json::Value data;
Json::Reader reader;
reader.parse(message, data);
_result = data["result"].asInt();
_code = data["code"].asBool();
}
private:
int _result;
int _code;
};
2. 封包与解包
半包问题
解决了粘包问题,但我们还需要知道能够判断报文是否完整,即处理半包问题。
对于半包问题,这里我们选择在数据包前加上一个报头,这个报头存储的是这个数据包有效载荷的长度,然后报头与数据包用“\\r\\n”区分开,在报文尾加“\\r\\n”用来区分下一个报文。这样的话我们可以通过报头知道这个报文一个有多长,然后去看报文有没有到达对应的长度,如果是则是完整的,如果不是,则就是不完整。这个过程我们称为封包和解包。
我们创建一个类Protocol来封装 封包、解包、请求处理、获取响应、请求构建等。
- 封包:添加报头,即有效载荷的长度,用“\\r\\n”与报文分开。方便接收方识别报文的完整性。
- 解包:判断报文的完整性,并移除报头。
- 请求构建:对数据进行序列化,封包。
- 获取响应:接收数据,解包,反序列化。
- 请求处理:接收数据,解包,反序列化,业务处理(回调),序列化,封包,发送。
数据的处理实质就是传入一个Request,得到一个Response,所以我们定义一个函数类型:
- using func_t = function<Response(Request &)>
const string sep = "\\r\\n";
using func_t = function<Response(Request &)>;
class Protocol
{
public:
Protocol() {};
Protocol(func_t func)
: _func(func)
{}
string EnCode(string &jsonstr);//封包
bool DeCode(string &buffer, string *package);//解包
string BuildRequestString(int x, int y, char oper);//构建请求报文
void GetRequest(shared_ptr<Socket> &sock, InetAddr &addr);//请求处理
bool GetResponse(shared_ptr<Socket> &sock, string *buffer, Response *rsp);//获取响应
private:
func_t _func;
};
- GetRequest:给服务器用的,即请求处理(业务处理),涉及数据收发,所以传入Socket指针和客户端地址信息InetAddr。
- GetResponse:给客户端使用,用来获取数据处理结果,涉及数据接收,所以传入客户端的Socket指针,输出型参数buffer(缓冲区)和rsp。注意因为接收到的报文可能不完整,不能一次取到报文,所以需要缓冲区来保留数据。
还记得在TcpServer里我们缺少的成员变量数据处理函数吗?现在我们知道它是谁了,即:
- void GetRequest(shared_ptr<Socket> &sock, InetAddr &addr);
在TcpServer.hpp中声明一个类型:
- using func_t = function<void(shared_ptr<Socket> &sock, InetAddr)>;
然后添加成员变量func_t _func,并在构造函数的参数列表进行初始化。
EnCode
封包 = 报文长度+“\\r\\n”+报文+“\\r\\n”。如下:
string EnCode(string &jsonstr)
{
size_t len = jsonstr.size();
return to_string(len) + sep + jsonstr + sep;
}
DeCode
如下:
bool DeCode(string &buffer, string *package)
{
int pos = buffer.find(sep);
if (pos == string::npos)
return false;
string lenStr = buffer.substr(0, pos);
int lenTarget = lenStr.size() + stoi(lenStr) + 2 * sep.size();
if (buffer.size() < lenTarget)
return false;
*package = buffer.substr(pos + sep.size(), stoi(lenStr));
buffer.erase(0, lenTarget);
return true;
}
BuildRequestString
构建请求报文,即对数据进行序列化和封包,如下:
string BuildRequestString(int x, int y, char oper)
{
Request req(x, y, oper);
string json_str = req.Serialize();
return EnCode(json_str);
}
GetRequest
得到并处理请求,对我们刚才写的方法进行组合,即接收数据,解包,反序列化,业务处理,序列化,封包,发送数据。如下:
void GetRequest(shared_ptr<Socket> &sock, InetAddr &addr)
{
while (true)
{
string json_package; //接收数据
int n = sock->Recv(&json_package);
if (n == 0)
{
LOG(Level::INFO) << "client " << addr.tostring_ip() << " exit";
break;
}
else if (n < 0)
{
LOG(Level::WARING) << "Recv fail";
break;
}
else
{
// 解报包
string json_str;
while (DeCode(json_package, &json_str))
{
// 反序列化
Request req;
req.DeSerialize(json_str);
// 业务处理
Response resp = _func(req);
// 序列化
string send_str = resp.Serialize();
// 加报头
send_str = EnCode(send_str);
// 发送
sock->Send(send_str);
}
}
}
}
注意把解包过程写成循环,因为一次性也有可能读到多个完整报文,需要把它们都读取出来。
GetResponse
接收数据,解包,反序列化,最后数据是通过输出型参数带回的。
bool GetResponse(shared_ptr<Socket> &sock, string *buffer, Response *rsp)
{
while (true)
{
int n = sock->Recv(buffer);
if (n == 0)
{
LOG(Level::WARING) << "server exit";
return false;
}
else if (n < 0)
{
LOG(Level::WARING) << "client Recv fail";
return false;
}
else
{
// 解包
string json_str;
if (!DeCode(*buffer, &json_str)) continue;
// 反序列化
rsp->DeSerialize(json_str);
return true;
}
}
}
三、业务
业务处理部分大家可以自行设定,即实现一个function<Response(Request &)>类型的函数,最好封装一个类来维护。这里做一个简单的计算器来充当一个,如下:
class NetCal
{
public:
Response Execute(Request &req);
private:
};
具体实现在文末源码给出。
TcpServer.cc
做完上面的一切我们就可以来完成服务器主函数main了。
首先需要程序外部传入端口号,所以main函数需要传入命令行参数。需要检测格式的正确性。
其次我们创建业务类对象,协议类对象,通信类对象,并把回调方法一层一层的往下传,如下:
int main(int argc, char *argv[])
{
if (argc != 2)
{
LOG(Level::FATAL) << "Usage server port";
exit(USAGE_ERRO);
}
// 务业
unique_ptr<NetCal> nc(make_unique<NetCal>());
// 协议
unique_ptr<Protocol> pt(make_unique<Protocol>([&](Request &req) -> Response
{ return nc->Execute(req); }));
// 通通信
unique_ptr<TcpServer> ts(make_unique<TcpServer>(stoi(argv[1]), [&](shared_ptr<Socket> sock, InetAddr addr)
{ pt->GetRequest(sock, addr); }));
return 0;
}
客户端
inline void GetDatafromstdin(int *x, int *y, char *oper)
{
cout << "Please Enter x:";
cin >> *x;
cout << "Please Enter oper:";
cin >> *oper;
cout << "Please Enter y:";
cin >> *y;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
LOG(Level::FATAL) << "Usage serever's ip and port";
exit(USAGE_ERRO);
}
//构建Socket类,并创建套接字,与服务器连接。
shared_ptr<Socket> client = make_shared<TcpSocket>();
client->SocketOrDie();
client->Connect(argv[1], stoi(argv[2]));
//创建协议类对象
unique_ptr<Protocol> ptl = make_unique<Protocol>();
string buffer;
while (true)
{
//读取输入并构建请求
int x, y;
char oper;
GetDatafromstdin(&x, &y, &oper);
string send_str = ptl->BuildRequestString(x, y, oper);
//发送请求
client->Send(send_str);
//接收响应
Response rsp;
if (!ptl->GetResponse(client, &buffer, &rsp))
break;
//结果展示
rsp.ShowResult();
}
return 0;
}
非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!💕💕
四、源码
TcpServer.hpp
#pragma once
#include <iostream>
#include <functional>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include "Socket.hpp"
using namespace my_log;
using namespace SocketMoudule;
using ioservice_t = function<void(shared_ptr<Socket> &socket, InetAddr &addr)>;
class TcpServer
{
public:
TcpServer(uint16_t port, ioservice_t service)
:_service(service),_listensockptr(make_unique<TcpSocket>())
{
_listensockptr->BuildTcpServerMoudule(port);
Start();
}
void Start()
{
while(true)
{
InetAddr addr;
auto sock = _listensockptr->Accept(&addr);
if(sock == nullptr) continue;
LOG(Level::INFO)<<addr.stringaddr()<<"accept success…";
pid_t pid = fork();
if(pid < 0)
{
LOG(Level::FATAL)<<"fork fail";
exit(FORK_ERRO);
}
if(pid == 0)
{
if(fork()>0)
exit(SUCCESS);
_service(sock,addr);
sock->Close();
exit(SUCCESS);
}
else
{
sock->Close();
}
}
}
private:
ioservice_t _service;
unique_ptr<Socket> _listensockptr;
};
TcpServer.cc
#include "TcpServer.hpp"
#include "InetAddr.hpp"
#include "Socket.hpp"
#include "Protocol.hpp"
#include "NetCal.hpp"
using namespace SocketMoudule;
int main(int argc, char *argv[])
{
if (argc != 2)
{
LOG(Level::FATAL) << "Usage server port";
exit(USAGE_ERRO);
}
// 务业
unique_ptr<NetCal> nc(make_unique<NetCal>());
// 协议
unique_ptr<Protocol> pt(make_unique<Protocol>([&](Request &req) -> Response
{ return nc->Execute(req); }));
// 通通信
unique_ptr<TcpServer> ts(make_unique<TcpServer>(stoi(argv[1]), [&](shared_ptr<Socket> sock, InetAddr addr)
{ pt->GetRequest(sock, addr); }));
return 0;
}
TcpClient.cc
#include <iostream>
#include <memory>
#include "Common.hpp"
#include "Socket.hpp"
#include "Log.hpp"
#include "Protocol.hpp"
using namespace my_log;
using namespace SocketMoudule;
inline void GetDatafromstdin(int *x, int *y, char *oper)
{
cout << "Please Enter x:";
cin >> *x;
cout << "Please Enter oper:";
cin >> *oper;
cout << "Please Enter y:";
cin >> *y;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
LOG(Level::FATAL) << "Usage serever's ip and port";
exit(USAGE_ERRO);
}
shared_ptr<Socket> client = make_shared<TcpSocket>();
unique_ptr<Protocol> ptl = make_unique<Protocol>();
client->SocketOrDie();
client->Connect(argv[1], stoi(argv[2]));
string buffer;
while (true)
{
int x, y;
char oper;
GetDatafromstdin(&x, &y, &oper);
string send_str = ptl->BuildRequestString(x, y, oper);
client->Send(send_str);
Response rsp;
if (!ptl->GetResponse(client, &buffer, &rsp))
break;
rsp.ShowResult();
}
return 0;
}
Socket.hpp
#pragma once
#include <iostream>
#include <string>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <memory>
#include "Common.hpp"
#include "InetAddr.hpp"
#include "Log.hpp"
using namespace my_log;
static const int gbacklog = 8;
namespace SocketMoudule
{
class Socket
{
public:
virtual void SocketOrDie() = 0;
virtual void BindOrDie(uint16_t) = 0;
virtual void ListenOrDie(int) = 0;
virtual shared_ptr<Socket> Accept(InetAddr *) = 0;
virtual void Close() = 0;
virtual int Recv(string *) = 0;
virtual int Send(string &) = 0;
virtual void Connect(const std::string &, uint16_t) = 0;
public:
void BuildTcpServerMoudule(uint16_t port, int backlog = gbacklog)
{
SocketOrDie();
BindOrDie(port);
ListenOrDie(backlog);
}
void BuildUdpServerMoudule()
{
void SocketOrDie();
void BindOrDie();
}
};
class TcpSocket : public Socket
{
public:
TcpSocket(int socketfd = -1)
: _socketfd(socketfd)
{
}
virtual void SocketOrDie() override
{
_socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (_socketfd < 0)
{
LOG(Level::FATAL) << "Socket fail";
exit(SOCKET_ERRO);
}
LOG(Level::INFO) << "socket success";
}
virtual void BindOrDie(uint16_t port) override
{
InetAddr addr(port);
int n = bind(_socketfd, addr.getaddr(), addr.getlen());
if (n < 0)
{
LOG(Level::FATAL) << "bind fail";
exit(BIND_ERRO);
}
LOG(Level::INFO) << "Bind success";
}
virtual void ListenOrDie(int backlog) override
{
int n = listen(_socketfd, backlog);
if (n < 0)
{
LOG(Level::FATAL) << "Listen fail";
exit(LISTEN_ERRO);
}
LOG(Level::INFO) << "Listen success";
}
virtual shared_ptr<Socket> Accept(InetAddr *addr) override
{
// 为什么不直接用addr,因为构造不了IP。
socklen_t len = sizeof(sockaddr_in);
sockaddr_in peer;
int n = accept(_socketfd, (sockaddr *)&peer, &len);
if (n < 0)
{
LOG(Level::WARING) << addr->tostring_ip() << "accept fail";
return nullptr;
}
*addr = InetAddr(peer);
return make_shared<TcpSocket>(n);
}
virtual void Close() override
{
close(_socketfd);
}
virtual int Recv(string *out) override
{
char buffer[1024];
int n = read(_socketfd, buffer, sizeof(buffer) – 1);
if (n > 0)
{
buffer[n] = '\\0';
*out += buffer;
}
return n;
}
virtual int Send(string &message) override
{
return write(_socketfd, message.c_str(), message.size());
}
virtual void Connect(const std::string &ip, uint16_t port) override
{
InetAddr addr(port, ip);
int n = connect(_socketfd, addr.getaddr(), addr.getlen());
if (n < 0)
{
LOG(Level::FATAL) << "connect fail";
exit(CONNECT_ERRO);
}
LOG(Level::INFO) << "connect success";
}
private:
int _socketfd;
};
class UdpSocket : public Socket
{
public:
//……
private:
int _socketfd;
};
}
InteAddr.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
using namespace std;
class InetAddr
{
public:
InetAddr() {}
InetAddr(sockaddr_in &peer)
: _addr(peer)
{
_port = ntohs(peer.sin_port);
char buffer[32];
inet_ntop(AF_INET, &peer.sin_addr, buffer, sizeof(peer));
_ip = buffer;
}
InetAddr(uint16_t port)
: _port(port), _ip(to_string(INADDR_ANY))
{
_addr.sin_family = AF_INET;
_addr.sin_port = htons(_port);
_addr.sin_addr.s_addr = INADDR_ANY;
}
InetAddr(uint16_t port, string ip)
: _port(port), _ip(ip)
{
_addr.sin_family = AF_INET;
//主机序->网络序
_addr.sin_port = htons(_port);
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
//_addr.sin_addr.s_addr = inet_addr(_ip.c_str());
}
string tostring_port()
{
return to_string(_port);
}
string tostring_ip()
{
return _ip;
}
bool operator==(InetAddr addr)
{
return _port == addr._port && _ip == addr._ip;
}
sockaddr *getaddr()
{
return (sockaddr *)&_addr;
}
socklen_t getlen()
{
return sizeof(_addr);
}
string stringaddr()
{
return tostring_ip() + ":" + tostring_port() + " ";
}
private:
uint16_t _port;
string _ip;
sockaddr_in _addr;
};
Protocol.hpp
#pragma once
#include <iostream>
#include <jsoncpp/json/json.h>
#include <string>
#include <functional>
#include "Socket.hpp"
#include "InetAddr.hpp"
using namespace SocketMoudule;
using namespace std;
class Request
{
public:
Request() {}
Request(int x, int y, char oper)
: _x(x), _y(y), _oper(oper)
{
}
string Serialize()
{
Json::Value data;
data["x"] = _x;
data["y"] = _y;
data["oper"] = _oper;
Json::FastWriter writer;
cout<<writer.write(data);
return writer.write(data);
}
void DeSerialize(string &message)
{
Json::Value data;
Json::Reader reader;
reader.parse(message, data);
_x = data["x"].asInt();
_y = data["y"].asInt();
_oper = data["oper"].asInt();
}
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)
{
}
string Serialize()
{
Json::Value data;
data["result"] = _result;
data["code"] = _code;
Json::FastWriter writer;
return writer.write(data);
}
void DeSerialize(string &message)
{
Json::Value data;
Json::Reader reader;
reader.parse(message, data);
_result = data["result"].asInt();
_code = data["code"].asBool();
}
void ShowResult()
{
cout << "result[" << _result << "]:code[" << _code << "]" << endl;
}
int Result() { return _result; }
bool Code() { return _code; }
void SetResult(int ret) { _result = ret; }
void SetCode(int f) { _code = f; }
private:
int _result;
int _code;
};
const string sep = "\\r\\n";
using func_t = function<Response(Request &)>;
class Protocol
{
public:
Protocol() {};
Protocol(func_t func)
: _func(func)
{
}
string EnCode(string &jsonstr)
{
size_t len = jsonstr.size();
return to_string(len) + sep + jsonstr + sep;
}
bool DeCode(string &buffer, string *package)
{
int pos = buffer.find(sep);
if (pos == string::npos)
return false;
string lenStr = buffer.substr(0, pos);
int lenTarget = lenStr.size() + stoi(lenStr) + 2 * sep.size();
if (buffer.size() < lenTarget)
return false;
*package = buffer.substr(pos + sep.size(), stoi(lenStr));
buffer.erase(0, lenTarget);
return true;
}
void GetRequest(shared_ptr<Socket> &sock, InetAddr &addr)
{
while (true)
{
string json_package; //?
int n = sock->Recv(&json_package);
if (n == 0)
{
LOG(Level::INFO) << "client " << addr.tostring_ip() << " exit";
break;
}
else if (n < 0)
{
LOG(Level::WARING) << "Recv fail";
break;
}
else
{
// 解报包
string json_str;
while (DeCode(json_package, &json_str))
{
// 反序列化
Request req;
req.DeSerialize(json_str);
// 处理
Response resp = _func(req);
// 序列化
string send_str = resp.Serialize();
// 加报头
send_str = EnCode(send_str);
// 发送
sock->Send(send_str);
}
}
}
}
bool GetResponse(shared_ptr<Socket> &sock, string *buffer, Response *rsp)
{
while (true)
{
int n = sock->Recv(buffer);
if (n == 0)
{
LOG(Level::WARING) << "server exit";
return false;
}
else if (n < 0)
{
LOG(Level::WARING) << "client Recv fail";
return false;
}
else
{
// 解包
string json_str;
if (!DeCode(*buffer, &json_str)) continue;
// 反序列化
rsp->DeSerialize(json_str);
return true;
}
}
}
string BuildRequestString(int x, int y, char oper)
{
Request req(x, y, oper);
string json_str = req.Serialize();
return EnCode(json_str);
}
private:
func_t _func;
};
NetCal.hpp
#pragma once
#include "Common.hpp"
#include "Protocol.hpp"
class NetCal
{
public:
Response Execute(Request &req)
{
Response resp(0, 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);
else
resp.SetResult(req.X() / req.Y());
break;
case '%':
if (req.Y() == 0)
resp.SetCode(2);
else
resp.SetResult(req.X() % req.Y());
default:
resp.SetCode(3);
break;
}
return resp;
}
private:
};
评论前必须登录!
注册