MATLAB TCP/IP客户端与NetAssist上位机双向通信实战指南
一、前言
在工业控制和数据采集领域,TCP/IP通信是最常用的网络通信协议之一。MATLAB作为强大的科学计算软件,与各种上位机软件(如NetAssist)进行通信可以实现数据采集、设备控制和实时监控等功能。本文将详细介绍如何使用MATLAB开发TCP/IP客户端,实现与NetAssist上位机的双向实时通信,并提供两个完整Demo程序。
二、环境准备
1. 软件需求
- MATLAB (建议R2016b或更新版本),我用的2023b
- NetAssist 网络调试助手
- Windows/Linux 操作系统,测试平台式Win11
2. 网络配置
确保两台设备在同一局域网内,或使用本地回环地址(127.0.0.1)进行本地测试。
三、MATLAB TCP客户端实现
1. 基础版TCP客户端(命令行交互)
function TCPClientDemo()
% 修复版TCP客户端Demo
% 修复问题:
% - ESC键无法中断连接的问题
% - 完善资源清理
% 使用方法:
% 1. 在NetAssist中启动TCP服务器(如8080端口)
% 2. 运行本程序
% 3. 按ESC键可正常退出程序
% 清空工作区
clearvars -except keepVariables;
% 连接参数
serverIP = '127.0.0.1';
serverPort = 8080;
% 创建TCP对象
try
tcpObj = tcpclient(serverIP, serverPort, 'Timeout', 5);
fprintf('成功连接到服务器 %s:%d\n', serverIP, serverPort);
catch ME
error('连接失败: %s', ME.message);
end
% 创建图窗用于ESC键检测
fig = figure('Name', 'TCP客户端 - 按ESC退出', ...
'NumberTitle', 'off', ...
'KeyPressFcn', @(src,event)keyPressCallback(src, event.Key));
% 共享变量
isRunning = true;
startTime = datetime('now');
fprintf('[%s] TCP客户端已启动 (按ESC退出)\n', datestr(now, 'HH:MM:SS'));
% 主通信循环
while isRunning && ishandle(fig)
try
% ========== 接收处理部分 ==========
if tcpObj.BytesAvailable > 0
data = read(tcpObj, tcpObj.BytesAvailable);
% 尝试解码为UTF-8中文
try
strData = native2unicode(data, 'GB2312');
fprintf('[%s] 收到: %s\n', datestr(now, 'HH:MM:SS'), strData);
% 收到数据后自动回复(实现双向通信)
replyMsg = ['已收到: ' strData];
sendData(tcpObj, replyMsg);
catch
fprintf('[%s] 收到二进制数据(Hex): %s\n', ...
datestr(now, 'HH:MM:SS'), dec2hex(data'));
end
end
% ========== 发送测试数据部分 ==========
% 示例:每隔5秒发送测试数据
if seconds(datetime('now') - startTime) >= 5
sendData(tcpObj, '测试消息 from MATLAB');
startTime = datetime('now'); % 重置计时
end
% 短暂暂停避免CPU过高
pause(0.1);
catch ME
fprintf('[%s] 错误: %s\n', datestr(now, 'HH:MM:SS'), ME.message);
isRunning = false;
end
end
% 清理资源
cleanupResources(tcpObj, fig);
% 嵌套函数:发送数据
function sendData(tcpObj, text)
utf8Data = unicode2native(text, 'GB2312');
write(tcpObj, utf8Data);
fprintf('[%s] 已发送: %s\n', datestr(now, 'HH:MM:SS'), text);
end
% 嵌套函数:按键回调
function keyPressCallback(~, key)
if strcmp(key, 'escape')
fprintf('[%s] 检测到ESC键,关闭连接...\n', datestr(now, 'HH:MM:SS'));
isRunning = false;
if ishandle(fig)
close(fig);
end
end
end
% 嵌套函数:清理资源
function cleanupResources(tcpObj, fig)
% 关闭TCP连接
if exist('tcpObj', 'var') && isvalid(tcpObj)
delete(tcpObj);
fprintf('[%s] TCP连接已关闭\n', datestr(now, 'HH:MM:SS'));
end
% 关闭图窗
if exist('fig', 'var') && ishandle(fig)
close(fig);
end
fprintf('[%s] 程序已退出\n', datestr(now, 'HH:MM:SS'));
end
end
2. GUI增强版TCP客户端
classdef TCPClientGUI < handle
properties
fig
tcpClient
ipEdit
portEdit
connectBtn
sendTextBtn
textInput
logText
isConnected = false
receiveTimer
end
methods
function obj = TCPClientGUI()
% 创建GUI界面
obj.fig = figure('Name', 'MATLAB TCP客户端(支持中文)', ...
'NumberTitle', 'off', ...
'Position', [100, 100, 600, 500], ...
'CloseRequestFcn', @(~,~)obj.onClose());
% IP地址输入
uicontrol('Style', 'text', 'String', '服务器IP:', ...
'Position', [20, 460, 80, 20]);
obj.ipEdit = uicontrol('Style', 'edit', ...
'String', '127.0.0.1', ...
'Position', [100, 460, 120, 20]);
% 端口输入
uicontrol('Style', 'text', 'String', '端口:', ...
'Position', [240, 460, 50, 20]);
obj.portEdit = uicontrol('Style', 'edit', ...
'String', '8080', ...
'Position', [290, 460, 60, 20]);
% 连接按钮
obj.connectBtn = uicontrol('Style', 'pushbutton', ...
'String', '连接', ...
'Position', [370, 460, 80, 20], ...
'Callback', @(~,~)obj.toggleConnect());
% 文本发送区域
uicontrol('Style', 'text', 'String', '发送文本:', ...
'Position', [20, 420, 80, 20]);
obj.textInput = uicontrol('Style', 'edit', ...
'String', '你好NetAssist', ...
'Position', [100, 420, 350, 20]);
obj.sendTextBtn = uicontrol('Style', 'pushbutton', ...
'String', '发送文本', ...
'Position', [460, 420, 100, 20], ...
'Callback', @(~,~)obj.sendTextData(), ...
'Enable', 'off');
% 日志显示区域(使用可显示中文的字体)
uicontrol('Style', 'text', 'String', '通信日志:', ...
'Position', [20, 340, 80, 20]);
obj.logText = uicontrol('Style', 'listbox', ...
'Position', [20, 20, 560, 320], ...
'String', {'等待连接...'}, ...
'FontName', 'Microsoft YaHei'); % 使用支持中文的字体
end
function toggleConnect(obj)
if obj.isConnected
obj.disconnect();
else
obj.connect();
end
end
function connect(obj)
ip = get(obj.ipEdit, 'String');
port = str2double(get(obj.portEdit, 'String'));
try
% 清理现有连接
if ~isempty(obj.tcpClient) && isvalid(obj.tcpClient)
delete(obj.tcpClient);
end
% 创建新连接(设置UTF-8编码)
obj.tcpClient = tcpclient(ip, port, ...
'Timeout', 5, ...
'ByteOrder', 'little-endian');
obj.isConnected = true;
set(obj.connectBtn, 'String', '断开');
set(obj.sendTextBtn, 'Enable', 'on');
% 启动数据接收
obj.startReceiving();
obj.logMessage(['已连接到 ' ip ':' num2str(port)]);
catch ME
obj.logMessage(['连接失败: ' ME.message]);
obj.isConnected = false;
set(obj.connectBtn, 'String', '连接');
set(obj.sendTextBtn, 'Enable', 'off');
end
end
function disconnect(obj)
obj.stopReceiving();
if ~isempty(obj.tcpClient) && isvalid(obj.tcpClient)
delete(obj.tcpClient);
obj.tcpClient = [];
end
obj.isConnected = false;
set(obj.connectBtn, 'String', '连接');
set(obj.sendTextBtn, 'Enable', 'off');
obj.logMessage('已断开连接');
end
function startReceiving(obj)
if ~isempty(obj.receiveTimer) && isvalid(obj.receiveTimer)
stop(obj.receiveTimer);
delete(obj.receiveTimer);
end
obj.receiveTimer = timer(...
'ExecutionMode', 'fixedRate', ...
'Period', 0.1, ...
'TimerFcn', @(~,~)obj.checkForData());
start(obj.receiveTimer);
end
function stopReceiving(obj)
if ~isempty(obj.receiveTimer) && isvalid(obj.receiveTimer)
stop(obj.receiveTimer);
delete(obj.receiveTimer);
obj.receiveTimer = [];
end
end
function checkForData(obj)
if ~obj.isConnected || isempty(obj.tcpClient) || ~isvalid(obj.tcpClient)
return;
end
try
if obj.tcpClient.BytesAvailable > 0
data = read(obj.tcpClient, obj.tcpClient.BytesAvailable);
% 尝试解码为UTF-8中文
try
strData = native2unicode(data, 'GB2312');
obj.logMessage(['收到: ' strData]);
catch
% 如果UTF-8解码失败,显示十六进制
obj.logMessage(['收到数据(Hex): ' dec2hex(data)']);
end
end
catch ME
obj.logMessage(['接收错误: ' ME.message]);
obj.disconnect();
end
end
function sendTextData(obj)
if ~obj.isConnected
obj.logMessage('错误: 未连接');
return;
end
textToSend = get(obj.textInput, 'String');
try
% 将中文转换为UTF-8字节序列
utf8Data = unicode2native(textToSend, 'GB2312');
write(obj.tcpClient, utf8Data);
obj.logMessage(['已发送: ' textToSend]);
catch ME
obj.logMessage(['发送失败: ' ME.message]);
obj.disconnect();
end
end
function logMessage(obj, message)
currentLog = get(obj.logText, 'String');
if ischar(currentLog)
currentLog = {currentLog};
end
newLog = [currentLog; {[datestr(now, 'HH:MM:SS') ' - ' message]}];
set(obj.logText, 'String', newLog);
set(obj.logText, 'Value', length(newLog));
drawnow;
end
function onClose(obj)
obj.disconnect();
delete(obj.fig);
end
function delete(obj)
obj.disconnect();
if ishandle(obj.fig)
delete(obj.fig);
end
end
end
end
四、NetAssist配置指南
1. 基本设置
- 打开NetAssist网络调试助手
- 选择"TCP Server"模式
- 设置本地主机地址为
127.0.0.1
- 设置监听端口为
8080
(与MATLAB客户端一致) - 点击"启动"按钮
2. 编码设置
- 在"接收设置"中选择"GB2312"编码
- 在"发送设置"中也选择"GB2312"编码
- 确保"十六进制显示"选项关闭(除非需要调试)
五、通信测试流程
1. 启动NetAssist服务器
按照上述配置启动TCP服务器,确保状态显示为"监听中"。
2. 运行MATLAB客户端
% 下面是二选一
% 运行基础版客户端
TCPClientDemo();
% 或者运行GUI版客户端
client = TCPClientGUI();
3. 测试双向通信
- 基础版TCP客户端:
- 在MATLAB中发送文本或数据
- 在NetAssist接收窗口查看消息
- 点击"发送"按钮
- 在MATLAB命令窗口或GUI日志区查看接收消息
- 在fig上面按esc退出程序
- GUI增强版TCP客户端:
- 按照上述配置启动GUI TCPNetAssist服务器,确保状态显示为"监听中"。
- 启动GUI TCP客户端,在NetAssist发送区输入消息
- 点击"发送文本"按钮
- 在MATLAB命令窗口或GUI日志区查看接收消息
- 按照上述配置启动GUI TCPNetAssist服务器,确保状态显示为"监听中"。
六、常见问题解决
1. 中文乱码问题
现象:接收到的中文显示为乱码
解决方案:
- 确保MATLAB和NetAssist使用相同的编码(建议GB2312)
- 检查字符编码转换函数:
% 发送时 utf8Data = unicode2native(textToSend, 'GB2312'); % 接收时 strData = native2unicode(data, 'GB2312');
2. 连接失败问题
现象:无法建立TCP连接
解决方案:
- 检查防火墙设置,确保端口未被阻止
- 确认NetAssist已正确启动TCP服务器
- 验证IP地址和端口号是否匹配
- 使用
ping
命令测试网络连通性
3. ESC键无法退出
现象:按ESC键无法终止程序
解决方案:
- 确保使用
ishandle()
检查图形窗口 - 检查按键回调函数是否正确定义:
'KeyPressFcn', @(src,event)keyPressCallback(src, event.Key)
七、应用扩展
1. 工业设备监控
通过TCP通信实时获取设备状态数据,在MATLAB中进行分析和可视化。
2. 实验数据采集
将实验仪器通过NetAssist接入,使用MATLAB进行数据采集和处理。
3. 自动化测试系统
构建自动化测试平台,通过TCP协议控制测试设备并收集测试结果。
八、总结
本文详细介绍了MATLAB与NetAssist实现TCP/IP双向通信的完整方案,提供了两个实用Demo:
- 基础命令行版:适合简单通信需求,支持基本的数据收发和中文处理
- GUI增强版:提供友好的用户界面,适合复杂的通信场景
通过本文的指导,您可以快速搭建起MATLAB与上位机软件的通信桥梁,为各种工业控制和数据采集应用奠定基础。
提示:在实际应用中,建议添加数据校验、超时重连等机制来提高通信可靠性。对于高频数据通信,可能需要优化缓冲区管理和数据处理算法。
现在,我们在本教程中学习了如何使用MATLAB连接万物。从而实现对外部世界的感知,充分认识这个有机与无机的环境,后期会持续分享MATLAB实用工程案例,为人类社会发展贡献一点微薄之力。🙌🙌🙌
如果你有任何问题,可以通过Q Group(945348278)加入鹏鹏小分队,期待与你思维的碰撞! 😘😘😘