西门子s7通信协议

目录

西门子s7通信协议

s7协议的使用

S7协议帧结构

使用tcp五次握手进行连接

数据的读取

接收数据的响应

数据的写入

完整代码


西门子s7通信协议

S7Comm(S7 Communication)是西门子专有的协议,是西门子S7通讯协议簇里的一种。 S7通信协议是西门子S7系列PLC内部集成的一种通信协议,是S7系列PLC的精髓所在。 它是一种运行在传输层之上的(会话层/表示层/应用层)、经过特殊优化的通信协议,其信息传输可以基于MPI网络、 PROFIBUS网络或者以太网 s7在TCP连接上后还需要进行两次握手 S7协议的TCP/IP实现依赖于面向块的ISO传输服务。S7协议被封装在TPKT和ISO-COTP协议中,这使得PDU(协议数据单元) 能够通过TCP传送。

s7协议的使用

使用tcp连接时需要进行五次握手,其中有三次是tcp客户端与服务器的基有链接,然后需要再通过s7协议发送两次请求连接,共为五次握手。

S7协议帧结构

1 TPKT 会话层 主要设置版本号 预留号 报文总长度

2 COPT 表示层 设置PDU类型

3 s7协议 应用层 设置协议头和协议参数等

使用tcp五次握手进行连接

cs 复制代码
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    /// <summary>
    /// 五次握手
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void button1_Click(object sender, EventArgs e)
    {
        // s7协议
        // 1 需要通过socket三次握手,不用写握手过程
        // 目前提供设备型号s71200  cpu:1212c    电压是24vDC
        TcpClient client = new TcpClient();
        client.Connect("192.168.107.202",102); // 连接服务器

        receiveData(client);

        // 2 第一请求连接  发送请求帧为
        // 总共22个字节
        byte[] bs1 = new byte[]
        {
            0x03, // 1字节版本号 默认是03
            0x00, // 1字节 保留值 默认0
            0x00, 0x16, // 2 字节 报文的总长度

            0x11, // 1字节从该字节往后字节个数 十进制是17
            0xE0, // PDU 类型
            0x00,0x00, // DST引用 默认值
            0x00,0x01, // src引用
            0x00, // 采用默认值
            0xc1, // 上位机擦书
            0x02, // 上位机长度
            0x10,0x00, // 0x01代表双边通信 0x00机架号和插槽号
            
            0xC2, // plc参数
            0x02, // 长度

            0x03,0x01, // 0x01和0x00 共同控制机架号和插槽
            0xC0,0x01,
            0x0a
        };
        client.GetStream().Write(bs1,0,bs1.Length); // 发送第一次请求帧 


        // 3 第二次请求连接 发送请求帧为
        bs1 = new byte[]
        {
            0x03, // 1字节版本号 默认是03
            0x00, // 1字节 保留值 默认0
            0x00, 0x19, // 2 字节 报文的总长度

            0x02, // 当前字节后的字节数
            0xF0, // PUD类型 数据传输
            0x80, // 最高是十进制128

            0x32, // 协议ID,固定值
            0x01, // 工作类型 0x01 主站发送请求
            0x00,0x00,
            0x00,0x00,
            0x00,0x08, // 参数长度
            0x00,0x00, // 数据长度

            0xF0, // 功能码
            0x00, // Reserved保留值
            0x00,0x03, // 允许操作最大工作队列
            0x00,0x03, 
            0x03,0xc0, // 允许处理最大字节数组
        };
        client.GetStream().Write(bs1,0,bs1.Length);
        MessageBox.Show("连接成功");

    }

    /// <summary>
    /// 接收响应数据集
    /// </summary>
    /// <param name="tcpClient"></param>
    public void receiveData(TcpClient tcpClient)
    {
        Task.Run(() =>
        {
            byte[] bytes = new byte[1024];
            while (tcpClient.Connected)
            {
                // ONE
                int count = tcpClient.GetStream().Read(bytes,0,bytes.Length);
                if (count == 0) return;
                Console.WriteLine(BitConverter.ToString(bytes, 0, count) + "\r\n");

                // TWO
                byte[] s = new byte[count];
                Array.Copy(bytes,s,count);
                Console.WriteLine(string.Join(",",s));
            }
        });
    }
}

数据的读取

cs 复制代码
/// <summary>
/// 读取M区
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button2_Click(object sender, EventArgs e)
{
    // 发送请求帧 请求M区地址从00开始 读取一个数据
    // 读取数据时的请求帧
    byte[] data = new byte[] 
    {
        // TPKT: 版本号 预留号 总字节长度
        0x03, // 版本号 
        0x00, // 预留号
        0x00,0x01F, // 总字节长度

        // COTP: 
        0x02, // 往下的长度
        0xF0, // PDU类型
        0x80, // 目标引用

        // s7-header s7头
        0x32, // 协议ID 默认
        0x01, // 主站开始发请求
        0x00,0x00, // 预留位置
        0x03,0x7b, // 随机生成的数字 每次在基础之上递增
        0x00,0x0e, // 参数长度
        0x00,0x00, // 数据长度

        // s7-参数部分
        0x04, // 功能码 读取功能                 重点
        0x01, // 如果涉及多读时候 设置为1,
        0x12, // 结构表示 一般默认12
        0x0a, // 往后的字节长度
        0x10, // 寻址模式
        0x02, // 读取的数据类型 02是字节类型
        0x00,0x01, // 读取长度                   重点
        0x00,0x00, // 读取不是DB区               重点
        0x83, // 0x83 M存储区,0x84DB块          重点
        0x00,0x00,0x70, // 开始数据起始地址      重点

        // 列如M30000, 实际地址是30000*8=24 00000,把转成山歌字节,转成16进制3a980 对应三个地址,0x03,0xA9 0x80
        // 列如数据DB块的数据,DB21234,4000,其中DB号是21234 转成16进制0x52F2,DB区改为0x52,0xF2
        // 40000*8=,2000,转成16进制7d00 转成三个字节变成 0x00,0x7d,0x00
    };
    socket.Send(data);
}

接收数据的响应

cs 复制代码
/// <summary>
/// 接收响应数据
/// </summary>
void startReceive()
{
    Task.Run(() =>
    {
        byte[] bytes = new byte[1024];
        while (true)
        {
            int count = socket.Receive(bytes);
            if (count == 0) break;

            // 转为16进制的字符串
            Console.WriteLine("十六进制打印:"+BitConverter.ToString(bytes,0,count));

            // 转为十进制打印
            byte[] datas = new byte[count];
            Array.Copy(bytes,datas,count);
            Console.WriteLine("十进制打印:" + string.Join(",",datas));

            Invoke(new Action(() =>
            {
                try
                {
                    this.label1.Text = datas[25].ToString();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }));


            /* 响应的数据
            * 连接的响应
            * 03-00-00-16-11-D0-00-01-00-08-00-C0-01-0A-C1-02-10-00-C2-02-03-01
            * 03-00-00-1B-02-F0-80-32-03-00-00-00-00-00-08-00-00-00-00-F0-00-00-03-00-03-00-F0
            * 
            * 读取数据的响应
            * 03-00-00-1A-02-F0-80-32-03-00-00-03-7B-00-02-00-05-00-00-04-01-FF(读取成功的标志)-04(读取的数据类型)-00-08(数据的长度)-0C(数据)
            */

        }
    });
}

数据的写入

cs 复制代码
/// <summary>
/// 写入M14
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
    byte[] value = BitConverter.GetBytes(uint.Parse(textBox1.Text));
    // 生成写的报文
    byte[] bs = new byte[]
    {
        // TPKT部分
        0x03, // 版本号
        0x00, // 预留号
        // 0x00,0x24, // 报文总长度36
        0x00,0x27, // 报文总长度39

        // TOPT
        0x02, // 长度
        0xF0, // PDU类型
        0xB0, // 目标引用

        // s7 header
        0x32, // 协议id
        0x01, // 主站开始请求

        0x00,0x00, // 预留部分

        0x03,0x7d, // 随机生成
        0x00,0x0E, // 参数长度
        0x00,0x08, // 参数数据长度

        // s7 参数
        0x05, // 05代表写入,04代表读取
        0x01, // 通信项数 可以支持多写
        0x12, // 变量指定
        0x0A, // 后面的长度
        0x10,
        0x02, // 传输数据类型 字节

        // 0x00,0x01, // 操作数据的长度
        0x00,0x04, // 操作数据的长度

        0x00,0x00, // M区 不是DB区
        0x83, // M区

        // 0x00,0x00,0x70, // 开始写入的地址M14
        0x00,0x3e,0x80, // 开始写入的地址M2000

        0x00,
        0x04, // 字节类型

        // 0x00,0x80, // 写入的长度  8位=1字节
        0x00,0x20, // 写入的长度  8位=1字节 (写入4个数据 4*8=32转16进制为20)

        //byte.Parse(textBox1.Text)
        value[3],value[2],value[1],value[0]
    };
    tcp.Send(bs);
    Type = RequestType.Write;
}

完整代码

cs 复制代码
public partial class Form1 : Form
{
    TcpClientHelper tcp;
    public Form1()
    {
        InitializeComponent();
    }

    private void Tcp_OnClose(TcpClientHelper obj)
    {
        MessageBox.Show("客户端关闭");
    }

    enum RequestType
    {
        Write,  // 写入请求
        Read,   // 读取请求
        Connect // 连接的请求
    }
    RequestType Type;


    private void Tcp_OnMessage(byte[] arg1, TcpClientHelper arg2)
    {
        // 获取数据即可 自动触发
        BeginInvoke(new Action(() =>
        {
            switch (Type)
            {
                case RequestType.Write:
                    // 写入数据的响应
                    Console.WriteLine("写入数据的响应"+BitConverter.ToString(arg1) );
                    break;
                case RequestType.Read:
                    // 读取数据的响应
                    Console.WriteLine("读取数据的响应" + BitConverter.ToString(arg1));
                    this.label1.Text = arg1[arg1.Length-1].ToString();
                    break;
                case RequestType.Connect:
                    Console.WriteLine("连接时的响应" + BitConverter.ToString(arg1));
                    break;
                    
            }
        }));
    }

    /// <summary>
    /// 写入M14
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void button1_Click(object sender, EventArgs e)
    {
        byte[] value = BitConverter.GetBytes(uint.Parse(textBox1.Text));
        // 生成写的报文
        byte[] bs = new byte[]
        {
            // TPKT部分
            0x03, // 版本号
            0x00, // 预留号
            // 0x00,0x24, // 报文总长度36
            0x00,0x27, // 报文总长度39

            // TOPT
            0x02, // 长度
            0xF0, // PDU类型
            0xB0, // 目标引用

            // s7 header
            0x32, // 协议id
            0x01, // 主站开始请求

            0x00,0x00, // 预留部分

            0x03,0x7d, // 随机生成
            0x00,0x0E, // 参数长度
            0x00,0x08, // 参数数据长度

            // s7 参数
            0x05, // 05代表写入,04代表读取
            0x01, // 通信项数 可以支持多写
            0x12, // 变量指定
            0x0A, // 后面的长度
            0x10,
            0x02, // 传输数据类型 字节

            // 0x00,0x01, // 操作数据的长度
            0x00,0x04, // 操作数据的长度

            0x00,0x00, // M区 不是DB区
            0x83, // M区

            // 0x00,0x00,0x70, // 开始写入的地址M14
            0x00,0x3e,0x80, // 开始写入的地址M2000

            0x00,
            0x04, // 字节类型

            // 0x00,0x80, // 写入的长度  8位=1字节
            0x00,0x20, // 写入的长度  8位=1字节 (写入4个数据 4*8=32转16进制为20)

            //byte.Parse(textBox1.Text)
            value[3],value[2],value[1],value[0]
        };
        tcp.Send(bs);
        Type = RequestType.Write;
    }

    /// <summary>
    /// 读取M14
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void button2_Click(object sender, EventArgs e)
    {
        // 读取的报文31字节
        byte[] bs = new byte[]
        {
            0x03,
            0x00,
            0x00,0x1f,

            0x02,
            0xf0,
            0xb0,

            // 协议参数
            0x32,
            0x01,
            0x00,0x00,
            0x03,0x7d,
            0x00,0x0e,
            0x00,0x00, // 读取操作 为0

            0x04,0x01,
            0x12,0x0a,
            0x10,
            0x02,

            //0x00,0x01, // 读取长度
            0x00,0x04, // M2000 读取四个字节

            0x00,0x00,
            0x83,

            // 0x00, 0x00,0x70
            0x00,0x3e,0x80,
        };
        tcp.Send(bs);
        Type = RequestType.Read;
    }

    /// <summary>
    /// 连接
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Form1_Load(object sender, EventArgs e)
    {
        // 1 创建客户端对象
        tcp = new TcpClientHelper();
        tcp.Connect("192.168.107.202", 102); // 连接服务器

        // 2 获取数据
        tcp.OnMessage += Tcp_OnMessage;

        // 3 关闭连接
        tcp.OnClose += Tcp_OnClose;

        // 第一次连接请求
        byte[] bs = new byte[]
        {
            0x03, // 1字节版本号 默认是03
            0x00, // 1字节 保留值 默认0
            0x00, 0x16, // 2 字节 报文的总长度

            0x11, // 1字节从该字节往后字节个数 十进制是17
            0xE0, // PDU 类型
            0x00,0x00, // DST引用 默认值
            0x00,0x01, // src引用
            0x00, // 采用默认值
            0xc1, // 上位机擦书
            0x02, // 上位机长度
            0x10,0x00, // 0x01代表双边通信 0x00机架号和插槽号
            
            0xC2, // plc参数
            0x02, // 长度

            0x03,0x01, // 0x01和0x00 共同控制机架号和插槽
            0xC0,0x01,
            0x0a
        };
        tcp.Send(bs);

        // 第二次请求连接
        bs = new byte[]
        {
            0x03, // 1字节版本号 默认是03
            0x00, // 1字节 保留值 默认0
            0x00, 0x19, // 2 字节 报文的总长度

            0x02, // 当前字节后的字节数
            0xF0, // PUD类型 数据传输
            0x80, // 最高是十进制128

            0x32, // 协议ID,固定值
            0x01, // 工作类型 0x01 主站发送请求
            0x00,0x00,
            0x00,0x00,
            0x00,0x08, // 参数长度
            0x00,0x00, // 数据长度

            0xF0, // 功能码
            0x00, // Reserved保留值
            0x00,0x03, // 允许操作最大工作队列
            0x00,0x03,
            0x03,0xc0, // 允许处理最大字节数组
        };
        tcp.Send(bs);
        Type = RequestType.Connect;
        MessageBox.Show("连接成功");
    }
}
相关推荐
九河云21 分钟前
如何选择适合的AWS EC2实例类型
服务器·云计算·aws
Tassel_YUE1 小时前
网络自动化04:python实现ACL匹配信息(主机与主机信息)
网络·python·自动化
其乐无涯2 小时前
服务器技术(一)--Linux基础入门
linux·运维·服务器
Diamond技术流2 小时前
从0开始学习Linux——网络配置
linux·运维·网络·学习·安全·centos
写bug的小屁孩2 小时前
前后端交互接口(三)
运维·服务器·数据库·windows·用户界面·qt6.3
斑布斑布2 小时前
【linux学习2】linux基本命令行操作总结
linux·运维·服务器·学习
紅色彼岸花2 小时前
第六章:DNS域名解析服务器
运维·服务器
Spring_java_gg2 小时前
如何抵御 Linux 服务器黑客威胁和攻击
linux·服务器·网络·安全·web安全
✿ ༺ ོIT技术༻2 小时前
Linux:认识文件系统
linux·运维·服务器
恒辉信达2 小时前
hhdb数据库介绍(8-4)
服务器·数据库·mysql