MES、SCADA下的数据采集—从TCP到你应该了解的socket通信

[复制链接]
查看314 | 回复0 | 2024-6-5 21:18:43 | 显示全部楼层 |阅读模式
>

↑ 点击上方

智能制造之家

关注我们


写在面前

大家好,我是小智,智能制造之家号主~

还记得前面写了一篇文章:

自动化早已不是原来的自动化,为何你却还是原来的你

得到了不少人的点赞,里面提到原理性思维,今天我们就从理论出发,说一说Socket通信,不论你是作为MES开发工程师,作为SCADA工程师,或者是PLC工程师,网络工程师,其实在项目中你或多或少都会遇到~

关于数据采集,前面我们已经讲了串口通讯和Modbus TCP,我想做过项目的朋友肯定也有遇到过:

MES下的数据采集——扫码器的串口通讯

MES、SCADA项目中的数据采集—基于C#与研华IO模块的ModbusTCP通讯

本次的主要内容有:

01 网络层级与TCP

02 Socket过程简述

03 Socket通信案例与Demo

04 多线程



01 网络层级与TCP

在开始介绍socket前先补充补充基础知识,在此基础上理解网络通信才会顺理成章。

TCP/IP:Transmission Control Protocol/Internet Protocol,传输控制协议/因特网互联协议,又名网络通讯协议。

简单来说:TCP控制传输数据,负责发现传输的问题,一旦有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地,而IP是负责给因特网中的每一台电脑定义一个地址,以便传输。从协议分层模型方面来讲:TCP/IP由:网络接口层(链路层)、网络层、传输层、应用层。(其实在前面文章:网络的OSI七层模型和TCP/IP五层模型 | 网络基础(三)里面就详细谈到过)它和OSI的七层结构以及对于协议族不同,下图简单表示:

现阶段socket通信使用TCP、UDP协议,相对应UDP来说,TCP则是比较安全稳定的协议了。本文只涉及到TCP协议来说socket通信。一般建立TCP需要三次握手才能建立,而断开连接则需要四次握手。(更详细的讲解可以查看:Wireshark抓包分析 TCP三次握手/四次挥手详解 |网络基础(四))整个过程如下图所示,在握手基础上延伸socket通信的基本过程。

表1 TCP/IP结构

图1 TCP/IP关系图

图2 三次握手 四次握手关系


02 Socket过程简述


在此基础上,socket连接过程:

服务器监听:服务器端socket并不定位具体的客户端socket,而是处于等待监听状态,实时监控网络状态。

客户端请求:客户端clientSocket发送连接请求,目标是服务器的serverSocket。为此,clientSocket必须知道serverSocket的地址和端口号,进行扫描发出连接请求。

连接确认:当服务器socket监听到或者是受到客户端socket的连接请求时,服务器就响应客户端的请求,建议一个新的socket,把服务器socket发送给客户端,一旦客户端确认连接,则连接建立。

注:在连接确认阶段:服务器socket即使在和一个客户端socket建立连接后,还在处于监听状态,仍然可以接收到其他客户端的连接请求,这也是一对多产生的原因。

下图简单说明连接过程:

03 Socket通信案例与Demo

代码可以详见,附件中的内容,不过唯一值得说一下的事情是,其实在TCP/IP传输的数据都应该是以字节为单位的。比如说传送50个double类型的数据就是传送400个字节的数组。所以在这个过程中,我们首先需要将这一类的数据首先转化成为字节数组才能进行传递。在这个过程中,LabVIEW主要是通过一个节点完成的转化。这个函数的主要的操作就是将任意类型的数据转化为字节数组然后在进行数据传输。

这里又要进行一个说明,由于C#本身的原因,所以在字节存储格式的时候都是由小端进行存储的,但是在TCP/IP传输格式的时候标准默认的时候都是用大端方式进行传输的,所以拿到的数据不能直接进行解析。由于这个原因这边编写一个可以完成大小端转化的类,方便用户在TCP/IP以及其他串口等类型的时候进行使用。其中GetBytes可以完成多种类型的转化,包括数值和数组。使用十分方便。

下图所示是范例运行的过程,其中127.0.0.1,是使用本机IP号的时候IP地址,其中一个是Server端一个是Client端。

同时与LabVIEW Simple TCP的范例可以共同使用,相互做Server与Client都没有问题。如下图所示。

04 多线程

介绍了通信的过程以及机制,但实际上这中间简单的TCP的通信在实际应用中是比较的,利用C# TCP多线程的应用案例,这边一起来分析一下多线程的代码,大家也可以在文章附带的Demo进行尝试。

我们点击启动服务按钮,服务器:

            // 创建负责监听的套接字,注意其中的参数;

            socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // 获得文本框中的IP对象;

            IPAddress address = IPAddress.Parse(txtIp.Text.Trim());

                // 创建包含ip和端口号的网络节点对象;

                IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));

                try

                {

                    // 将负责监听的套接字绑定到唯一的ip和端口上;

                    socketWatch.Bind(endPoint);

                }

                catch (SocketException se)

                {

                    MessageBox.Show("异常:"+se.Message);

                    return;

                }


首先我们创建负责监听的套接字, 在Bind绑定后,我们创建了负责监听的线程。代码如下:

             // 设置监听队列的长度;

            socketWatch.Listen(10);

            // 创建负责监听的线程;

            threadWatch = new Thread(WatchConnecting);

            threadWatch.IsBackground = true;

            threadWatch.Start();

            ShowMsg("服务器启动监听成功!");

            btnBeginListen.Enabled = false;

其中 WatchConnecting方法是负责监听新客户端请求的。然后让我们看一下WatchConnecting的代码。

        /// <summary>

        /// 监听客户端请求的方法;

        /// </summary>

        void WatchConnecting()

        {

            while (true)  // 持续不断的监听客户端的连接请求;

            {

                // 开始监听客户端连接请求,Accept方法会阻断当前的线程;

                Socket sokConnection = socketWatch.Accept(); // 一旦监听到一个客户端的请求,就返回一个与该客户端通信的 套接字;

                // 向列表控件中添加客户端的IP信息;

                lbOnline.Items.Add(sokConnection.RemoteEndPoint.ToString());

                // 将与客户端连接的 套接字 对象添加到集合中;

                dict.Add(sokConnection.RemoteEndPoint.ToString(), sokConnection);

                ShowMsg("客户端连接成功!");

                Thread thr = new Thread(RecMsg);

                thr.IsBackground = true;

                thr.Start(sokConnection);

                dictThread.Add(sokConnection.RemoteEndPoint.ToString(), thr);  //  将新建的线程 添加 到线程的集合中去。

            }

        }

这个线程是一直存在的,主要的任务就是监听是否有Client与Server端进行连接,如果连接成功则会另开一个线程”RecMsg”。在该线程中则主要是得到字符数据的处理,包括接受数据以及发送数据。

void RecMsg(object sokConnectionparn)

        {

                Socket sokClient = sokConnectionparn as Socket;

                while (true)

                {

                    // 定义一个2M的缓存区;

                    byte[] arrMsgRec = new byte[1024 * 1024 * 2];

                    // 将接受到的数据存入到输入  arrMsgRec中;

                    int length = -1;

                    try

                    {

                        length = sokClient.Receive(arrMsgRec); // 接收数据,并返回数据的长度;

                    }

                    catch (SocketException se)

                    {

                        ShowMsg("异常:" + se.Message);

                        // 从 通信套接字 集合中删除被中断连接的通信套接字;

                        dict.Remove(sokClient.RemoteEndPoint.ToString());

                        // 从通信线程集合中删除被中断连接的通信线程对象;

                        dictThread.Remove(sokClient.RemoteEndPoint.ToString());

                        // 从列表中移除被中断的连接IP

                        lbOnline.Items.Remove(sokClient.RemoteEndPoint.ToString());

                        break;

                    }

                    catch (Exception e)

                    {

                        ShowMsg("异常:" + e.Message);

                        // 从 通信套接字 集合中删除被中断连接的通信套接字;

                        dict.Remove(sokClient.RemoteEndPoint.ToString());

                        // 从通信线程集合中删除被中断连接的通信线程对象;

                        dictThread.Remove(sokClient.RemoteEndPoint.ToString());

                        // 从列表中移除被中断的连接IP

                        lbOnline.Items.Remove(sokClient.RemoteEndPoint.ToString());

                        break;

                    }

                    if (arrMsgRec[0] == 0)  // 表示接收到的是数据;

                    {

                        string strMsg = System.Text.Encoding.UTF8.GetString(arrMsgRec,1, length-1);// 将接受到的字节数据转化成字符串;

                        ShowMsg(strMsg);

                    }

                    if (arrMsgRec[0] == 1) // 表示接收到的是文件;

                    {

                            SaveFileDialog sfd = new SaveFileDialog();

                          

                            if (sfd.ShowDialog(this) == System.Windows.Forms.DialogResult.OK)

                            {// 在上边的 sfd.ShowDialog() 的括号里边一定要加上 this 否则就不会弹出 另存为 的对话框,而弹出的是本类的其他窗口,,这个一定要注意!!!【解释:加了thissfd.ShowDialog(this),“另存为”窗口的指针才能被SaveFileDialog的对象调用,若不加thisSaveFileDialog 的对象调用的是本类的其他窗口了,当然不弹出“另存为”窗口。】

                              

                                string fileSavePath = sfd.FileName;// 获得文件保存的路径;

                                // 创建文件流,然后根据路径创建文件;

                                using (FileStream fs = new FileStream(fileSavePath, FileMode.Create))

                                {

                                    fs.Write(arrMsgRec, 1, length - 1);

                                    ShowMsg("文件保存成功:" + fileSavePath);

                                }

                            }

                        }

                }    

        }

其实这就是建立多线程TCP通信的主要过程,这里值得注意的就是其实监听线程监听的一直都是一个固定的端口,在应用层如果建立建立连接了,则连接不会使用监听的端口号,而会使用另一个空闲的端口号,这样才能保证一直连接监听一直使用一个固定端口号,从而使得连接也变得更加容易。Demo有数据交换的,也有类似于通信的,大家都可以参考。


本文转载于简仪科技,感兴趣可以点击文末阅读原文了解


往期推荐

浅谈 MES、SCADA、PLC项目中的串口通信(232,485,422)及常见问题


MES、SCADA项目组网,如何进行冗余网络配置 | 工业通讯应用(一)


离散行业MES/MOM问题点全梳理,与流程工业MES/MOM有什么异同?


工业网络的两层与三级-从PLM到MES,再到SCADA、PLC的连接


MES系统需要的主要数据有哪些?


[ 附labview下载 ]LabVIEW与RS232串口通信


OPCUA、PROFINET、Ethercat等都支持的TSN是什么?—工业通信未来已来


OPC UA&与OPC Classic之间的数据转换


OPC UA-面向未来的工业通讯规范


PLM和MES/MOM视角下的数字化工厂构建

今天就到这里啦~ ,如果各位看官喜欢的话,欢迎点击右下角的“在看”,或转发和收藏哦。(不要忘记文末彩蛋哦)


  • 免责申明:本公众号所载文章为本公众号原创或根据网络搜索编辑整理,文章版权归原作者所有。因转载众多,无法找到真正来源,如标错来源,或对于文中所使用的图片,资料,下载链接中所包含的软件,资料等,如有侵权,请跟我们联系协商或删除,谢谢!

我们是一群智能制造技术的爱好者,我们乐于分享,我们积极向上,我们也许有些宅,但是我们很有爱,我们期待您的加入

--智能制造之家


多重福利哦

1.独学而无友则孤陋而寡闻微信公众号后台回复:入群。获取小编微信号,添加小编微信并备注“行业+姓名+城市”(格式不对能通过好友验证,但一律不加群),加入【智能制造之家】,和志同道合的朋友们共同打卡学习!

2.公众号后台回复TCP即可拿走今天文中的Demo代码。

我就知道你“在看”



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册哦

x
您需要登录后才可以回帖 登录 | 注册哦

本版积分规则