mfc异步TCP Client通信向主线程发送接收消息

ops/2024/11/17 23:55:00/

通信是个基础的问题,通常要具有连接、断开及消息的发送和接收,一个功能完备的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::stringCT2A 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;}});

http://www.ppmy.cn/ops/117787.html

相关文章

【UE5】将2D切片图渲染为体积纹理,最终实现使用RT实时绘制体积纹理【第二篇-着色器制作】

在上一篇文章中&#xff0c;我们已经理顺了实现流程。 接下来&#xff0c;我们将在UE5中&#xff0c;从头开始一步一步地构建一次流程。 通过这种方法&#xff0c;我们可以借助一个熟悉的开发环境&#xff0c;使那些对着色器不太熟悉的朋友们更好地理解着色器的工作原理。 这篇…

第九节 Opencv自带颜色表操作

知识点&#xff1a;Look Up lTable&#xff08;LUT&#xff09;查找表 了解LUT查找表的作用与用法&#xff0c;代码实现与API介绍 -applyColorMap&#xff08;src,dst,COLORMAP&#xff09; -src表示输入图像 -dst表示输出图像 匹配到的颜色LUT&#xff0c;Opencv支持13种…

网络高级day03(Http)

目录 【1】HTTP简介 【2】 HTTP特点 【3】 HTTP协议格式 1》客户端请求消息格式 1> 请求行 2> 请求头 3> 空行 4> 请求数据 2》服务器响应消息格式 【1】HTTP简介 HTTP协议是Hyper Text Transfer Protocol &#xff08;超文本传输协议&#xff09;的缩写&a…

element ui 精确控制日期控件 date-picker

https://github.com/element-plus/element-plus/discussions/17378 -- 某组件 xxx.vue ... <el-date-pickerv-model"timeRange"type"daterange"range-separator"-"start-placeholder"开始日期"end-placeholder"结束日期"…

利用QEMU安装一台虚拟机的三种方法

文章目录 宿主机的选择方法一&#xff1a;直接用qemu源码安装步骤1&#xff1a;下载好qemu源码&#xff0c;这里我们用qemu-5.1.0步骤2&#xff1a;编译步骤3&#xff1a;创建一个系统盘步骤4&#xff1a;用步骤2编译的qemu-system-x86_64 启动一台Linux虚拟机步骤5&#xff1a…

架构设计笔记-5-软件工程基础知识

知识要点 按软件过程活动&#xff0c;将软件工具分为软件开发工具、软件维护工具、软件管理和软件支持工具。 软件开发工具&#xff1a;需求分析工具、设计工具、编码与排错工具。 软件维护工具&#xff1a;版本控制工具、文档分析工具、开发信息库工具、逆向工程工具、再工…

Stream流将List转换成Map

一、前言 通常会需要使用到对象和Map互相转换的开发场景,下面这篇文章主要给大家介绍了关于java对象list使用stream根据某一个属性转换成map的3种方式,需要的朋友可以参考下。 二、将List转换为Map&#xff0c;键为某个属性&#xff0c;值为对象本身 List<User> userL…

js中正则表达式中【exec】用法深度解读

exec() 是 JavaScript 正则表达式对象&#xff08;RegExp&#xff09;中的一个方法&#xff0c;用于匹配字符串中的特定模式&#xff0c;并返回匹配结果。它比 test() 和 match() 更强大&#xff0c;因为它不仅仅返回匹配成功与否&#xff0c;还返回匹配的具体内容及其相关信息…