手摸手教你撕碎西门子S7通讯协议06--S7Read读取short数据

1、S7通讯回顾

    - (1)建立TCP连接      Socket.Connect-》已实现

     - (2)发送访问请求     COTP-》已实现 

     - (3)交换通信信息     Setup Communication-》已实现

     - (4)执行相关操作     读、写、PLC启停、时间、上传下载-》本节实现读取short数据

2、S7Read请求介绍

根据上节读取bool数据,可以想想如何读取short数据,报文拼装方面要改哪些地方,希望通过这节动手,加深对报文的理解。什么是short类型,PLC中的word类型就是C#中的short类型,它是指int16格式的数据,包括有符号和无符号整数,但不包括小数,比如459,-832。

 通过观察可以得知以下几点:

1》发送报文在序号22的Transport size这个地方不同,再来看下这个具体值,S7Parameter->Item->Transport size常见值,所以应该是0x04

2》如果要读取多个数据,那哪里要改动一下了,当然是序号18这个地方表示数据项长度,读取一个数据就要拼成一个item,它占12个字节,那读取二个就要拼成两个item,也就是这个地方

 3、开搞

1、修改博途中的监控数值

DB1.DBW2地址中,2是字节地址,0是位地址,这个别弄错了,虽然DB1.DBW2后面没有0,但的的确确位地址是0,因为在程序中要用到。 

2、读取1个short代码

 

 

完整代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;namespace west.siemenscomm
{internal class Program{/// <summary>/// plc的ip地址/// </summary>static string _ip = "192.168.1.66";/// <summary>/// 端口号/// </summary>static int _port = 102;/// <summary>/// 机柜号,插槽号/// </summary>static byte _rack = 0, _slot = 1;/// <summary>/// socket对象/// </summary>static Socket socket = null;/// <summary>/// 时间事件/// </summary>static ManualResetEvent TimeoutObject = new ManualResetEvent(false);/// <summary>/// 连接状态 /// </summary>static bool connectState = false;/// <summary>/// 通讯连接的pdu长度/// </summary>static short _pduSize = 240;static void Main(string[] args){Connect();if (connectState){COTPConnection();if (connectState){SetupCommunication();if (connectState){//Console.WriteLine(ReadBool());Console.WriteLine(ReadOneShort());}}}Console.ReadKey();}/// <summary>/// 读取一个short数据/// </summary>/// <returns></returns>static short ReadOneShort(){// TPKTList<byte> tpktBytes = new List<byte>();tpktBytes.Add(0x03);tpktBytes.Add(0x00);// --------整个字节数组的长度 // COTPList<byte> cotpBytes = new List<byte>();cotpBytes.Add(0x02);cotpBytes.Add(0xf0);cotpBytes.Add(0x80);// HeaderList<byte> headerBytes = new List<byte>();headerBytes.Add(0x32);headerBytes.Add(0x01);headerBytes.Add(0x00);headerBytes.Add(0x00);headerBytes.Add(0x00);headerBytes.Add(0x00);// 添加Parameter字节数组的长度// 添加Data字节数组的长度// ParameterList<byte> paramBytes = new List<byte>();paramBytes.Add(0x04);// Read VarparamBytes.Add(0x01);// 如果有多个区域请求的情况下,这里需要计算,计算Item的个数// Item Bytes   #region Item1List<byte> itemBytes_1 = new List<byte>();itemBytes_1.Add(0x12);itemBytes_1.Add(0x0a);itemBytes_1.Add(0x10);itemBytes_1.Add(0x04);// 读取类型: 01Bit  02Byte  03Char  04Word// 读取长度itemBytes_1.Add(0x00);itemBytes_1.Add(0x01);// DB块编号   DB1.DBW2 itemBytes_1.Add(0x00);itemBytes_1.Add(0x01);// 数据区域itemBytes_1.Add(0x84);  //81=I   82=Q   83=M   84=DB// 地址DB1.DBW2 //int address = startAddr * 8 + bitAddr;int addr = (2 << 3) + 0;itemBytes_1.Add((byte)(addr / 256 / 256 % 256));itemBytes_1.Add((byte)(addr / 256 % 256));itemBytes_1.Add((byte)(addr % 256));#endregion // 拼装Parameter&ItemparamBytes.AddRange(itemBytes_1); // 拼装Header&ParameterheaderBytes.Add((byte)(paramBytes.Count / 256 % 256));headerBytes.Add((byte)(paramBytes.Count % 256));headerBytes.Add(0x00);headerBytes.Add(0x00);headerBytes.AddRange(paramBytes);// 拼装COTP&HeadercotpBytes.AddRange(headerBytes);//拼装 TPKT&COTP// tpkt现有长度+报文总长度2个字节+COTP长度int count = tpktBytes.Count + 2 + cotpBytes.Count;tpktBytes.Add((byte)(count / 256 % 256));tpktBytes.Add((byte)(count % 256));tpktBytes.AddRange(cotpBytes); socket.Send(tpktBytes.ToArray());// 拿多少数据// TPKTbyte[] bytes = new byte[4];socket.Receive(bytes, 0, 4, SocketFlags.None);byte[] lenBytes = new byte[2];lenBytes[0] = bytes[3];lenBytes[1] = bytes[2];short len = BitConverter.ToInt16(lenBytes,0);len -= 4; byte[] buffer = new byte[len];socket.Receive(buffer, 0, len, SocketFlags.None);// 判断是否有异常,buffer[13]是error class,buffer[14]是error code,buffer[17]是return codeint index = 17;if (buffer[13] == 0x00 && buffer[14] == 0x00 && buffer[index] == 0xff){lenBytes[0] = buffer[index + 3];lenBytes[1] = buffer[index + 2]; ushort dataLen = (ushort)(BitConverter.ToUInt16(lenBytes,0) / 8);// 数据响应长度,这个长度是位长度,除以8得到字节,这里应该是2,因为short占2个字节宽度byte[] dataBuffer = new byte[dataLen];//返回的具体数据Array.Copy(buffer, index + 4, dataBuffer, 0, dataLen); //数组拷贝 List<byte> dataList = new List<byte>(dataBuffer);dataList.Reverse();// 处理大小端问题 return BitConverter.ToInt16(dataList.ToArray(),0);}return 0;}/// <summary>/// 读取bool/// </summary>/// <exception cref="NotImplementedException"></exception>private static bool ReadBool(){ // TPKT,占4个字节List<byte> tpktBytes = new List<byte>();tpktBytes.Add(0x03);//Version,版本默认3tpktBytes.Add(0x00);//Reserved,保留默认// --------整个字节数组的长度,这个稍留着,要等到后面计算出来 // COTP,占3个字节List<byte> cotpBytes = new List<byte>();cotpBytes.Add(0x02);//当前字节以后的字节数cotpBytes.Add(0xf0);//PDU Type,数据传输cotpBytes.Add(0x80);//TPDU number,固定值// Header,占10个字节List<byte> headerBytes = new List<byte>();headerBytes.Add(0x32); // Protocol Id,默认headerBytes.Add(0x01); // ROSCTR:JOBheaderBytes.Add(0x00); // Redundancy Identification (ReservedheaderBytes.Add(0x00); // headerBytes.Add(0x00); // Protocol Data Unit ReferenceheaderBytes.Add(0x00); // // 添加Parameter字节数组的长度,这个稍留着,要等到后面计算出来 // 添加Data字节数组的长度,这个稍留着,要等到后面计算出来 // Parameter,占2个字节List<byte> paramBytes = new List<byte>();paramBytes.Add(0x04); // Function: Read Var (0x04)[paramBytes.Add(0x01);// Item count: 1如果有多个区域请求的情况下,这里需要计算,计算Item的个数// Item Bytes  ,占12个字节 List<byte> itemBytes = new List<byte>();itemBytes.Add(0x12); // 结构标识,一般默认0x12itemBytes.Add(0x0a); // 此字节往后的字节长度itemBytes.Add(0x10); // Syntax Id: S7ANY (0x10) itemBytes.Add(0x01); // Transport size: bit (2) // 读取长度itemBytes.Add(0x00); // 高位itemBytes.Add(0x01); // 低位// DB块编号itemBytes.Add(0x00); // 高位itemBytes.Add(0x01); // 低位// 数据区域itemBytes.Add(0x84); // 数据区域// 地址,DB1.DBX0.0 //计算公式:int address = startAddr * 8 + bitAddr;int addr = (0 << 3) +0;//字节地址itemBytes.Add((byte)(addr / 256 / 256 % 256));itemBytes.Add((byte)(addr / 256 % 256));//位地址itemBytes.Add((byte)(addr % 256)); // 拼装Parameter&ItemparamBytes.AddRange(itemBytes);// 拼装Header&ParameterheaderBytes.Add((byte)(paramBytes.Count / 256 % 256));//Parameter长度headerBytes.Add((byte)(paramBytes.Count % 256));headerBytes.Add(0x00);//Data length,读取操作没有数据,这里自然是0headerBytes.Add(0x00);headerBytes.AddRange(paramBytes);// 拼装COTP&HeadercotpBytes.AddRange(headerBytes);//拼装 TPKT&COTP// tpkt现有长度+报文总长度2个字节+COTP长度int count = tpktBytes.Count + 2 + cotpBytes.Count;tpktBytes.Add((byte)(count / 256 % 256));tpktBytes.Add((byte)(count % 256));tpktBytes.AddRange(cotpBytes); //发送socket.Send(tpktBytes.ToArray());//响应数据处理byte[] bytes = new byte[4];socket.Receive(bytes, 0, 4, SocketFlags.None); // TPKTbyte[] lenBytes = new byte[2];//整个响应长度的字节数 lenBytes[0] = bytes[3];lenBytes[1] = bytes[2];short len = BitConverter.ToInt16(lenBytes,0);len -= 4;//减去 TPKT部分,就是剩下的长度byte[] buffer = new byte[len];socket.Receive(buffer, 0, len, SocketFlags.None);//接收剩下的全部数据// 判断是否有异常,buffer[13]是error class,buffer[14]是error code,buffer[17]是return codeint index = 17;if (buffer[13] == 0x00 && buffer[14] == 0x00 && buffer[index] == 0xff){//数据响应长度所在位置 lenBytes[0] = buffer[index + 3];lenBytes[1] = buffer[index + 2];//得到响应的数据长度ushort dataLen = BitConverter.ToUInt16(lenBytes,0); byte[] dataBuffer = new byte[dataLen];//获取响应的数据Array.Copy(buffer, index + 4, dataBuffer, 0, dataLen); return dataBuffer[0] == 0x01;}return false;}/// <summary>/// 通讯连接/// </summary>/// <exception cref="NotImplementedException"></exception>private static bool SetupCommunication(){//s7comm连接包括4个部分,共25个字节,即25=4+3+10+8 byte[] setupBytes = new byte[] {// 1)TPKT包括4个字节0x03,//版本默认30x00,//保留默认00x00,//整个请求字节数高位0x19,//整个请求字节数低位,0x19转换成10进制就是25//2)COTP包括3个字节0x02,//当前字节以后的字节数(不包括自已,0x02转换成10进制就是2),注意这个“当前字节以后的字节数”是指COTP这部分,而不是整个字节部分0xf0,//PDU Type,0xe0 连接请求,0xd0 连接确认,0x08 断开请求,0x0c 断开确认,0x05 拒绝访问,0x01 加急数据,0x02 加急数据确认,0x04 用户数据,0x07 TPDU错误,0xf0 数据传输0x80,//TPDU number,固定值// 3)Header包括10个字节0x32,//默认值,协议id0x01,//ROSCTR,0x01 Job request。主站发送请求,0x02 Ack。从站响应请求不带数据,0x03 Ack_Data。从站响应请求并带有数据,0x07 Userdata。原始协议的扩展。读取编程/调试、SZL读取、安全功能、时间设置等0x00,//Redundancy Identification (Reserved)固定值,占2个字节0x00,0x00,//Protocol Data Unit Reference固定值,占2个字节0x00,0x00,//Parameter length参数长度,占2个字节0x08,0x00,//Data length数据长度,占2个字节0x00,// 4)Parameter包括8个字节0xf0,//Function功能码,具体是:0x00 CPU服务,0xF0 设置通信,0x04 读取变量,0x05 写变量,0x1A 请求下载,0x1B 下载块,0x1C 下载结束,0x1D 开始上传,0x1E 上传,0x1F 结束上传,0x28 PLC 控制,0x29 PLC 停止0x00,//保留默认值0x00,//Max AmQ(parallel jobs with ack) calling,占2个字节0x03,0x00,//Max AmQ(parallel jobs with ack) called,占2个字节0x03,0x03,//PDU length,占2个字节,0x03co转换成10进制就是9600xc0};try{socket.Send(setupBytes);//响应报文的长度就是固定的27个字节byte[] respBytes = new byte[27];int count = socket.Receive(respBytes);// 拿到PDU长度   后续进行报文组装和接收的时候可以参考byte[] pdu_size = new byte[2];pdu_size[0] = respBytes[26];pdu_size[1] = respBytes[25];_pduSize = BitConverter.ToInt16(pdu_size,0);if (respBytes[17] != 0x00&& respBytes[18] != 0x00){Console.WriteLine("粗问题,COMM连接响应异常");connectState = false;}else{Console.WriteLine("太好了,COMM连接响应正常");connectState = true;}}catch (Exception ex){Console.WriteLine("Setup通信未建立!" + ex.Message);connectState = false;}return connectState;}/// <summary>/// cotp连接/// </summary>private static bool COTPConnection(){//COTP连接包括2个部分,共22个字节),22=4+18byte[] cotpBytes = new byte[] {//1)TPKT包括4个字节0x03,//版本号,版本默认30x00,//默认保留为00x00,//整个请求字节高位0x16,//整个请求字节低位(0x16转换成为10进制就是22)//2)COTP包括18个字节 0x11,//当前字节以后的字节数(不包括自已,0x11转换成10进制就是17)0xe0,//PDU type,0xe0 连接请求,0xd0 连接确认,0x08 断开请求,0x0c 断开确认,0x05 拒绝访问,0x01 加急数据,0x02 加急数据确认,0x04 用户数据,0x07 TPDU错误,0xf0 数据传输0x00,//DST reference(2个字节)0x00,//0x00,//SRC reference(2个字节)0x00,//0x00,//class(固定的) 0xc1, //Parameter-code  src-tsap 上位机0x02,  //Parameter-Len   0x10 ,   //Source TSAP:01->PG;02->OP;03->S7单边(服务器模式);0x10->S7双边通 0x00,   //机架与插槽号为0   0xc2,//Parameter-code  dst-tsap PLC 0x02,//Parameter len  0x03,//Destination TSAP  (byte)(_rack*32+_slot),//机架与插槽号: 0xc0,  //    Parameter code:tpdu-size 0x01,   //   Parameter length0x0a  //    TPDU size };try{socket.Send(cotpBytes);//响应报文的长度是固定的22个字节byte[] respBytes = new byte[22];int count = socket.Receive(respBytes, 0, 22, SocketFlags.None);//第5个字节是pdu type,具体是:0xe0 连接请求,0xd0 连接确认,0x08 断开请求,0x0c 断开确认,0x05 拒绝访问,0x01 加急数据,0x02 加急数据确认,0x04 用户数据,0x07 TPDU错误,0xf0 数据传输if (respBytes[5] != 0xd0){Console.WriteLine("粗问题,COTP连接响应异常");connectState = false;}else{Console.WriteLine("太好了,COTP连接响应正常");connectState = true;}}catch (Exception ex){Console.WriteLine("COTP连接未建立!" + ex.Message);connectState = false;}return connectState;}/// <summary>/// tcp连接 /// </summary>/// <param name="timeout"></param>private static void Connect(int timeout = 50){TimeoutObject.Reset();try{socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);socket.BeginConnect(_ip, _port, callback =>{connectState = false;var cbSocket = callback.AsyncState as Socket;if (cbSocket != null){connectState = cbSocket.Connected;if (cbSocket.Connected)cbSocket.EndConnect(callback);}TimeoutObject.Set();}, socket);TimeoutObject.WaitOne(2000, false);}catch (SocketException ex){if (ex.ErrorCode == 10060)Console.WriteLine(ex.Message);}if (socket == null || !socket.Connected || ((socket.Poll(200, SelectMode.SelectRead) && (socket.Available == 0)))){Console.WriteLine("网络连接失败");}Console.WriteLine(connectState == true ? "连接成功" : "连接失败");}}
}

 3、运行结果 

4、读取2个short代码

   

 

完整代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;namespace west.siemenscomm
{internal class Program{/// <summary>/// plc的ip地址/// </summary>static string _ip = "192.168.1.66";/// <summary>/// 端口号/// </summary>static int _port = 102;/// <summary>/// 机柜号,插槽号/// </summary>static byte _rack = 0, _slot = 1;/// <summary>/// socket对象/// </summary>static Socket socket = null;/// <summary>/// 时间事件/// </summary>static ManualResetEvent TimeoutObject = new ManualResetEvent(false);/// <summary>/// 连接状态 /// </summary>static bool connectState = false;/// <summary>/// 通讯连接的pdu长度/// </summary>static short _pduSize = 240;static void Main(string[] args){Connect();if (connectState){COTPConnection();if (connectState){SetupCommunication();if (connectState){//Console.WriteLine(ReadBool());//Console.WriteLine("读取1个short");//Console.WriteLine(ReadOneShort());Console.WriteLine("读取2个short");List<short> myshort = ReadMuiShort();if (myshort.Count != 0){myshort.ForEach(x => Console.WriteLine(x));}}}}Console.ReadKey();}/// <summary>/// 读取多个short数据/// </summary>/// <returns></returns>static List<short> ReadMuiShort(){List<short> listshort = new List<short>();// TPKTList<byte> tpktBytes = new List<byte>();tpktBytes.Add(0x03);tpktBytes.Add(0x00);// --------整个字节数组的长度 // COTPList<byte> cotpBytes = new List<byte>();cotpBytes.Add(0x02);cotpBytes.Add(0xf0);cotpBytes.Add(0x80);// HeaderList<byte> headerBytes = new List<byte>();headerBytes.Add(0x32);headerBytes.Add(0x01);headerBytes.Add(0x00);headerBytes.Add(0x00);headerBytes.Add(0x00);headerBytes.Add(0x00);// 添加Parameter字节数组的长度// 添加Data字节数组的长度// ParameterList<byte> paramBytes = new List<byte>();paramBytes.Add(0x04);// Read VarparamBytes.Add(0x02);// 如果有多个区域请求的情况下,这里需要计算,计算Item的个数// Item Bytes   #region Item1List<byte> itemBytes_1 = new List<byte>();itemBytes_1.Add(0x12);itemBytes_1.Add(0x0a);itemBytes_1.Add(0x10);itemBytes_1.Add(0x04);// 读取类型: 01Bit  02Byte  03Char  04Word// 读取长度itemBytes_1.Add(0x00);itemBytes_1.Add(0x01);// DB块编号   DB1.DBW2 itemBytes_1.Add(0x00);itemBytes_1.Add(0x01);// 数据区域itemBytes_1.Add(0x84);  //81=I   82=Q   83=M   84=DB// 地址DB1.DBW2 //int address = startAddr * 8 + bitAddr;int addr = (2 << 3) + 0;itemBytes_1.Add((byte)(addr / 256 / 256 % 256));itemBytes_1.Add((byte)(addr / 256 % 256));itemBytes_1.Add((byte)(addr % 256));#endregion#region Item2List<byte> itemBytes_2 = new List<byte>();itemBytes_2.Add(0x12);itemBytes_2.Add(0x0a);itemBytes_2.Add(0x10);itemBytes_2.Add(0x04);// 读取类型: 01Bit  02Byte  03Char  04Word// 读取长度itemBytes_2.Add(0x00);itemBytes_2.Add(0x01);// DB块编号   DB1.DBW4 itemBytes_2.Add(0x00);itemBytes_2.Add(0x01);// 数据区域itemBytes_2.Add(0x84);  //81=I   82=Q   83=M   84=DB// 地址DB1.DBW4 //int address = startAddr * 8 + bitAddr;int addr2 = (4 << 3) + 0;itemBytes_2.Add((byte)(addr2 / 256 / 256 % 256));itemBytes_2.Add((byte)(addr2 / 256 % 256));itemBytes_2.Add((byte)(addr2 % 256));#endregion // 拼装Parameter&ItemparamBytes.AddRange(itemBytes_1);paramBytes.AddRange(itemBytes_2);// 拼装Header&ParameterheaderBytes.Add((byte)(paramBytes.Count / 256 % 256));headerBytes.Add((byte)(paramBytes.Count % 256));headerBytes.Add(0x00);headerBytes.Add(0x00);headerBytes.AddRange(paramBytes);// 拼装COTP&HeadercotpBytes.AddRange(headerBytes);//拼装 TPKT&COTP// tpkt现有长度+报文总长度2个字节+COTP长度int count = tpktBytes.Count + 2 + cotpBytes.Count;tpktBytes.Add((byte)(count / 256 % 256));tpktBytes.Add((byte)(count % 256));tpktBytes.AddRange(cotpBytes);socket.Send(tpktBytes.ToArray());// 拿多少数据// TPKTbyte[] bytes = new byte[4];socket.Receive(bytes, 0, 4, SocketFlags.None);byte[] lenBytes = new byte[2];lenBytes[0] = bytes[3];lenBytes[1] = bytes[2];short len = BitConverter.ToInt16(lenBytes, 0);len -= 4;byte[] buffer = new byte[len];socket.Receive(buffer, 0, len, SocketFlags.None);// 判断是否有异常,buffer[13]是error class,buffer[14]是error code,buffer[17]是return codeint index = 17;if (buffer[13] == 0x00 && buffer[14] == 0x00 && buffer[index] == 0xff){lenBytes[0] = buffer[index + 3];lenBytes[1] = buffer[index + 2];ushort dataLen = (ushort)(BitConverter.ToUInt16(lenBytes, 0) / 8);// 数据响应长度,这个长度是位长度,除以8得到字节,这里应该是2,因为short占2个字节宽度byte[] dataBuffer = new byte[dataLen];//返回的具体数据//第一个数据位置 index+4Array.Copy(buffer, index + 4, dataBuffer, 0, dataLen); //数组拷贝 List<byte> dataList = new List<byte>(dataBuffer);dataList.Reverse();// 处理大小端问题 listshort.Add(BitConverter.ToInt16(dataList.ToArray(), 0)); //第二个数据位置index+10,不是index+8Array.Copy(buffer, index + 10, dataBuffer, 0, dataLen); //数组拷贝 dataList.Clear();//清空dataList = new List<byte>(dataBuffer);dataList.Reverse();// 处理大小端问题 listshort.Add(BitConverter.ToInt16(dataList.ToArray(), 0)); }return listshort;}/// <summary>/// 读取一个short数据/// </summary>/// <returns></returns>static short ReadOneShort(){// TPKTList<byte> tpktBytes = new List<byte>();tpktBytes.Add(0x03);tpktBytes.Add(0x00);// --------整个字节数组的长度 // COTPList<byte> cotpBytes = new List<byte>();cotpBytes.Add(0x02);cotpBytes.Add(0xf0);cotpBytes.Add(0x80);// HeaderList<byte> headerBytes = new List<byte>();headerBytes.Add(0x32);headerBytes.Add(0x01);headerBytes.Add(0x00);headerBytes.Add(0x00);headerBytes.Add(0x00);headerBytes.Add(0x00);// 添加Parameter字节数组的长度// 添加Data字节数组的长度// ParameterList<byte> paramBytes = new List<byte>();paramBytes.Add(0x04);// Read VarparamBytes.Add(0x01);// 如果有多个区域请求的情况下,这里需要计算,计算Item的个数// Item Bytes   #region Item1List<byte> itemBytes_1 = new List<byte>();itemBytes_1.Add(0x12);itemBytes_1.Add(0x0a);itemBytes_1.Add(0x10);itemBytes_1.Add(0x04);// 读取类型: 01Bit  02Byte  03Char  04Word// 读取长度itemBytes_1.Add(0x00);itemBytes_1.Add(0x01);// DB块编号   DB1.DBW2 itemBytes_1.Add(0x00);itemBytes_1.Add(0x01);// 数据区域itemBytes_1.Add(0x84);  //81=I   82=Q   83=M   84=DB// 地址DB1.DBW2 //int address = startAddr * 8 + bitAddr;int addr = (2 << 3) + 0;itemBytes_1.Add((byte)(addr / 256 / 256 % 256));itemBytes_1.Add((byte)(addr / 256 % 256));itemBytes_1.Add((byte)(addr % 256));#endregion // 拼装Parameter&ItemparamBytes.AddRange(itemBytes_1); // 拼装Header&ParameterheaderBytes.Add((byte)(paramBytes.Count / 256 % 256));headerBytes.Add((byte)(paramBytes.Count % 256));headerBytes.Add(0x00);headerBytes.Add(0x00);headerBytes.AddRange(paramBytes);// 拼装COTP&HeadercotpBytes.AddRange(headerBytes);//拼装 TPKT&COTP// tpkt现有长度+报文总长度2个字节+COTP长度int count = tpktBytes.Count + 2 + cotpBytes.Count;tpktBytes.Add((byte)(count / 256 % 256));tpktBytes.Add((byte)(count % 256));tpktBytes.AddRange(cotpBytes); socket.Send(tpktBytes.ToArray());// 拿多少数据// TPKTbyte[] bytes = new byte[4];socket.Receive(bytes, 0, 4, SocketFlags.None);byte[] lenBytes = new byte[2];lenBytes[0] = bytes[3];lenBytes[1] = bytes[2];short len = BitConverter.ToInt16(lenBytes,0);len -= 4; byte[] buffer = new byte[len];socket.Receive(buffer, 0, len, SocketFlags.None);// 判断是否有异常,buffer[13]是error class,buffer[14]是error code,buffer[17]是return codeint index = 17;if (buffer[13] == 0x00 && buffer[14] == 0x00 && buffer[index] == 0xff){lenBytes[0] = buffer[index + 3];lenBytes[1] = buffer[index + 2]; ushort dataLen = (ushort)(BitConverter.ToUInt16(lenBytes,0) / 8);// 数据响应长度,这个长度是位长度,除以8得到字节,这里应该是2,因为short占2个字节宽度byte[] dataBuffer = new byte[dataLen];//返回的具体数据Array.Copy(buffer, index + 4, dataBuffer, 0, dataLen); //数组拷贝 List<byte> dataList = new List<byte>(dataBuffer);dataList.Reverse();// 处理大小端问题 return BitConverter.ToInt16(dataList.ToArray(),0);}return 0;}/// <summary>/// 读取bool/// </summary>/// <exception cref="NotImplementedException"></exception>private static bool ReadBool(){ // TPKT,占4个字节List<byte> tpktBytes = new List<byte>();tpktBytes.Add(0x03);//Version,版本默认3tpktBytes.Add(0x00);//Reserved,保留默认// --------整个字节数组的长度,这个稍留着,要等到后面计算出来 // COTP,占3个字节List<byte> cotpBytes = new List<byte>();cotpBytes.Add(0x02);//当前字节以后的字节数cotpBytes.Add(0xf0);//PDU Type,数据传输cotpBytes.Add(0x80);//TPDU number,固定值// Header,占10个字节List<byte> headerBytes = new List<byte>();headerBytes.Add(0x32); // Protocol Id,默认headerBytes.Add(0x01); // ROSCTR:JOBheaderBytes.Add(0x00); // Redundancy Identification (ReservedheaderBytes.Add(0x00); // headerBytes.Add(0x00); // Protocol Data Unit ReferenceheaderBytes.Add(0x00); // // 添加Parameter字节数组的长度,这个稍留着,要等到后面计算出来 // 添加Data字节数组的长度,这个稍留着,要等到后面计算出来 // Parameter,占2个字节List<byte> paramBytes = new List<byte>();paramBytes.Add(0x04); // Function: Read Var (0x04)[paramBytes.Add(0x01);// Item count: 1如果有多个区域请求的情况下,这里需要计算,计算Item的个数// Item Bytes  ,占12个字节 List<byte> itemBytes = new List<byte>();itemBytes.Add(0x12); // 结构标识,一般默认0x12itemBytes.Add(0x0a); // 此字节往后的字节长度itemBytes.Add(0x10); // Syntax Id: S7ANY (0x10) itemBytes.Add(0x01); // Transport size: bit (2) // 读取长度itemBytes.Add(0x00); // 高位itemBytes.Add(0x01); // 低位// DB块编号itemBytes.Add(0x00); // 高位itemBytes.Add(0x01); // 低位// 数据区域itemBytes.Add(0x84); // 数据区域// 地址,DB1.DBX0.0 //计算公式:int address = startAddr * 8 + bitAddr;int addr = (0 << 3) +0;//字节地址itemBytes.Add((byte)(addr / 256 / 256 % 256));itemBytes.Add((byte)(addr / 256 % 256));//位地址itemBytes.Add((byte)(addr % 256)); // 拼装Parameter&ItemparamBytes.AddRange(itemBytes);// 拼装Header&ParameterheaderBytes.Add((byte)(paramBytes.Count / 256 % 256));//Parameter长度headerBytes.Add((byte)(paramBytes.Count % 256));headerBytes.Add(0x00);//Data length,读取操作没有数据,这里自然是0headerBytes.Add(0x00);headerBytes.AddRange(paramBytes);// 拼装COTP&HeadercotpBytes.AddRange(headerBytes);//拼装 TPKT&COTP// tpkt现有长度+报文总长度2个字节+COTP长度int count = tpktBytes.Count + 2 + cotpBytes.Count;tpktBytes.Add((byte)(count / 256 % 256));tpktBytes.Add((byte)(count % 256));tpktBytes.AddRange(cotpBytes); //发送socket.Send(tpktBytes.ToArray());//响应数据处理byte[] bytes = new byte[4];socket.Receive(bytes, 0, 4, SocketFlags.None); // TPKTbyte[] lenBytes = new byte[2];//整个响应长度的字节数 lenBytes[0] = bytes[3];lenBytes[1] = bytes[2];short len = BitConverter.ToInt16(lenBytes,0);len -= 4;//减去 TPKT部分,就是剩下的长度byte[] buffer = new byte[len];socket.Receive(buffer, 0, len, SocketFlags.None);//接收剩下的全部数据// 判断是否有异常,buffer[13]是error class,buffer[14]是error code,buffer[17]是return codeint index = 17;if (buffer[13] == 0x00 && buffer[14] == 0x00 && buffer[index] == 0xff){//数据响应长度所在位置 lenBytes[0] = buffer[index + 3];lenBytes[1] = buffer[index + 2];//得到响应的数据长度ushort dataLen = BitConverter.ToUInt16(lenBytes,0); byte[] dataBuffer = new byte[dataLen];//获取响应的数据Array.Copy(buffer, index + 4, dataBuffer, 0, dataLen); return dataBuffer[0] == 0x01;}return false;}/// <summary>/// 通讯连接/// </summary>/// <exception cref="NotImplementedException"></exception>private static bool SetupCommunication(){//s7comm连接包括4个部分,共25个字节,即25=4+3+10+8 byte[] setupBytes = new byte[] {// 1)TPKT包括4个字节0x03,//版本默认30x00,//保留默认00x00,//整个请求字节数高位0x19,//整个请求字节数低位,0x19转换成10进制就是25//2)COTP包括3个字节0x02,//当前字节以后的字节数(不包括自已,0x02转换成10进制就是2),注意这个“当前字节以后的字节数”是指COTP这部分,而不是整个字节部分0xf0,//PDU Type,0xe0 连接请求,0xd0 连接确认,0x08 断开请求,0x0c 断开确认,0x05 拒绝访问,0x01 加急数据,0x02 加急数据确认,0x04 用户数据,0x07 TPDU错误,0xf0 数据传输0x80,//TPDU number,固定值// 3)Header包括10个字节0x32,//默认值,协议id0x01,//ROSCTR,0x01 Job request。主站发送请求,0x02 Ack。从站响应请求不带数据,0x03 Ack_Data。从站响应请求并带有数据,0x07 Userdata。原始协议的扩展。读取编程/调试、SZL读取、安全功能、时间设置等0x00,//Redundancy Identification (Reserved)固定值,占2个字节0x00,0x00,//Protocol Data Unit Reference固定值,占2个字节0x00,0x00,//Parameter length参数长度,占2个字节0x08,0x00,//Data length数据长度,占2个字节0x00,// 4)Parameter包括8个字节0xf0,//Function功能码,具体是:0x00 CPU服务,0xF0 设置通信,0x04 读取变量,0x05 写变量,0x1A 请求下载,0x1B 下载块,0x1C 下载结束,0x1D 开始上传,0x1E 上传,0x1F 结束上传,0x28 PLC 控制,0x29 PLC 停止0x00,//保留默认值0x00,//Max AmQ(parallel jobs with ack) calling,占2个字节0x03,0x00,//Max AmQ(parallel jobs with ack) called,占2个字节0x03,0x03,//PDU length,占2个字节,0x03co转换成10进制就是9600xc0};try{socket.Send(setupBytes);//响应报文的长度就是固定的27个字节byte[] respBytes = new byte[27];int count = socket.Receive(respBytes);// 拿到PDU长度   后续进行报文组装和接收的时候可以参考byte[] pdu_size = new byte[2];pdu_size[0] = respBytes[26];pdu_size[1] = respBytes[25];_pduSize = BitConverter.ToInt16(pdu_size,0);if (respBytes[17] != 0x00&& respBytes[18] != 0x00){Console.WriteLine("粗问题,COMM连接响应异常");connectState = false;}else{Console.WriteLine("太好了,COMM连接响应正常");connectState = true;}}catch (Exception ex){Console.WriteLine("Setup通信未建立!" + ex.Message);connectState = false;}return connectState;}/// <summary>/// cotp连接/// </summary>private static bool COTPConnection(){//COTP连接包括2个部分,共22个字节),22=4+18byte[] cotpBytes = new byte[] {//1)TPKT包括4个字节0x03,//版本号,版本默认30x00,//默认保留为00x00,//整个请求字节高位0x16,//整个请求字节低位(0x16转换成为10进制就是22)//2)COTP包括18个字节 0x11,//当前字节以后的字节数(不包括自已,0x11转换成10进制就是17)0xe0,//PDU type,0xe0 连接请求,0xd0 连接确认,0x08 断开请求,0x0c 断开确认,0x05 拒绝访问,0x01 加急数据,0x02 加急数据确认,0x04 用户数据,0x07 TPDU错误,0xf0 数据传输0x00,//DST reference(2个字节)0x00,//0x00,//SRC reference(2个字节)0x00,//0x00,//class(固定的) 0xc1, //Parameter-code  src-tsap 上位机0x02,  //Parameter-Len   0x10 ,   //Source TSAP:01->PG;02->OP;03->S7单边(服务器模式);0x10->S7双边通 0x00,   //机架与插槽号为0   0xc2,//Parameter-code  dst-tsap PLC 0x02,//Parameter len  0x03,//Destination TSAP  (byte)(_rack*32+_slot),//机架与插槽号: 0xc0,  //    Parameter code:tpdu-size 0x01,   //   Parameter length0x0a  //    TPDU size };try{socket.Send(cotpBytes);//响应报文的长度是固定的22个字节byte[] respBytes = new byte[22];int count = socket.Receive(respBytes, 0, 22, SocketFlags.None);//第5个字节是pdu type,具体是:0xe0 连接请求,0xd0 连接确认,0x08 断开请求,0x0c 断开确认,0x05 拒绝访问,0x01 加急数据,0x02 加急数据确认,0x04 用户数据,0x07 TPDU错误,0xf0 数据传输if (respBytes[5] != 0xd0){Console.WriteLine("粗问题,COTP连接响应异常");connectState = false;}else{Console.WriteLine("太好了,COTP连接响应正常");connectState = true;}}catch (Exception ex){Console.WriteLine("COTP连接未建立!" + ex.Message);connectState = false;}return connectState;}/// <summary>/// tcp连接 /// </summary>/// <param name="timeout"></param>private static void Connect(int timeout = 50){TimeoutObject.Reset();try{socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);socket.BeginConnect(_ip, _port, callback =>{connectState = false;var cbSocket = callback.AsyncState as Socket;if (cbSocket != null){connectState = cbSocket.Connected;if (cbSocket.Connected)cbSocket.EndConnect(callback);}TimeoutObject.Set();}, socket);TimeoutObject.WaitOne(2000, false);}catch (SocketException ex){if (ex.ErrorCode == 10060)Console.WriteLine(ex.Message);}if (socket == null || !socket.Connected || ((socket.Poll(200, SelectMode.SelectRead) && (socket.Available == 0)))){Console.WriteLine("网络连接失败");}Console.WriteLine(connectState == true ? "连接成功" : "连接失败");}}
}

 5、运行结果

 4、小结

有人肯定会问,那地址是多变的,那代码不要改来改去吗,别急,现在先走通报文组装,熟悉报文结构,后面会利用OOP思想进行操作,现在只要走通报文组装才是核心重点,万丈高楼打好基础,后面的继续干。小伙伴们,明白了不?下节继续吹起来

原创不易,打字截图不易,走过路过,不要错过,欢迎点赞,收藏,转载,复制,抄袭,留言,动动你的金手指,早日实现财务自由

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/51843.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何使用rdma-core来实现RDMA操作

rdma-core 是一个开源项目&#xff0c;为远程直接内存访问&#xff08;RDMA&#xff09;提供用户空间的支持。它包括 RDMA 设备的驱动程序、库和工具&#xff0c;旨在简化 RDMA 应用的开发和部署。 基础知识参考博文&#xff1a; 一文带你了解什么是RDMA RDMA 高性能架构基本…

Langchain--如何使用大模型 2.0

【&#x1f34a;易编橙终身成长社群&#x1f34a;】 大家好&#xff0c;我是小森( &#xfe61;ˆoˆ&#xfe61; ) &#xff01; 易编橙终身成长社群创始团队嘉宾&#xff0c;橙似锦计划领衔成员、阿里云专家博主、腾讯云内容共创官、CSDN人工智能领域优质创作者 。 Langch…

【已解决】嵌入式linux mobaxterm unable to open connection to comx 串口正常连接,但终端无法输入

1.点击Session重新选择串口&#xff0c;注意看看串口是不是连接到虚拟机&#xff0c;导致串口被占用。 2.选择PC机与开发板连接的串口&#xff0c;不知道的话可以打开设备管理器看看&#xff0c;选择正确的波特率&#xff0c;一般是115200。 3.关键一步&#xff1a;选择后别急…

【计算机网络原理】网络层IP协议的总结和数据链路层以太网协议的总结.

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

MobaXterm tmux 配置妥当

一、事出有因 缘由&#xff1a;接上篇文章&#xff0c;用Docker搭建pwn环境后&#xff0c;用之前学过的多窗口tmux进行调试程序&#xff0c;但是鼠标滚动的效果不按预期上下翻屏。全网搜索很难找到有效解决办法&#xff0c;最后还是找到了一篇英文文章&#xff0c;解决了&…

正点原子imx6ull-mini-Linux设备树下的LED驱动实验(4)

1&#xff1a;修改设备树文件 在根节点“/”下创建一个名为“alphaled”的子节点&#xff0c;打开 imx6ull-alientek-emmc.dts 文件&#xff0c; 在根节点“/”最后面输入如下所示内容 alphaled {#address-cells <1>;#size-cells <1>;compatible "atkalp…

25.惰性队列

介绍 消费者由于各种原因而致使长时间不能消费消息造成堆积。比如有一百万条消息发送到mq中&#xff0c;消费者这时宕机了不能消费消息&#xff0c;造成了消息堆积。惰性队列就有必要了。 正常情况下&#xff0c;消息保存在内存中。消费者从内存中读取消息消费&#xff0c;速…

游戏制作中没想明白的事情

当一个备忘录&#xff0c;有的是还没有时间去深入研究&#xff0c;或者没有从头了解 什么是建模绑定&#xff1f;为什么人物建模&#xff0c;初始化都是双手打开的&#xff1f;平着放武器&#xff0c;但运行的时候武器会自动竖起来&#xff0c;这是怎么做到的&#xff1f; 思…

KamaCoder 100. 岛屿的最大面积 + Leetcode 695. Max Area of Island

题目描述 给定一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的矩阵&#xff0c;计算岛屿的最大面积。岛屿面积的计算方式为组成岛屿的陆地的总数。岛屿由水平方向或垂直方向上相邻的陆地连接而成&#xff0c;并且四周都是水域。你可以假设矩阵外均…

XYCTF2024 WP

Pwn&#xff1a; hello_world(签到)&#xff1a; 这里的printf没有格式化字符串漏洞&#xff0c;但是我们依旧可以填充栈来利用printf泄露栈上信息 根据我们能填充的字节数来看&#xff0c;我们无法泄露出libc_start_main128的地址&#xff0c;但是可以泄露libc_start_call_m…

一款免费且功能强大的硬件检测工具,绿色小巧免安装!

HWiNFO是一款免费功能强大且广泛使用的硬件信息检测和监控工具&#xff0c;适用于Windows系统。它能够提供详细的硬件信息&#xff0c;包括CPU、主板、内存、硬盘、显卡等组件的详细规格和性能数据。此外&#xff0c;HWiNFO还支持实时监控硬件状态&#xff0c;如温度、电压和风…

【React Hooks原理 - useTransition】

概述 在上一篇中我们介绍了useDeferredValue的基本原理&#xff0c;本文主要介绍一下useTransition这个Hook&#xff0c;之所以在这里提到useDeferredValue&#xff0c;是因为这两个Hook都是在React18引入的进行渲染优化的Hooks&#xff0c;在某些功能上是重叠的&#xff0c;主…

面试面到自闭,字节软件测试岗五轮面试,四个小时灵魂拷问...

准备过程 我自己是本科毕业后在老东家干了两年多&#xff0c;老东家算是一家”小公司”(毕竟这年头没有 BAT 或 TMD 的 title 都不好意思报出身)&#xff0c;毕业这两年多我也没有在大厂待过&#xff0c;因此找坑的时候是非常非常虚的。迫于心慌&#xff0c;我好好思考了一阵来…

Android 性能优化(二):LeakCanary【用于分析代码是否存在内存泄漏】程序无响应

目录 1&#xff09;内存相关的五种常见问题 2&#xff09;内存溢出和内存泄漏 3&#xff09;LeakCanary是什么? 4&#xff09;LeakCanary如何使用&#xff0c;如何分析&#xff1f; 5&#xff09;LeakCanary监测的内容 提问&#xff1a;程序有时候很卡&#xff0c;经常会出现…

前端开发:Vue2.0桌面组件库-Element

引入Element的步骤&#xff1a; 1.在vscode终端中执行命令&#xff08;需要联网&#xff09; 下载成功 2.在main.js中导入element.ui组件库。 同上&#xff0c;自定义的组件需要先在根组件中引入。 3.访问官网&#xff0c;复制调整代码

变阻器的主要特性和参数有哪些?

变阻器的主要特性和参数有很多&#xff0c;下面将详细介绍几个重要的特性和参数&#xff1a; 1. 电阻范围&#xff1a;滑动变阻器的电阻范围是指其最大电阻值和最小电阻值之间的范围&#xff0c;这个范围通常由制造商指定&#xff0c;用户在选择变阻器时需要根据实际需求选择合…

基于 SSM 的汽车租赁系统

基于 SSM 的电器网上订购系统 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Spring、JSP、MyBatis 工具&#xff1a;MyEclipse/IDEA、Tomcat 引言 汽车租赁是在约定时间内&#xff0c;租赁经营人将租赁汽车&#xff08;包括载货汽车和载客汽车&#x…

AFSim 仿真系统--子系统几何考虑

子系统几何考虑 概述 由于WSF试图表示以多种方式运行的子系统&#xff08;传感器&#xff0c;武器或通信&#xff09;&#xff0c;因此它提供的定义属性的机制&#xff0c;如几何限制&#xff0c;可能相当令人生畏。本文档提供了关于这些机制如何运作以及如何定义行为类似于真实…

Axure RP:打造动态交互的大屏可视化设计利器

Axure大屏可视化是指使用Axure RP这款原型设计工具来创建具有视觉冲击力和数据展示功能的大屏幕界面。Axure以其强大的交互设计和丰富的组件库&#xff0c;成为了实现大屏可视化的重要工具之一。以下是对Axure大屏可视化的详细阐述&#xff1a; 一、Axure在大屏可视化中的优势 …