1、S7通讯回顾
- (1)建立TCP连接 Socket.Connect-》已实现
- (2)发送访问请求 COTP-》已实现
- (3)交换通信信息 Setup Communication-》已实现
- (4)执行相关操作 读、写、PLC启停、时间、上传下载-》本节实现写入bool数据
2、S7Write请求介绍
请看报文,很明显报文比读取复杂,复杂在哪里了,一是长度变长了,二是内容变多了,这个写入可以写入连续的多个,也可以写入不同区域的数据,先从简单地开始。对于写入操作,发送报文复杂,响应报文简单,因为响应报文无非就是告诉你成功还是失败。对于读取操作,发送报文简单,响应报文复杂,因为响应报文中包含了返回的数据,需要对数据进行解析,解析时会遇到大小端,字节序,字符字节,数据类型转换等。
报文中出现的几个附录在此展现下,以便找到对应的信息
附录1:COTP->PDU type已知枚举值
附录2:S7Header->ROSCTR已知枚举值
![](https://i-blog.csdnimg.cn/direct/06e670830a0542a39048130a45d62ff8.png)
附录3:S7Header->Error class已知枚举值
附录4:S7Parameter->Error code已知枚举值
附录5:S7Parameter->Function已知枚举值
附录6:S7Parameter->Item->Syntax Id已知枚举值
附录7:S7Parameter->Item->Transport size常见值
附录8:S7Parameter->Item->Area常见值
附录9:S7Data->Item->Return code已知枚举值
附录10:Userdata已知枚举值
附录11:PI service names已知枚举值
附录14:SZL-ID 类型
3、搞起来
1、先看下plc数据,前后对比下
2、写入代码
完整代码
using System;
using System.Collections.Generic;
using System.Data;
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)
{
#region 读数据
//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.WriteLine("读取2个float");
//List<float> myfloat = ReadMuiFloat();
//if (myfloat.Count != 0)
//{
// myfloat.ForEach(x => Console.WriteLine(x));
//}
#endregion
#region 写数据
WriteBool();
#endregion
}
}
}
Console.ReadKey();
}
/// <summary>
/// 写入bool
/// </summary>
static void WriteBool()
{
// TPKT
List<byte> tpktBytes = new List<byte>();
tpktBytes.Add(0x03);//Version,版本默认3
tpktBytes.Add(0x00);//Reserved,保留默认0
// --------整个字节数组的长度
// COTP
List<byte> cotpBytes = new List<byte>();
cotpBytes.Add(0x02);//当前字节以后的字节数,注意这里是指cotp这个部分,而不是指整个字节部分,cotp占3个字节,所以这里是02
cotpBytes.Add(0xf0);//PDU Type,
cotpBytes.Add(0x80);
// Header
List<byte> headerBytes = new List<byte>();
headerBytes.Add(0x32);//Protocol Id,默认
headerBytes.Add(0x01);//ROSCTR:JOB
headerBytes.Add(0x00);//Redundancy Identification (Reserved),占2个字节
headerBytes.Add(0x00);
headerBytes.Add(0x00);//Protocol Data Unit Reference,占2个字节
headerBytes.Add(0x00);
// 添加Parameter字节数组的长度
// 添加Data字节数组的长度
// Parameter
List<byte> paramBytes = new List<byte>();
paramBytes.Add(0x05);// 功能码,固定的
paramBytes.Add(0x01);//item cout数量,一个
#region Item1
List<byte> itemBytes_1 = new List<byte>();
itemBytes_1.Add(0x12);//结构标识,一般默认0x12
itemBytes_1.Add(0x0a);//此字节往后的字节长度,不包括自己,0x0a转换成10进制就是10,所以从它后面有10个字节,但不包括自己
itemBytes_1.Add(0x10);//Syntax Id: S7ANY
itemBytes_1.Add(0x01);// 读取类型 01-Bit 02-Byte 03-Char 04-Word 06-DWord
// 写入长度
itemBytes_1.Add(0x00);
itemBytes_1.Add(0x01);
// DB块编号
itemBytes_1.Add(0x00);
itemBytes_1.Add(0x01);
// 数据区域
itemBytes_1.Add(0x84); //81>I 82>Q 83>M 84>DB
// 地址DB1.DBX0.0
//int address = startAddr * 8 + bitAddr;
int addr = (0 << 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 data
List<byte> dataBytes_1 = new List<byte>();
dataBytes_1.Add(0x00);
dataBytes_1.Add(0x03);//Transport size
//数据长度
dataBytes_1.Add(0x00);
dataBytes_1.Add(0x01);
//数据值
dataBytes_1.Add(0x00);// true为01,false为00
dataBytes_1.Add(0x00);// 填充字节
#endregion
paramBytes.AddRange(itemBytes_1);
// 拼装Header&Parameter
headerBytes.Add((byte)(paramBytes.Count / 256 % 256));
headerBytes.Add((byte)(paramBytes.Count % 256)); // Parameter Lenght
headerBytes.Add((byte)(dataBytes_1.Count / 256 % 256));
headerBytes.Add((byte)(dataBytes_1.Count % 256));// Data Length
headerBytes.AddRange(paramBytes);
headerBytes.AddRange(dataBytes_1);
// 拼装COTP&Header
cotpBytes.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());
// TPKT占4个字节
byte[] bytes = new byte[4];
socket.Receive(bytes, 0, 4, SocketFlags.None);//先接收前4个字节,即TPKT部分
byte[] lenBytes = new byte[2];//响应报文的整个字节长度
lenBytes[0] = bytes[3];//高位
lenBytes[1] = bytes[2];//低位
short len = BitConverter.ToInt16(lenBytes,0);//将字节转换成整形
//这里减4的目的就是减去TPKT的长度
len -= 4;//除TPKT后剩下的长度
//获取除TPKT之后的所有字节
byte[] respBytes = new byte[len];
socket.Receive(respBytes, 0, len, SocketFlags.None);
if (respBytes[13] == 0x00&& respBytes[14] == 0x00&&respBytes[17] == 0xff)
{
Console.WriteLine("写入成功");
}
}
/// <summary>
/// 读取多个float数据
/// </summary>
/// <returns></returns>
static List<float> ReadMuiFloat()
{
List<float> listfloat = new List<float>();
// TPKT
List<byte> tpktBytes = new List<byte>();
tpktBytes.Add(0x03);
tpktBytes.Add(0x00);
// --------整个字节数组的长度
// COTP
List<byte> cotpBytes = new List<byte>();
cotpBytes.Add(0x02);
cotpBytes.Add(0xf0);
cotpBytes.Add(0x80);
// Header
List<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字节数组的长度
// Parameter
List<byte> paramBytes = new List<byte>();
paramBytes.Add(0x04);// Read Var
paramBytes.Add(0x01);// 如果有多个区域请求的情况下,这里需要计算,计算Item的个数
// Item Bytes
#region Item1
List<byte> itemBytes_1 = new List<byte>();
itemBytes_1.Add(0x12);
itemBytes_1.Add(0x0a);
itemBytes_1.Add(0x10);
itemBytes_1.Add(0x06);// 读取类型: 01Bit 02Byte 03Char 04Word 06DWord
// 读取长度
itemBytes_1.Add(0x00);
itemBytes_1.Add(0x04);
// DB块编号 DB1.DBD6
itemBytes_1.Add(0x00);
itemBytes_1.Add(0x01);
// 数据区域
itemBytes_1.Add(0x84); //81=I 82=Q 83=M 84=DB
// 地址DB1.DBD6
//int address = startAddr * 8 + bitAddr;
int addr = (6 << 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&Item
paramBytes.AddRange(itemBytes_1);
// 拼装Header&Parameter
headerBytes.Add((byte)(paramBytes.Count / 256 % 256));
headerBytes.Add((byte)(paramBytes.Count % 256));
headerBytes.Add(0x00);
headerBytes.Add(0x00);
headerBytes.AddRange(paramBytes);
// 拼装COTP&Header
cotpBytes.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());
// 拿多少数据
// TPKT
byte[] 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 code
int 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);// 数据响应长度
int typeLen = 4;//数据长度,float占4个字节
List<byte> dataList = new List<byte>();
byte[] dataBuffer = new byte[typeLen];//返回的具体数据
// 所有返回的数据字节
for (int sit = 0; sit < dataLen/typeLen;sit++)
{
int myter = sit;
myter++;
Array.Copy(buffer, index + myter*4, dataBuffer, 0, typeLen); //数组拷贝
dataList = new List<byte>(dataBuffer);
dataList.Reverse();// 反转顺序,处理大小端问题
listfloat.Add(BitConverter.ToSingle(dataList.ToArray(), 0));//字节转换成浮点
}
}
return listfloat;
}
/// <summary>
/// 读取多个short数据
/// </summary>
/// <returns></returns>
static List<short> ReadMuiShort()
{
List<short> listshort = new List<short>();
// TPKT
List<byte> tpktBytes = new List<byte>();
tpktBytes.Add(0x03);
tpktBytes.Add(0x00);
// --------整个字节数组的长度
// COTP
List<byte> cotpBytes = new List<byte>();
cotpBytes.Add(0x02);
cotpBytes.Add(0xf0);
cotpBytes.Add(0x80);
// Header
List<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字节数组的长度
// Parameter
List<byte> paramBytes = new List<byte>();
paramBytes.Add(0x04);// Read Var
paramBytes.Add(0x02);// 如果有多个区域请求的情况下,这里需要计算,计算Item的个数
// Item Bytes
#region Item1
List<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 Item2
List<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&Item
paramBytes.AddRange(itemBytes_1);
paramBytes.AddRange(itemBytes_2);
// 拼装Header&Parameter
headerBytes.Add((byte)(paramBytes.Count / 256 % 256));
headerBytes.Add((byte)(paramBytes.Count % 256));
headerBytes.Add(0x00);
headerBytes.Add(0x00);
headerBytes.AddRange(paramBytes);
// 拼装COTP&Header
cotpBytes.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());
// 拿多少数据
// TPKT
byte[] 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 code
int 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+4
Array.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+8
Array.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()
{
// TPKT
List<byte> tpktBytes = new List<byte>();
tpktBytes.Add(0x03);
tpktBytes.Add(0x00);
// --------整个字节数组的长度
// COTP
List<byte> cotpBytes = new List<byte>();
cotpBytes.Add(0x02);
cotpBytes.Add(0xf0);
cotpBytes.Add(0x80);
// Header
List<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字节数组的长度
// Parameter
List<byte> paramBytes = new List<byte>();
paramBytes.Add(0x04);// Read Var
paramBytes.Add(0x01);// 如果有多个区域请求的情况下,这里需要计算,计算Item的个数
// Item Bytes
#region Item1
List<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&Item
paramBytes.AddRange(itemBytes_1);
// 拼装Header&Parameter
headerBytes.Add((byte)(paramBytes.Count / 256 % 256));
headerBytes.Add((byte)(paramBytes.Count % 256));
headerBytes.Add(0x00);
headerBytes.Add(0x00);
headerBytes.AddRange(paramBytes);
// 拼装COTP&Header
cotpBytes.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());
// 拿多少数据
// TPKT
byte[] 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 code
int 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,版本默认3
tpktBytes.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:JOB
headerBytes.Add(0x00); // Redundancy Identification (Reserved
headerBytes.Add(0x00); //
headerBytes.Add(0x00); // Protocol Data Unit Reference
headerBytes.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); // 结构标识,一般默认0x12
itemBytes.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&Item
paramBytes.AddRange(itemBytes);
// 拼装Header&Parameter
headerBytes.Add((byte)(paramBytes.Count / 256 % 256));//Parameter长度
headerBytes.Add((byte)(paramBytes.Count % 256));
headerBytes.Add(0x00);//Data length,读取操作没有数据,这里自然是0
headerBytes.Add(0x00);
headerBytes.AddRange(paramBytes);
// 拼装COTP&Header
cotpBytes.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); // TPKT
byte[] 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 code
int 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,//版本默认3
0x00,//保留默认0
0x00,//整个请求字节数高位
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,//默认值,协议id
0x01,//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进制就是960
0xc0
};
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+18
byte[] cotpBytes = new byte[] {
//1)TPKT包括4个字节
0x03,//版本号,版本默认3
0x00,//默认保留为0
0x00,//整个请求字节高位
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 length
0x0a // 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、小结
写入数据时,发送报文复杂,响应报文简单,送人玫瑰,留有余香,下节继续冲上去
原创不易,打字截图不易,走过路过,不要错过,欢迎点赞,收藏,转载,复制,抄袭,留言,动动你的金手指,早日实现财务自由