关于c++:BoostAsyncSocket-异步反弹通信案例

4次阅读

共计 7703 个字符,预计需要花费 20 分钟才能阅读完成。

Boost 利用 ASIO 框架实现一个跨平台的反向远控程序,该远控反对保留套接字,当有套接字连入时,主动存储到 map 容器,当客户下线时主动从 map 容器中移除,当咱们须要与特定客户端通信时,只须要指定客户端 ID 号即可。

AsyncTcpServer

服务端首先定义 CEventHandler 类并继承自 CAsyncTcpServer::IEventHandler 接口,该类内须要咱们实现三个办法,办法 ClientConnected 用于在客户端连贯时触发,办法 ClientDisconnect 则是在登录客户端来到时触发,而当客户端有数据发送过去时则 ReceiveData 办法则会被触发。

办法 ClientConnected 当被触发时主动将 clientId 客户端 Socket 套接字放入到 tcp_client_id 全局容器内存储起来,而当 ClientDisconnect 客户端退出时,则间接遍历这个迭代容器,找到序列号并通过 tcp_client_id.erase 将其剔除;

// 客户端连贯时触发
virtual void ClientConnected(int clientId)
{
    // 将登录客户端退出到容器中
    tcp_client_id.push_back(clientId);
}
  
// 客户端退出时触发
virtual void ClientDisconnect(int clientId)
{
    // 将登出的客户端从容器中移除
    vector<int>::iterator item = find(tcp_client_id.begin(), tcp_client_id.end(), clientId);
    if (item != tcp_client_id.cend())
        tcp_client_id.erase(item);
}

ReceiveData 一旦收到数据,则间接将其打印输出到屏幕,即可实现客户端参数接管的目标;

// 客户端获取数据
virtual void ReceiveData(int clientId, const BYTE* data, size_t length)
{
    std::cout << std::endl;
    PrintLine(80);
    std::cout << data << std::endl;
    PrintLine(80);
    std::cout << "[Shell] #";
}

绝对于接收数据而言,发送数据则是通过同步的形式进行,当咱们须要发送数据时,只须要将数据字符串放入到一个 BYTE* 字节数组中,并在调用 tcpServer.Send 时将所需参数,套接字 ID,缓冲区 Buf 数据,以及长度传递即可实现将数据发送给指定的客户端;

// 同步发送数据到指定的线程中
void send_message(CAsyncTcpServer& tcpServer, int clientId, std::string message, int message_size)
{
    // 获取长度
    BYTE* buf = new BYTE(message_size + 1);
    memset(buf, 0, message_size + 1);

    for (int i = 0; i < message_size; i++)
    {buf[i] = message.at(i);
    }
    tcpServer.Send(clientId, buf, message_size);
}

AsyncTcpClient

客户端首先咱们封装实现 AsyncConnect 类,该类内次要实现两个性能,其中 aysnc_connect 办法用于实现异步连贯到服务端,而 port_is_open 办法则用于验证服务器特定端口是否凋谢,在调用 boost::bind 绑定套接字时传入 &AsyncConnect::timer_handle 设置一个超时等待时间。

进入到 main 主函数中,通过 while 循环让程序能够始终运行上来,并通过 hander.aysnc_connect(ep, 5000) 每隔 5 秒验证是否连贯胜利,如果连贯了则进入内循环,通过hander.port_is_open("127.0.0.1", 10000, 5000) 验证端口是否凋谢,这次要是为了保障服务端断开后客户端仍然可能跳转到内部循环持续期待服务端上线。

案例演示

首先运行服务端程序,接着运行多个客户端,即可实现主动上线;

当用户须要通信时,只须要指定 id 序号到指定的 Socket 套接字编号即可;

源代码

服务端代码

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include "AsyncTcpServer.h"
#include <string>
#include <vector>
#include <iostream>
#include <boost/tokenizer.hpp>

using namespace std;

// 存储以后客户端的 ID 号
std::vector<int> tcp_client_id;

// 输入特定长度的行
void PrintLine(int line)
{for (int x = 0; x < line; x++)
    {printf("-");
    }
    printf("\n");
}

class CEventHandler : public CAsyncTcpServer::IEventHandler
{
public:
    // 客户端连贯时触发
    virtual void ClientConnected(int clientId)
    {
        // 将登录客户端退出到容器中
        tcp_client_id.push_back(clientId);
    }

    // 客户端退出时触发
    virtual void ClientDisconnect(int clientId)
    {
        // 将登出的客户端从容器中移除
        vector<int>::iterator item = find(tcp_client_id.begin(), tcp_client_id.end(), clientId);
        if (item != tcp_client_id.cend())
            tcp_client_id.erase(item);
    }

    // 客户端获取数据
    virtual void ReceiveData(int clientId, const BYTE* data, size_t length)
    {
        std::cout << std::endl;
        PrintLine(80);
        std::cout << data << std::endl;
        PrintLine(80);
        std::cout << "[Shell] #";
    }
};

// 同步发送数据到指定的线程中
void send_message(CAsyncTcpServer& tcpServer, int clientId, std::string message, int message_size)
{
    // 获取长度
    BYTE* buf = new BYTE(message_size + 1);
    memset(buf, 0, message_size + 1);

    for (int i = 0; i < message_size; i++)
    {buf[i] = message.at(i);
    }
    tcpServer.Send(clientId, buf, message_size);
}

int main(int argc, char* argv[])
{CAsyncTcpServer tcpServer(10, 10000);
    CEventHandler eventHandler;
    tcpServer.AddEventHandler(&eventHandler);
    std::string command;

    while (1)
    {std::cout << "[Shell] #";
        std::getline(std::cin, command);

        if (command.length() == 0)
        {continue;}
        else if (command == "help")
        {printf("_            ____             _        _   \n");
            printf("| |   _   _  / ___|  ___   ___| | _____| |_  \n");
            printf("| |  | | | | \\___ \\ / _ \\ / __| |/ / _ \\ __| \n");
            printf("| |__| |_| |  ___) | (_) | (__|   <  __/ |_  \n");
            printf("|_____\\__, | |____/ \\___/ \\___|_|\\_\\___|\\__| \n");
            printf("|___/                                 \n\n");
            printf("Usage: LySocket \t PowerBy: LyShark.com \n");
            printf("Optional: \n\n");
            printf("\t ShowSocket        输入所有 Socket 容器 \n");
            printf("\t GetCPU            获取 CPU 数据 \n");
            printf("\t GetMemory         获取内存数据 \n");
            printf("\t Exit              退出客户端 \n\n");
        }
        else
        {// 定义分词器: 定义宰割符号为[逗号, 空格]
            boost::char_separator<char> sep(", --");
            typedef boost::tokenizer<boost::char_separator<char>> CustonTokenizer;
            CustonTokenizer tok(command, sep);

            // 将分词后果放入 vector 链表
            std::vector<std::string> vecSegTag;
            for (CustonTokenizer::iterator beg = tok.begin(); beg != tok.end(); ++beg)
            {vecSegTag.push_back(*beg);
            }
            // 解析  # ShowSocket
            if (vecSegTag.size() == 1 && vecSegTag[0] == "ShowSocket")
            {PrintLine(80);
                printf("客户 ID \t 客户 IP 地址 \t 客户端口 \n");
                PrintLine(80);
                for (int x = 0; x < tcp_client_id.size(); x++)
                {std::cout << tcp_client_id[x] << "\t"
                        << tcpServer.GetRemoteAddress(tcp_client_id[x]) << "\t"
                        << tcpServer.GetRemotePort(tcp_client_id[x]) << std::endl;
                }
                PrintLine(80);
            }

            // 解析  # GetCPU --id 100
            if (vecSegTag.size() == 3 && vecSegTag[0] == "GetCPU")
            {char *id = (char *)vecSegTag[2].c_str();
                send_message(tcpServer, atoi(id), "GetCPU", strlen("GetCPU"));
            }

            // 解析  # GetMemory --id 100
            if (vecSegTag.size() == 3 && vecSegTag[0] == "GetMemory")
            {char* id = (char*)vecSegTag[2].c_str();
                send_message(tcpServer, atoi(id), "GetMEM", strlen("GetMEM"));
            }

            // 解析  # Exit --id 100
            if (vecSegTag.size() == 3 && vecSegTag[0] == "Exit")
            {char* id = (char*)vecSegTag[2].c_str();
                send_message(tcpServer, atoi(id), "Exit", strlen("Exit"));
            }
        }
    }
    return 0;
}

客户端代码

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include <iostream>
#include <string>
#include <boost/asio.hpp> 
#include <boost/bind.hpp>  
#include <boost/array.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>  
#include <boost/noncopyable.hpp>

using namespace std;
using boost::asio::ip::tcp;

// 异步连贯地址与端口
class AsyncConnect
{
public:
    AsyncConnect(boost::asio::io_service& ios, tcp::socket &s)
        :io_service_(ios), timer_(ios), socket_(s) {}

    // 异步连贯
    bool aysnc_connect(const tcp::endpoint &ep, int million_seconds)
    {
        bool connect_success = false;

        // 异步连贯, 当连贯胜利后将触发 connect_handle 函数
        socket_.async_connect(ep, boost::bind(&AsyncConnect::connect_handle, this, _1, boost::ref(connect_success)));

        // 设置一个定时器  million_seconds 
        timer_.expires_from_now(boost::posix_time::milliseconds(million_seconds));
        bool timeout = false;

        // 异步期待 如果超时则执行 timer_handle
        timer_.async_wait(boost::bind(&AsyncConnect::timer_handle, this, _1, boost::ref(timeout)));
        do
        {
            // 期待异步操作实现
            io_service_.run_one();
            // 判断如果 timeout 没超时, 或者是连贯建设了, 则不再期待
        } while (!timeout && !connect_success);
        timer_.cancel();
        return connect_success;
    }

    // 验证服务器端口是否凋谢
    bool port_is_open(std::string address, int port, int timeout)
    {
        try
        {
            boost::asio::io_service io;
            tcp::socket socket(io);
            AsyncConnect hander(io, socket);
            tcp::endpoint ep(boost::asio::ip::address::from_string(address), port);
            if (hander.aysnc_connect(ep, timeout))
            {io.run();
                io.reset();
                return true;
            }
            else
            {return false;}
        }
        catch (...)
        {return false;}
    }

private:
    // 如果连贯胜利了, 则 connect_success = true
    void connect_handle(boost::system::error_code ec, bool &connect_success)
    {if (!ec)
        {connect_success = true;}
    }

    // 定时器超时 timeout = true
    void timer_handle(boost::system::error_code ec, bool &timeout)
    {if (!ec)
        {socket_.close();
            timeout = true;
        }
    }
    boost::asio::io_service &io_service_;
    boost::asio::deadline_timer timer_;
    tcp::socket &socket_;
};

int main(int argc, char * argv[])
{
    try
    {
        boost::asio::io_service io;
        tcp::socket socket(io);
        AsyncConnect hander(io, socket);
        boost::system::error_code error;
        tcp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 10000);

        // 循环验证是否在线
    go_:  while (1)
    {
        // 验证是否连贯胜利, 并定义超时工夫为 5 秒
        if (hander.aysnc_connect(ep, 5000))
        {io.run();
            std::cout << "已连贯到服务端." << std::endl;

            // 循环接管命令
            while (1)
            {
                // 验证地址端口是否凋谢, 默认期待 5 秒
                bool is_open = hander.port_is_open("127.0.0.1", 10000, 5000);

                // 客户端接管数据包
                boost::array<char, 4096> buffer = {0};

                // 如果在线则继续执行
                if (is_open == true)
                {socket.read_some(boost::asio::buffer(buffer), error);

                    // 判断收到的命令是否为 GetCPU
                    if (strncmp(buffer.data(), "GetCPU", strlen("GetCPU")) == 0)
                    {
                        std::cout << "获取 CPU 参数并返回给服务端." << std::endl;
                        socket.write_some(boost::asio::buffer("CPU: 15 %"));
                    }

                    // 判断收到的命令是否为 GetMEM
                    if (strncmp(buffer.data(), "GetMEM", strlen("GetMEM")) == 0)
                    {
                        std::cout << "获取 MEM 参数并返回给服务端." << std::endl;
                        socket.write_some(boost::asio::buffer("MEM: 78 %"));
                    }

                    // 判断收到的命令是否为终止程序
                    if (strncmp(buffer.data(), "Exit", strlen("Exit")) == 0)
                    {
                        std::cout << "终止客户端." << std::endl;
                        return 0;
                    }
                }
                else
                {
                    // 如果连贯失败, 则跳转到期待环节
                    goto go_;
                }
            }
        }
        else
        {std::cout << "连贯失败, 正在从新连贯." << std::endl;}
    }
    }
    catch (...)
    {return false;}

    std::system("pause");
    return 0;
}

我的项目地址

https://github.com/lyshark/BoostAsyncSocket

正文完
 0