mfc异步TCP Client通信向主线程发送接收消息
通信是个基础的问题,通常要具有连接、断开及消息的发送和接收,一个功能完备的TCP类应该具有连接断开和消息接收的对外接口。现在有一个项目通过TCP实现进程间通信,通过解析发送的字符串识别、执行彼此的命令。现使用c++ Asio独立库实现功能,代码如下。
#pragma once
#include <afxwin.h> // MFC core and standard components
#include <iostream>
#include <string>
#include <thread>
#include <functional>
#include <asio.hpp>
class TCPClient : public std::enable_shared_from_this<TCPClient> {
private:
asio::io_context io_context;
asio::ip::tcp::socket socket{ io_context };
std::function<void(const CString&)> dataReceivedCallback;
std::function<void()> disconnectedCallback; // 断开连接回调
std::array<char, 10240> buffer;
bool connected = false;
private:
std::string CStringToStdString(CString cstr)
{
// 将 CString 转换为 std::string
CT2A ascii(cstr); // 转换为 ANSI 字符串
return std::string(ascii);
}
char* CStringToChar(CString& cstr)
{
return (char*)CStringToStdString(cstr).data();
}
public:
TCPClient()
: socket(io_context) { }
bool Connect(const CString& serverIP, int port)
{
return Connect(CStringToStdString(serverIP), port);
}
bool Connect(const std::string& serverIP, int port) {
if (IsConnected()) return true;
try {
asio::ip::tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve(serverIP, std::to_string(port));
asio::connect(socket, endpoints);
connected = true;
StartReceiving();
}
catch (const std::exception& e) {
std::cerr << "Connection error: " << e.what() << std::endl;
return false;
}
}
void Disconnect() {
if (connected) {
asio::error_code ec;
socket.shutdown(asio::ip::tcp::socket::shutdown_both, ec);
socket.close(ec);
connected = false;
if (disconnectedCallback) {
disconnectedCallback();
}
}
}
bool Send(const CString& data) {
return Send(CStringToStdString(data));
}
bool Send(const std::string& data) {
if (!IsConnected()) return false;
try {
asio::write(socket, asio::buffer(data));
return true;
}
catch (const std::exception& e) {
std::cerr << "Send error: " << e.what() << std::endl;
return false;
}
}
void SetDataReceivedCallback(std::function<void(const CString&)> callback) {
dataReceivedCallback = callback;
}
void SetDisconnectedCallback(std::function<void()> callback) {
disconnectedCallback = callback;
}
bool IsConnected() const {
return connected;
}
private:
void StartReceiving() {
Receive(); // 开始接收数据
std::thread([this]() { io_context.run(); }).detach(); // 在新线程中运行事件循环
}
void Receive() {
auto self = shared_from_this();
socket.async_read_some(asio::buffer(buffer),
[this, self](const asio::error_code& ec, size_t len) {
if (!ec) {
std::string data(buffer.data(), len);
if (dataReceivedCallback) {
// 调用回调函数,将接收到的数据传递给主线程中的类
CString receivedData(data.c_str());
dataReceivedCallback(receivedData);
}
Receive(); // 继续接收数据
}
else {
Disconnect();
}
});
}
};
上面的Client类作为对外接口的回调函数dataReceivedCallback、dataReceivedCallback均在子线程中执行,现在不想改动该类,想在外部处理实现回调函数在主线程中执行的功能(主要是设计刷新界面)。
关于子、主线程通信,QT的信号槽可以相对方便地实现该功能,至于MFC,一般通过PostMessage加上自定义消息实现,虽然麻烦点,但也能实现功能。现在主线程类中实现子线程传消息到主线程。
1. 自定义消息
// 在某个全局头文件中
#define WM_USER_DATA_RECEIVED (WM_USER + 1)
#define WM_USER_TCP_DISCONNECTED (WM_USER + 2)
2. 响应函数声明
下面的函数将会在主线程执行
//声明
protected:
afx_msg LRESULT OnDataReceived(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnTcpDisconnected(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
private:
void onTcpClientDataReceived(CString data);
3. 响应函数定义
LRESULT CMFCApplication6Dlg::OnDataReceived(WPARAM wParam, LPARAM lParam) {
CString* pData = reinterpret_cast<CString*>(lParam);
if (pData != nullptr) {
// 处理接收到的数据
onTcpClientDataReceived(pData->GetString());
delete pData; // 清理内存
pData = nullptr;
}
else
{
AfxMessageBox(_T("pData is nullptr!"));
}
return 0; // 返回值可以是0或者其他值
}
LRESULT CMFCApplication6Dlg::OnTcpDisconnected(WPARAM wParam, LPARAM lParam)
{
threadInfo();
return 0;
}
4. 建立消息与响应应函数的映射
BEGIN_MESSAGE_MAP(CMFCApplication6Dlg, CDialogEx)
ON_MESSAGE(WM_USER_DATA_RECEIVED, &CMFCApplication6Dlg::OnDataReceived)
ON_MESSAGE(WM_USER_TCP_DISCONNECTED, &CMFCApplication6Dlg::OnTcpDisconnected)
END_MESSAGE_MAP()
5. 为消息接收、连接断开建立回调函数,并在回调函数中发送自定义消息实现子、主线程通信
需要注意的是,PostMessage第三个参数就是传参的(没怎么研究PostMessage,在此不求甚解),消息接收函数中有个参数,而连接断开不需要参数,第三个参数置为0即可。
std::shared_ptr<TCPClient> m_client = std::make_shared<TCPClient>();
m_client->SetDataReceivedCallback([&](const CString& data)
{
CString* pData = new CString(data);
BOOL result = PostMessage(WM_USER_DATA_RECEIVED, 0, (LPARAM)pData);
if (!result) {
// 处理消息发送失败
std::cerr << "PostMessage failed: " << GetLastError() << std::endl;
}
});
m_client->SetDisconnectedCallback([&]() {
BOOL result = PostMessage(WM_USER_TCP_DISCONNECTED, 0, 0);
if (!result) {
// 处理消息发送失败
std::cerr << "PostMessage failed: " << GetLastError() << std::endl;
}
});