当前位置: 首页 > article >正文

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;
		}
		});

http://www.kler.cn/news/318522.html

相关文章:

  • wpf中如何访问控件和生成使用事件?
  • FME学习笔记
  • Leetcode 106. 从中序与后序遍历序列构造二叉树
  • 计算机毕业设计之:基于微信小程序的中药材科普系统(源码+文档+讲解)
  • vue3/Element/Tabs 标签页的使用与详解
  • 基于Qt5.12.2开发 MQTT客户端调试助手
  • Go基础学习04-变量重声明;类型转换;类型断言;Unicode代码点;类型别名;潜在类型
  • MobileNetV2: Inverted Residuals and Linear Bottlenecks
  • vue2和vue3页面加自定义水印(组件化)
  • 【计算机网络 - 基础问题】每日 3 题(二十)
  • SpringBoot的应用
  • 现代桌面UI框架科普及WPF入门1
  • Mac电脑上最简单安装Python的方式
  • Java:文件操作
  • [spring]用MyBatis XML操作数据库 其他查询操作 数据库连接池 mysql企业开发规范
  • WPF入门教学十四 命令与ICommand接口
  • OpenAI GPT o1技术报告阅读(5)-安全性对齐以及思维链等的综合评估与思考
  • Servlet入门:服务端小程序的初试(自己学习整理的资料)
  • R包:gplots经典热图
  • CentOS中使用Docker运行mysql并挂载本地目录
  • 滚雪球学SpringCloud[9.3讲]:微服务监控与运维详解
  • redis 快速入门
  • Serilog文档翻译系列(五) - 编写日志事件
  • [利用python进行数据分析01] “来⾃Bitly的USA.gov数据” 分析出各个地区的 windows和非windows用户
  • vue2 实现简易版的模糊查询功能
  • 大数据新视界 --大数据大厂之大数据实战指南:Apache Flume 数据采集的配置与优化秘籍
  • RabbitMQ 高级特性——发送方确认
  • 实现信创Linux桌面录制成MP4(源码,银河麒麟、统信UOS)
  • debain 登录后提示符显示ip
  • jekyll相关的技术点