QT下使用Asio库搭建一个简易网络服务器端

        在工作中,有时候会需要搭建一个网络服务器端来管理某些网络连接。QT自带的QSever可能不是那么好用,在工作过程中,也接触到了另一个C++的网络库Asio,并以此搭建一个简易的Server端。

        首先,下载好Asio所需的文件,博主这里使用的是Asio的代码文件,也可使用库文件,请按需使用。(Asio库包含在Boost库中,但是也可以单独使用)

        单独的Asio下载链接:Asio C++ Library

        由于我这里使用的是Asio的文件,我就将其文件放在工程下,在调用处引用头文件即可。

// 引用Asio头文件
#include "asio.hpp"
// 使用asio的命名空间,如果使用的是boost库则为 boost::asio::ip::tcp
using asio::ip::tcp;

        在使用过程中,我需要一个将套接字部分信息进行存储,因此,在文件中声明了两个类,分别为Session类和Sever类,分别对应服务器和会话。会话Session类中存储有设备IP、设备名称、设备ID信息。Session类头文件声明:

class Session : public QObject,public std::enable_shared_from_this<Session>
{
    Q_OBJECT
public:
    Session(tcp::socket socket, std::function<void(Session*)> on_disconnect);
    ~Session();

    void close();               // 关闭当前套接字,用于下线操作

    void getClinetInfo();       // 获取套接字信息,如IP、ID、设备名称

    tcp::socket socket_;                                // 网络套接字

    uint32_t getIndex();                                // 获取设备ID

    QString getIPaddress();                             // 获取IP地址

    QString getDeviceName();                            // 获取设备名称

signals:
    void firstConnection(QString index,QString name,QString ip);                             // 接收设备信息信号
    void recvFirstPackage();    // 接收到第一个数据包
    void clientOffline();       // 设备下线

private:
    void isConnectionAlive();
private:
    std::function<void(Session*)> on_disconnect_;       // 处理网络下线的函数
    QString clientIP;           // 设备IP
    uint32_t index;             // 设备ID
    QString deviceName;         // 设备名称
    bool infoReadSuccess;       // 设备信息是否读取成功

    char data[1024];            // 用于接收网络信息
    int maxLength = 1024;

    QThread *heartBeat = nullptr;                       // 检测存活线程
};

服务器类头文件声明:

class NetServer : public QObject
{
    Q_OBJECT
public:
    explicit NetServer(asio::io_context& io_context, short port,QObject *parent = nullptr);

    void disconnectClient(int clientIndex);         // 下线指定客户端
    void pauseListening();         // 暂停监听
    void resumListening();         // 继续开始监听

    int getSessionFromID(uint32_t index);           // 通过设备ID获取会话,用于发送和接收数据
    QVector<std::shared_ptr<Session>> getConnectList();                 // 获取连接列表

signals:
    void newConnection(QString index,QString name,QString ip);          // 新的连接到来
    void clientDisconnected(QString index,QString name,QString ip);     // 客户端下线

private:
    void doAccept();                               // 接收到来的客户端连接
    void removeSocket(Session* session);           // 下线操作

private:
    tcp::acceptor acceptor_;        // 用于处理接收客户端连接服务器
    tcp::socket socket_;            // 连接到来处理
    asio::io_context& io_context_;
    QVector<std::shared_ptr<Session>> sessionList;  // 列表列表
    mutable std::mutex sessionListMutex;
    bool is_listening_;
};

        因为在使用过程中,需要接收第一个包来获取当前连接信息,因此连接到来信号会在第一个包接受信息完毕后,才发送接收信号。

        看完上述函数声明及其变量声明后,接下来内容为函数实现部分。

        首先当服务器类被初始化完成后,服务器开始监听,递归调用doAccept函数进行监听操作

void NetServer::doAccept()
{
    if (is_listening_)
    {
        acceptor_.async_accept(socket_,[this](std::error_code ec)
        {
            if (!ec)
            {
                std::lock_guard<std::mutex> lock(sessionListMutex);
                auto new_session = std::make_shared<Session>(std::move(socket_), [this](Session* session) {
                        removeSocket(session);
            });     // 将Socket移入,将removeSocket函数传递过去用于处理客户端下线操作
                sessionList.append(new_session);
                new_session->getClinetInfo();
                connect(new_session.get(),&Session::firstConnection,this,&NetServer::newConnection);
                DebugLogger::log("New connection accepted");
                qDebug()<<__LINE__<<"New connection accepted";
            }
            doAccept();     // 递归调用,接收连接到来
        });
    }
}

        当有新的连接到来时,会生成一个新的Session会话类,当会话类第一包接收完毕后,发送Session类的firstConnection信号,然后再又信号槽将服务器类的newConnection发射,告诉调用服务器类的对象当前已有一个连接成功到来。且会调用会话类获取信息函数,接收第一个数据包完成对设备名称、设备ID的获取。

        当连接到来是,在构造函数中,会获取当前连接IP以及开启连接存活的心跳检测

Session::Session(tcp::socket socket, std::function<void (Session *)> on_disconnect)
    : socket_(std::move(socket)),on_disconnect_(on_disconnect)
{
    // 会话建立好后,默认获取IP
    try {
        clientIP = QString::fromStdString(socket_.remote_endpoint().address().to_string());
        connect(this,&Session::recvFirstPackage,this,[=]{
            if (heartBeat == nullptr)
            {
                heartBeat = QThread::create([&]{
                    isConnectionAlive();
                });
                connect(this,&Session::clientOffline,heartBeat,&QThread::deleteLater);
                heartBeat->start();
            }
        });
    }
    catch (const std::exception& e) {
        clientIP = "Unknown";
    }
}

        简易的心跳检测

void Session::isConnectionAlive()
{
    if (!socket_.is_open())         // 套接字不是打开状态则返回不做处理,防止触发下线信号
    {
        return;
    }
    asio::error_code ec;
    // 尝试发送空数据(非阻塞)
    socket_.write_some(asio::buffer(""), ec);
    if (ec)
    {
        emit clientOffline();       // 连接关闭则发送下线信号
        close();
        on_disconnect_(this);
    }
    QThread::sleep(2);              // 设置发送时间间隔
    isConnectionAlive();            // 重复调用自身往套接字写空数据包
}

        设备信息获取处,当信息获取完毕后,发送recvFirstPackage信号将当前会话的心跳检测开启,紧接着第二个信号则是将当前连接的设备ID、设备名称和IP发送给服务器端,再由服务器端发送给调用服务器端的对象使用。

void Session::getClinetInfo()
{
    auto self(shared_from_this());
    memset(data,0,maxLength);
    socket_.async_read_some(asio::buffer(data, maxLength),
                            [this, self](std::error_code ec, std::size_t length) {
        if (!ec)    // 没有错误信息说明接收成功
        {
            // 处理接收到的第一个数据包
            std::string ip_addr = socket_.remote_endpoint().address().to_string();

            index = data[4];
            deviceName = BitConvert::hexStringToValue(data + 7,length - 9);         //减去开头发送方、接收方、任务标识加结尾0x0d、0x0a

            emit recvFirstPackage();
            emit firstConnection(QString::number(index),deviceName,clientIP);
        }
    });
}

        会话类删除调用的函数disconnectClient()。当会话下线时,会调用该函数,并将其从列表中删除。

void NetServer::disconnectClient(int clientIndex)
{
    std::lock_guard<std::mutex> lock(sessionListMutex);

    for (auto iter : qAsConst(sessionList))
    {
        if (iter.get()->getIndex() == (uint32_t)clientIndex)    // 找到设备ID后关闭套接字连接,并将列表中的会话删除
        {
            iter.get()->close();
            removeSocket(iter.get());
            break;
        }
    }
}

        当函数下线时,需要关闭当前会话,调用close函数

void Session::close()
{
    std::error_code ec;
    socket_.shutdown(tcp::socket::shutdown_both, ec);   // 关闭套接字
    if (ec)
    {
        DebugLogger::log("Shutdown error for " + clientIP + ": "  + "Error code is:" + QString::number(ec.value()) + ".Error info is:" + QString::fromStdString(ec.message()));
    }
    socket_.close(ec);
    if (ec)
    {
        DebugLogger::log("Close error for " + clientIP + ": " + "Error code is:" + QString::number(ec.value()) + ".Error info is:" + QString::fromStdString(ec.message()));
    }
}

        服务器开始监听和恢复监听函数

void NetServer::pauseListening()
{
    is_listening_ = false;
    DebugLogger::log("Server paused listening......");
}

void NetServer::resumListening()
{
    if (!is_listening_)
    {
        is_listening_ = true;
        DebugLogger::log("Server resumed listening");
        doAccept();
    }
}

        当上述步骤做完后,一个简易的使用Asio库进行编写的网络服务器端就写好了,能够正常识别到连接的到来,通过接收首包获取设备信息(设备ID、设备名称)。如果没有获取信息的必要,可在连接到来后,即刻发送连接到来信号即可。

### C++在游戏开发和对战系统中的应用 C++作为一种高效且灵活的编程语言,在游戏开发领域具有不可替代的地位。由于其高性能特性和强大的生态系统支持,它被广泛应用于各种类型的游戏中,尤其是那些需要实时处理大量数据的游戏[^1]。 #### 多人在线游戏的功能实现 许多现代游戏依赖于多人在线功能,而这些功能通常涉及复杂的网络通信机制。C++在网络编程方面的优势使其成为实现这类功能的理想选择。通过使用成熟的C++网络(如Boost.Asio),开发者可以轻松实现诸如玩家之间的实时交互、服务器端的数据管理和客户端的状态更新等功能[^2]。 #### 开发工具与环境配置 为了构建基于C++的游戏对战系统,开发者可以选择多种高效的开发工具。例如,服务端可以通过Visual Studio 2019这样的集成开发环境来完成逻辑设计;而在客户端部分,则可利用Qt Creator配合特定版本的MinGW编译器进行图形界面的开发[^4]。这种组合方式不仅能提高开发效率,还便于维护跨平台兼容性。 #### 网络通信基础知识及其实践意义 尽管初学者可能觉得网络通信较为抽象难懂,但实际上只要遵循科学合理的规划步骤并逐步实施,就能有效降低难度门槛。具体而言,理解基本原理之后再着手搭建简易版消息传递框架不失为一种良好开端[^5]。下面给出一段简单示例代码用于展示如何创建TCP连接: ```cpp #include <boost/asio.hpp> using boost::asio::ip::tcp; int main() { try { boost::asio::io_context io_context; tcp::resolver resolver(io_context); auto endpoints = resolver.resolve("localhost", "8080"); tcp::socket socket(io_context); boost::asio::connect(socket, endpoints); std::string message = "Hello Server!"; boost::asio::write(socket, boost::asio::buffer(message)); } catch (std::exception& e) { std::cerr << e.what() << "\n"; } } ``` 此段程序展示了怎样借助Boost.Asio建立到本地主机上运行的服务进程的一个TCP链接,并发送一条测试字符串过去。 #### 总结 综上所述,无论是从技术层面还是实际操作角度来看,采用C++来进行包含对战要素在内的各类电子竞技类项目的研发都是可行且推荐的做法。凭借该语言本身所具备的强大特性以及丰富的第三方扩展组件的支持,完全可以满足当今市场上主流网络游戏产品对于性能和技术指标的要求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值