上位机读取udp的报文是实时的报文吗

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了上位机读取udp的报文是实时的报文吗相关的知识,希望对你有一定的参考价值。

QT上位机与PLC通信
Fins/UDP通信
写格式
写反馈
读格式
读反馈
进制间转换函数
int-to-16进制
字符串-to-16进制
16进制-to-字符串
Fins/UDP通信
读写函数
读写反馈
Fins/UDP通信
编写上位机通过发送Fins/UDP命令读写plc内部数据,可以用于上位机socket通讯测试,员工操作监控,运行日志打印。Fins/UDP是以十六进制字符发送命令,所以所有字符在发送前都需要将其转换成十六进制。
写格式
固定头:800002 不变
plc节点:006E00 地址:XXX.XXX.XXX.110
PC节点:00F400 地址:XXX.XXX.XXX.244
结束:00 不变
写代码:0102 不变
D区代码:82 不变
D区地址:01A600 根据需求修改,十六进制数据
写入长度:0001 根据需求修改,十六进制数据
写入数据:0212

登录后复制

写反馈
固定头:C00002 不变
PC节点:00F400 地址:XXX.XXX.XXX.244
plc节点:006E00 地址:XXX.XXX.XXX.110
结束:00 不变
写代码:0102 不变
写入成功:0000
登录后复制
读格式
固定头:800002 不变
plc节点:006E00 地址:XXX.XXX.XXX.110
PC节点:00F400 地址:XXX.XXX.XXX.244
结束:00 不变
读代码:0101 不变
D区代码:82 不变
D区地址:01A600 根据需求修改,十六进制数据
读取长度:0002 根据需求修改,十六进制数据

登录后复制
读反馈
固定头:C00002 不变
PC节点:00F400 地址:XXX.XXX.XXX.244
plc节点:006E00 地址:XXX.XXX.XXX.110
结束:00 不变
读代码:0101 不变
plc状态:0000 自动变换
plc反馈的数据:66664006
登录后复制
进制间转换函数
int型与字符串转16进制,16进制转字符串。

int-to-16进制
int num=666;
QString Dnum = QString::number(num,16);
登录后复制
字符串-to-16进制
QByteArray ReadPLC::QString2Hex(QString str)

QByteArray senddata;
int hexdata,lowhexdata;
int hexdatalen = 0;
int len = str.length();
senddata.resize(len/2);
char lstr,hstr;
for(int i=0; i<len; )

hstr=str[i].toLatin1();
if(hstr == ' ')

i++;
continue;

i++;
if(i >= len)
break;
lstr = str[i].toLatin1();
hexdata = ConvertHexChar(hstr);
lowhexdata = ConvertHexChar(lstr);
if((hexdata == 16) || (lowhexdata == 16))
break;
else
hexdata = hexdata*16+lowhexdata;
i++;
senddata[hexdatalen] = (char)hexdata;
hexdatalen++;

senddata.resize(hexdatalen);
return senddata;


char ReadPLC::ConvertHexChar(char ch)

if((ch >= '0') && (ch <= '9'))
return ch-0x30;
else if((ch >= 'A') && (ch <= 'F'))
return ch-'A'+10;
else if((ch >= 'a') && (ch <= 'f'))
return ch-'a'+10;
else return (-1);

登录后复制

16进制-to-字符串
自行补充
登录后复制
Fins/UDP通信
读函数与写函数都差不多,读写反馈可以使用信号槽来监听新数据,也可以直接后面写函数来读取,只不过需要做一下延时处理。

读写函数
/*函数功能:读取PLC数据
函数参数说明:
*bindPort:plc端口
*bindIP:plcIP
*winIP:PCIP
*IO:读取位数
*WID:读取地址
*/
void ReadPLC::readplcdata(QString bindPort,QString bindIP, QString winIP, int IO, int WID)

QString plcip,winddowip;
int plcIPEnd = bindIP.split(".").at(3).toInt();
plcip = QString::number(plcIPEnd,16).toUpper();

int winIPEnd = winIP.split(".").at(3).toInt();
winddowip = QString::number(winIPEnd,16).toUpper(); //得到win的十六进制

if(plcip.size()==1)
plcip = "0"+plcip;

if(winddowip.size()==1)
winddowip = "0"+winddowip;


QString ID = intTo16(WID);
QString writeIO = "000"+QString::number(IO,16).toUpper();
QString plcwrite = "80000200"+plcip+"0000"+winddowip+"0000010182";

plcwrite += ID+"00000"+QString::number(IO);
QByteArray data = QString2Hex(plcwrite); //字符串转16

udpsocket->writeDatagram(data, QHostAddress(bindIP), quint16(bindPort.toInt()));

登录后复制

读写反馈
void ReadPLC::Read_plc_data()

QByteArray datagram;
datagram.resize(int(udpsocket->pendingDatagramSize()));
udpsocket->readDatagram(datagram.data(),datagram.size());
QString str = datagram.data();
QString byte = datagram.toHex();
if(!byte.isEmpty())

// qDebug()<<"16进制:"<<datagram.toHex();
QString str_rev = QString(datagram.toHex(' ').toUpper().append(' '));
qDebug()<<"data:"<<str_rev;
//转换为字符



登录后复制
参考技术A 首先,计算机网络的层次概念你没有搞清楚。计算机网络的体系结构是分层的,每一层都是相对独立的,上层与下层之间通过接口进行通信,层与层之间是通过协议进行的虚通信(物理层除外)。面向连接和面向无连接都是对特定层的特定协议来说的,也就是说,整个电话通信过程是不可以一概说成面向连接或者是无连接的。书上说IP电话使用面向无连接的UDP协议,那是指传输层而言的。\x0d\x0a\x0d\x0a好了,回到你的问题。你所说的先拨号,等待接通,这个过程实际上不是建立传输层连接的过程,而是建立物理层链接的过程。链接,就是建立数据链路的过程,建立好的数据链路就由数据链路层进行控制。物理层的报文交换方式主要有电路交换,分组交换和报文交换三种。其中电路交换,在数据传输前,需要建立物理层或者数据链路层上的链接,我们把它成为虚电路。这个链接其实也不能算是协议层次上的连接,而是在真正物理通信前,建立一个数据链路的过程。\x0d\x0a\x0d\x0a传输层上,就可以谈到连接的问题了,呵呵!TCP与UDP两个协议,我相信它们的优缺点,朋友你非常清楚。TCP有连接,有差错控制,有重传,可靠但效率低;UDP正相反,控制机制都没有,不可靠但效率高。很显然,传输层传递语音信号一定是使用的UDP协议,也就是面向无连接的,因为这比较适合语音通信的应用场合。语音单包数据一般比较小,要求传输的实时性高,你可以想象,在你打电话的时候,你可以接受偶尔一个丢音或者串音,但你绝不能接受因为要进行差错控制或信息重传而导致你的通话中断N秒,然后再继续,对吧?呵呵,所以传输层上看,一定是面向无连接的UDP。希望对你有所帮助,呵呵!
回答于 2022-12-11
向ta提问
查看全部回答
300m宽带需要用什么路由器_升级套餐,100M/200M多种套餐_限时优惠
300m宽带需要用什么路由器_升级服务,老牌宽带,品质好,价格低,网速快,值得信赖,百万家庭优选的好宽带,在线预约,享更多优惠套餐,24小时上门安装,省时方便。
上海长城宽带广告
买台湾 moxa路由器工业以太网交换机,首选-上海佳盈 moxa价格
根据文中提到的udp协议为您推荐
价格优惠 现货直供 五年,提供一系列解决方案 专业技术与服务,串口卡 工业串口moxa路由器, 全系列产品 , 地址:徐汇区龙华西路585号华富A座806室
佳盈网络系统有限公司广告
大家还在
参考技术B 首先,计算机网络的层次概念你没有搞清楚。计算机网络的体系结构是分层的,每一层都是相对独立的,上层与下层之间通过接口进行通信,层与层之间是通过协议进行的虚通信(物理层除外)。面向连接和面向无连接都是对特定层的特定协议来说的,也就是说,整个电话通信过程是不可以一概说成面向连接或者是无连接的。书上说IP电话使用面向无连接的UDP协议,那是指传输层而言的。\x0d\x0a\x0d\x0a好了,回到你的问题。你所说的先拨号,等待接通,这个过程实际上不是建立传输层连接的过程,而是建立物理层链接的过程。链接,就是建立数据链路的过程,建立好的数据链路就由数据链路层进行控制。物理层的报文交换方式主要有电路交换,分组交换和报文交换三种。其中电路交换,在数据传输前,需要建立物理层或者数据链路层上的链接,我们把它成为虚电路。这个链接其实也不能算是协议层次上的连接,而是在真正物理通信前,建立一个数据链路的过程。\x0d\x0a\x0d\x0a传输层上,就可以谈到连接的问题了,呵呵!TCP与UDP两个协议,我相信它们的优缺点,朋友你非常清楚。TCP有连接,有差错控制,有重传,可靠但效率低;UDP正相反,控制机制都没有,不可靠但效率高。很显然,传输层传递语音信号一定是使用的UDP协议,也就是面向无连接的,因为这比较适合语音通信的应用场合。语音单包数据一般比较小,要求传输的实时性高,你可以想象,在你打电话的时候,你可以接受偶尔一个丢音或者串音,但你绝不能接受因为要进行差错控制或信息重传而导致你的通话中断N秒,然后再继续,对吧?呵呵,所以传输层上看,一定是面向无连接的UDP。 参考技术C 上位机读取udp的报文是实时的报文。

上位机基础-PLC通信篇

上位机基础-通信PLC篇

1. ModbusRTU协议(测试与实现)

1. Modbus Slave 的使用教程

以读取输出线圈功能为例(RTU模式使用CRC校验,Ascii 使用LRC校验):

主站:11 01 00 13 00 1B CRC

含义:读取11H从站的输出线圈(01 功能码 是输出线圈) ,起始地址0013H(19->00020),读取的线圈个数001BH(27)个

报文的起始地址为0,但是寄存器的最小地址为1.所以对应的地址需要后移动一个。

即读取从站输出线圈从0020-0046

从站报文: 11 01 04 CD 6B B2 05 CRC

从站返回输出线圈 0020-0046。

CD= 1100 1101对应 0020--0027 八个线圈位置

2. 使用NModbus4包进行参数读取

        public Form1()
        
            InitializeComponent();
        
        SerialPort serialPort;
        private void Form1_Load(object sender, EventArgs e)
        
            serialPort = new SerialPort("COM2", 9600, Parity.None, 8, StopBits.One);
            serialPort.Open();
        

        private void button1_Click(object sender, EventArgs e)
        

            ModbusSerialMaster modbusMaster = ModbusSerialMaster.CreateRtu(serialPort);
            byte addr = 17;
            ushort startAddr = 19;
            ushort count = 27;
            bool[] boolData = modbusMaster.ReadCoils(addr, startAddr, count);
            string ldata = string.Empty;

            /// 先把bool 转换为 string 2进制
            foreach (bool item in boolData)
            
                ldata += item == true ? 1 : 0;
            
            ldata= ldata.Trim(\' \').Replace(" ", "");
            textBox1.Text = StringBinTOStringHex(ldata);
        


        public string StringBinTOStringHex(string str)
        
            int Cnt = (str.Length / 4);
            bool IsLastFourData = str.Length % 4 == 0 ? true : false;
            if (!IsLastFourData)  Cnt++; 
            string lResult = string.Empty;
            for (int i = 0; i < Cnt; i++)
            
                if (str.Length < 4)
                    str = str.PadLeft(4, \'0\');
                string temp = str.Substring(0, 4);

                if (i == Cnt - 1 && !IsLastFourData)
                    lResult += Convert.ToInt16(temp, 2).ToString("X2");
                else
                    lResult += Convert.ToInt16(temp, 2).ToString("X");

                if (i % 2 == 1)
                    lResult += " ";
                str = str.Remove(0, 4);
            
            return lResult;
        

2. PLC通信(配置与代码实现)

基础知识:PLC的各种数据类型

数据类型 位数 案例 说明
Bool 布尔,1位 DB9.DBX7.0 DB9块Bool类型,偏移量为7,第一位的布尔数据
Byte 字节,8位 DB9.DBB6 DB9块byte类型,偏移量为6的字节数据
Word 字,16位 DB9.DBW4 DB9块字类型,偏移量为4的字数据
Dword 双字,32位
Sint 有符号短整数,8位
Usint 无符号短整数,8位
Int 有符号整数,16位
UInt 无符号整数,16位
Real 32位单精度 DB9.DBD0 DB9块Real类型,偏移量为0的单精度数据
LReal 64位双精度

PC端通信配置

首先对需要通信的PLC模块进行设置

  1. 设置当前模块为允许通信

  1. 取消 “优化的块访问”

  2. 记住当前需要通信的设备ip地址

方法1:S7-PLCSIM Advanced

  1. 下载的时候,选择接口为虚拟适配器

  1. 配置仿真器

    选择适配器,配置网络地址,然后点击Start

    1. 下载程序到设备。先搜索设备地址,然后点击下载

  1. 点击装载

  2. 点击Run

  1. 查看仿真器,状态灯会显示绿色

方法2:NetToPLCsim

PLC模块通信配置略过

  1. 点击仿真

  2. 点击开始搜索,并且下载程序

  1. 仿真器点击Run

  2. 配置NetToPLCsim

    先配置本机的IP地址,这个是选择网卡中的任何一个IP地址都行

    配置需要访问的PLC模块地址

    配置卡槽号信息:

    点击Start

    注意最重要的一点

    程序访问的时候,使用你本地的IP地址

代码实现

第一部分:连接PLC并且初始化

  1. 安装指定的PLC类库

  1. 初始化连接

    Plc myPlc = new Plc(CpuType.S71500, "192.168.255.105", 0, 1);
    myPlc.Open();
    if (myPlc.IsConnected)MessageBox.Show(“连接成功”);
    

关于写入与读取对应转换说明如下:

bool -> bit

byte -> byte

Usint,Uint,(小于等于16位)。统一使用 ushort接收。

int,word (小于等于16位)。统一使用 short接收。

Dword(大于16为,小于等于32位)。使用int接收.

Real ,用Float 接收

第二部分:读取并且写入对应类型的PLC数据

第一种方法,指定地址读写 ( 使用myPlc.Read()方法进行读,使用myPlc.Write()方法进行写入)

使用案例(略):

有部分命令格式不知道。以后有空填坑

			//Bool
            plc.Write("DB1.DBX0.0", true);
            var IsRight = plc.Read("DB1.DBX0.0");
            Console.WriteLine("DB1.DBX0.0: " + IsRight);

            //Int
            plc.Write("DB1.DBW2.0", Convert.ToInt16(1));
            int Score = (ushort)plc.Read("DB1.DBW2.0");
            Console.WriteLine("DB1.DBW2.0: " + Score);

            // Real
            plc.Write("DB1.DBD4.0", Convert.ToSingle(1.1));
            var Money = ((uint)plc.Read("DB1.DBD4.0")).ConvertToFloat();
            Console.WriteLine("DB1.DBD4.0: " + Money);

            //String写入
            var temp = Encoding.ASCII.GetBytes("Chen");   //将val字符串转换为字符数组
            var bytes = S7.Net.Types.S7String.ToByteArray("Chen", temp.Length);
            plc.WriteBytes(DataType.DataBlock, 1, 8, bytes);
            //String读取
            var reservedLength = (byte)plc.Read(DataType.DataBlock, 1, 8, VarType.Byte, 1);//获取字符串长度
            var Name = (string)plc.Read(DataType.DataBlock, 1, 8, VarType.S7String, reservedLength);//获取对应长度的字符串
            Console.WriteLine("DB1.8.0: " + Name);

            // DInt
            plc.Write("DB1.DBD264.0", Convert.ToInt32(20));
            var dIntVar = (uint)plc.Read("DB1.DBD264.0");
            Console.WriteLine("DB1.DBD264.0: " + dIntVar);

            // DWord
            plc.Write("DB1.DBD268.0", 123456);
            var dWordVar = (uint)plc.Read("DB1.DBD268.0");
            Console.WriteLine("DB1.DBD268.0: " + dWordVar);

            // Word
            plc.Write("DB1.DBD270.0", 12345678);
            var wordVar = (uint)plc.Read("DB1.DBD270.0");
            Console.WriteLine("DB1.DBD270.0: " + wordVar);
第二种方法, 解析读写

需要指定DB的类型、DB号、起始地址、PLC数据类型及读取数量。虽然它需要传入的参数变多了,但是当需要读取多个地址连续且类型相同的变量时,仅需修改最后的读取数量,S7NetPlus就会自动读取这一连串的地址,并按照指定的变量类型解析出对应的值,

函数说明:

public object Read(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0); 
//读取
bool result = (bool)plc.Read(DataType.DataBlock, 10, 0, VarType.Bit, 1);
//写入
plc.Write(DataType.DataBlock, 10, 0, true);

代码如下(读取代码):

            var Real = myPlc.Read(DataType.DataBlock, 9, 0, VarType.Real,1);
            var Int = myPlc.Read(DataType.DataBlock, 9, 4, VarType.Int, 1);
            byte Byte = (byte)myPlc.Read(DataType.DataBlock, 9, 6, VarType.Byte, 1);
            var Word = myPlc.Read(DataType.DataBlock, 9, 8, VarType.Word, 1);
            var Dword = myPlc.Read(DataType.DataBlock, 9, 10, VarType.DWord, 1);
            var Uint = myPlc.Read(DataType.DataBlock, 9, 14, VarType.Int, 1);
            var LReal = myPlc.Read(DataType.DataBlock, 9, 16, VarType.LReal, 1);
            var lBool = myPlc.Read(DataType.DataBlock, 9, 24, VarType.Bit, 1);

写入代码:

            myPlc.Write(DataType.DataBlock, 9, 0, 6.5f);
            myPlc.Write(DataType.DataBlock, 9, 4,  (ushort)1);
            myPlc.Write(DataType.DataBlock, 9, 6, (byte)51);
            myPlc.Write(DataType.DataBlock, 9, 8, (ushort)11);
            myPlc.Write(DataType.DataBlock, 9, 24, false);

前两种方法,每次读取都是建立新的TCP连接,比较消耗计算机资源。

详细见文档表述:

This method reads a single variable from the plc, by parsing the string and returning the correct result. While this is the easiest method to get started, is very inefficient because the driver sends a TCP request for every variable.

第三种方法, 块读取与写入(未验证)。

一次性从连接中读取所有需要查看的信息,然后进行解析。

public byte[] ReadBytes(DataType dataType, int db, int startByteAdr, int count)
//读取数据选择从DB块中读取,db设置为1,起始地址为0,读取18个字节
var bytes = plc.ReadBytes(DataType.DataBlock, 1, 0, 18);
//取字节0中的第0位
var db1Bool1 = bytes[0].SelectBit(0);
Console.WriteLine("DB1.DBX0.0:" + db1Bool1);
//取字节0中的第1位
bool db1Bool2 = bytes[0].SelectBit(1); ;
Console.WriteLine("DB1.DBX0.1:" + db1Bool2);

//跳到字节2并连续取两个字节数据
int IntVariable = S7.Net.Types.Int.FromByteArray(bytes.Skip(2).Take(2).ToArray());
Console.WriteLine("DB1.DBW2.0:" + IntVariable);
//...
double RealVariable = S7.Net.Types.Real.FromByteArray(bytes.Skip(4).Take(4).ToArray());
Console.WriteLine("DB1.DBD4.0:" + RealVariable);
//...
int dIntVariable = S7.Net.Types.DInt.FromByteArray(bytes.Skip(8).Take(4).ToArray());
Console.WriteLine("DB1.DBD8.0: " + dIntVariable);
//...
uint dWordVariable = S7.Net.Types.DWord.FromByteArray(bytes.Skip(12).Take(4).ToArray());
Console.WriteLine("DB1.DBD12.0: " + Convert.ToString(dWordVariable, 16));
//...
ushort wordVariable = S7.Net.Types.Word.FromByteArray(bytes.Skip(16).Take(2).ToArray());
Console.WriteLine("DB1.DBW16.0: " + Convert.ToString(wordVariable, 16));
public void WriteBytes(DataType dataType, int db, int startByteAdr, byte[] value)
字符串读取与写入
//String读取
byte[] data = plc.ReadBytes(DataType.DataBlock, 10, 2, 254);
string result = Encoding.Default.GetString(data);

//Wstring读取
byte[] data = plc.ReadBytes(DataType.DataBlock, 10, 4, 508);
string result = Encoding.BigEndianUnicode.GetString(data);

在S7-1500中,一个String类型的变量占用256个字节,但是第一个字节是总字符数,第二个字节是当前字符数,所以真正的字符数据是从第三个字节开始的,共254个字节。

同理,WString类型其实就是双字节的Sring,也就是说一个字符占用两个字节,所以一个WString类型的变量占用512个字节,第一、二个字节是总字符数,第三、四个字节是当前字符数,真正的字符数据是从第五个字节开始的,共508个字节。

按照以上示例的方法,读取上来的字符串后面会带很多个"\\0"的字符,那是因为后面的空字节也读取上来了,正式使用时可以考虑使用.Replace("\\0", "")来去除,或者解析第二个字节来获取字符长度进而转码。

当写入字符串时,则需要根据不同的数据类型来生成对应字符串的字节数组,然后将该数组写入到指定地址中即可。

需要注意的是,String类型的编码格式对应的是ASCII,而WString的则是C#中的BigEndianUnicode格式。在WString中,由于总长度与当前字符数是都是双字节数,所以在转换成字节数组的时候存在高低字节顺序问题。在这里就有一个大坑:这两个变量在C#中转换出来的字节数组跟PLC中存储的,高低字节是反过来的。这也就是为什么下面的WString的示例中需要对总字符数和当前字符数的两个字节数组进行反转。

        /// <summary>
        /// 获取西门子PLC字符串数组--String
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        private byte[] GetPLCStringByteArray(string str)
        
            byte[] value = Encoding.Default.GetBytes(str);
            byte[] head = new byte[2];
            head[0] = Convert.ToByte(254);
            head[1] = Convert.ToByte(str.Length);
            value = head.Concat(value).ToArray();
            return value;
        
 
        /// <summary>
        /// 获取西门子PLC字符串数组--WString
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        private byte[] GetPLCWStringByteArray(string str)
        
            byte[] value = Encoding.BigEndianUnicode.GetBytes(str);
            byte[] head = BitConverter.GetBytes((short)508);
            byte[] length = BitConverter.GetBytes((short)str.Length);
            Array.Reverse(head);
            Array.Reverse(length);
            head = head.Concat(length).ToArray();
            value = head.Concat(value).ToArray();
            return value;
        
        
//写入String 
string str = "Example";
plc.Write(DataType.DataBlock, 10, 0, GetPLCStringByteArray(str));

//写入WString
string str = "示例";
plc.Write(DataType.DataBlock, 10, 0, GetPLCWStringByteArray(str));

旧版本的单次字节读取是有字节数限制的,每一次读取的最大字节数为200,如果需要读写更多的字节,则需要多次读写并进行拼接,以下提供两种方法,可供参考:

        /// <summary>
        /// 循环读取
        /// </summary>
        /// <param name="numBytes">要读取的字节数</param>
        /// <param name="db">DB号</param>
        /// <param name="startByteAdr">起始地址</param>
        /// <returns></returns>
        private byte[] CyclicReadMultipleBytes(int numBytes, int db, int startByteAdr = 0)
        
            byte[] resultBytes = new byte[0];
            int index = startByteAdr;
            while (numBytes > 0)
            
                var maxToRead = Math.Min(numBytes, 200);
                byte[] bytes = plc.ReadBytes(DataType.DataBlock, db, index, maxToRead);
                if (bytes == null)
                    return null;
                resultBytes = resultBytes.Concat(bytes).ToArray();
                numBytes -= maxToRead;
                index += maxToRead;
            
            return resultBytes;
        
 
        /// <summary>
        /// 递归读取
        /// </summary>
        /// <param name="numBytes">要读取的字节数</param>
        /// <param name="db">DB号</param>
        /// <param name="startByteAdr">起始地址</param>
        /// <returns></returns>
        public static byte[] RecursiveReadMultipleBytes(int numBytes, int db, int startByteAdr = 0)
        
            byte[] result = new byte[0];
            if (numBytes > 200)
            
                byte[] temp = plc.ReadBytes(DataType.DataBlock, db, startByteAdr, 200);
                numBytes -= 200;
                result = temp.Concat(RecursiveReadMultipleBytes(numBytes, db, startByteAdr + 200)).ToArray();
            
            else
            
                byte[] temp = plc.ReadBytes(DataType.DataBlock, db, startByteAdr, numBytes);
                result = result.Concat(temp).ToArray();
                return result;
            
 
            return result;
        

以上是关于上位机读取udp的报文是实时的报文吗的主要内容,如果未能解决你的问题,请参考以下文章

Modbus TCP协议及上位机软件编写

2021-11-26 WPF上位机 99-S7协议报文分析

2021-11-22 WPF上位机 94-Modbus通信数据交换问题

2021-11-27 WPF上位机 100-西门子S7协议之modbus读取数据

串口通讯继电器-modbus通信上位机调试软件工具项目开发案例

串口通讯继电器-modbus通信上位机调试软件工具项目开发案例