云计算百科
云计算领域专业知识百科平台

基于Tcp协议的应用层协议定制

前言:本文默认读者已掌握 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对象。
  • 回调数据处理方法:这个成员到后文再设计。

        在实际中会有很多客户端与服务器进行连接,通常需要并发的处理客户端需求,可以使用多进程、多线程、线程池、进程池等。这里我们就做简单一点,使用多进程完成并发功能。

        而子进程默认情况下是需要父进程进行等待的,这就会造成主进程阻塞,无法接收其他客户的请求,和单执行流没区别了。基于这样的问题有两种解决方法:

  • SIGCHIL信号(17号)的处理方法设为默认。
  • 主进程创建子进程a后再创建孙子进程b,此时a退出,让b进程去完成任务,b的父亲是a,a退出b进程成为了孤儿进程,会交给系统管理。主进程就不用管了。
  • 如果不理解第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

  • 从缓冲区找到标识符“\\r\\n”,如果找不到,说明报文不完整,返回false。
  • 从缓冲区找到标识符“\\r\\n”后,提取报头并算出完整报文的长度。如果大于缓冲区长度,说明缓冲区不够一个完整报文的长度,返回false。
  • 走到这里说明能取到一个完整的报文,然后把有效载荷提取出来,为方便下次提取,删除缓冲区一个报文的长度。返回true。
  • 如下:

    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;
    }

    客户端

  • 同样的需要传入命令行参数来指定服务器的IP和端口号,需要检查格式。
  • 创建Socket类对象,打开网络文件和与服务器进行连接。
  • 创建协议对象和用来接收返回结果的缓冲区。
  • 做一个死循环,进行构建请求,发送请求,接收响应,输出结果。如下:
  • 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;
    }

    非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!💕💕74c0781738354c71be3d62e05688fecc.png

    四、源码

    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:
    };

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 基于Tcp协议的应用层协议定制
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!