手摸手教你撕碎西门子S7通讯协议03--COTP连接请求
手摸手教你撕碎西门子S7通讯协议03--COTP连接请求
1、S7通讯回顾
- (1)建立TCP连接 Socket.Connect-》已实现
- (2)发送访问请求 COTP-》本节实现
- (3)交换通信信息 Setup Communication-》待实现
- (4)执行相关操作 读、写、PLC启停、时间、上传下载-》待实现
2、COTP请求介绍
在上节的socket三次握手成功之后,并不能马上进行数据交换,需要进行COTP连接请求,即客户端发送COTP报文给服务端,在COTP报文中包含“连接请求”和“Destination TSAP”,以明确CPU的机架号和槽号;服务端应答COTP报文,包含“连接确认”;这样服务端就清楚了客户端需要和哪个CPU来进行数据通讯。
COTP分为两个部分,一是COTP连接包,一是COTP功能包。
COTP连接包:COTP连接包主要用于建立、维护和断开COTP协议层的连接。这一过程包括连接请求(CR-Connect Request)、连接确认(CC-Connect Confirm)、断开请求(DR-Disconnect Request)和断开确认(DC-Disconnect Confirm)等步骤。通过这些连接包,COTP能够在通信双方之间建立一个稳定的、可靠的会话,保证数据的顺序传输和完整性。
COTP功能包:COTP功能包用于在已建立的COTP连接上进行数据传输和控制信息交换。这包括数据传输(DT-Data Transfer)功能,通过它发送用户数据;以及一些控制功能,如流量控制、复位、错误报告等。功能包使得COTP能够在连接的基础上提供高效、可靠的数据交换服务。
简而言之,COTP连接包主要用于建立和管理连接,而COTP功能包则用于在这些连接上进行实际的数据传输和控制操作,在这个过程中,有非常标准和严重要求的报文格式,这个过程会有22个字节,每个报文的字段内容代表不同的含义,千万不能搞错,报文如下
那么难点来了,如何熟悉消化这些东东了,没有关系,一回生,二回熟,三回精,看我如何手摸手教你,首先明白几点事项:
1)cotp报文请求22个字节,响应也是22个字节,为什么是22,从0到21共有22个,所以是22,发送和响应正好相等,都是22个,其中tpkt占4个字节(0--3),cotp占18个字节(4--21),这些字节如何组装了,在程序中可以用list集合对象进行一个个拼起来,利用数组对象一个个加进去也行,随便怎么弄,都是棒棒哒,最后用socket对象的send方法发送来实现
2)0x是16进制表示方式,所有的报文都是以16进制格式,不是10进制,16进制是0-9,a-f
3)0x00格式的表示一个字节,即byte,一个字节包括8个位,即8个bit,Hi表示高位,Lo表示低位
4)PDU type是一个枚举值,意思是只能从中选择某个值,不能自己定义,这是协议自身规定的,具体如下:
看报文,发送时这里要设置为e0,响应时正常是d0,也就是说如果响应的报文中第6个(索引为5)为d0则表示正常响应了,否则就是有错误,这就是判断通信是否正常的标识,这个就要在程序中进行判断处理,那怎么判断处理了?当然是利用socket对象接收响应的报文,然后分析报文,最后输出结果。
3、开搞COTP请求
完整代码如下:
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;
static void Main(string[] args)
{
Connect();
if (connectState)
{
COTPConnection();
}
Console.ReadKey();
}
private static void 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连接响应异常");
}
else
{
Console.WriteLine("太好了,COTP连接响应正常");
}
}
catch (Exception ex)
{
Console.WriteLine("COTP连接未建立!" + ex.Message);
}
}
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 ? "连接成功" : "连接失败");
}
}
}
看到了吗?伙伴们,就是这样搞,向数组中一个个添加字节并以0x格式,前面说过,0x是16进制格式,特别注意这里,数组类型是字节数组byte[],当然用list也可,随便玩,6得很,只是说socket发送命令send需要的是数组,所以直接用了数组。对于响应处理,看这个
这个报文搞懂了,对后面的确有巨大帮助
4、运行程序
5、小结
这就是第2个流程,建立COTP连接,它是通过socket对象通讯实现的,小伙伴们,明白了不?下节继续雄起来。