共计 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