【MATLAB TCP/IP客户端与NetAssist上位机双向通信实战指南】

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. 基本设置

  1. 打开NetAssist网络调试助手
  2. 选择"TCP Server"模式
  3. 设置本地主机地址为127.0.0.1
  4. 设置监听端口为8080(与MATLAB客户端一致)
  5. 点击"启动"按钮
    在这里插入图片描述

2. 编码设置

  1. 在"接收设置"中选择"GB2312"编码
  2. 在"发送设置"中也选择"GB2312"编码
  3. 确保"十六进制显示"选项关闭(除非需要调试)

五、通信测试流程

1. 启动NetAssist服务器

按照上述配置启动TCP服务器,确保状态显示为"监听中"。

2. 运行MATLAB客户端

% 下面是二选一
% 运行基础版客户端
TCPClientDemo();

% 或者运行GUI版客户端
client = TCPClientGUI();

3. 测试双向通信

  1. 基础版TCP客户端
    • 在MATLAB中发送文本或数据
    • 在NetAssist接收窗口查看消息
      在这里插入图片描述
  • 点击"发送"按钮
  • 在MATLAB命令窗口或GUI日志区查看接收消息
  • 在fig上面按esc退出程序
    在这里插入图片描述
  1. GUI增强版TCP客户端
    • 按照上述配置启动GUI TCPNetAssist服务器,确保状态显示为"监听中"。
      在这里插入图片描述
    • 启动GUI TCP客户端,在NetAssist发送区输入消息
    • 点击"发送文本"按钮
    • 在MATLAB命令窗口或GUI日志区查看接收消息

在这里插入图片描述

六、常见问题解决

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:

  1. 基础命令行版:适合简单通信需求,支持基本的数据收发和中文处理
  2. GUI增强版:提供友好的用户界面,适合复杂的通信场景

通过本文的指导,您可以快速搭建起MATLAB与上位机软件的通信桥梁,为各种工业控制和数据采集应用奠定基础。

提示:在实际应用中,建议添加数据校验、超时重连等机制来提高通信可靠性。对于高频数据通信,可能需要优化缓冲区管理和数据处理算法。

现在,我们在本教程中学习了如何使用MATLAB连接万物。从而实现对外部世界的感知,充分认识这个有机与无机的环境,后期会持续分享MATLAB实用工程案例,为人类社会发展贡献一点微薄之力。🙌🙌🙌

如果你有任何问题,可以通过Q Group(945348278)加入鹏鹏小分队,期待与你思维的碰撞! 😘😘😘

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

2345VOR

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值