精华内容
下载资源
问答
  • Galil C#通讯链接范例

    2018-10-14 22:32:01
    Galil C#通讯链接范例 只是C#与Galil控制卡的链接,没有其他内容
  • SuperSocket Socket C# 通讯详细介绍,不错的 Socket,学习很有用处SuperSocket Socket 技能
  • C#通讯调试工具经典实用源码2019
  • 利用thrift实现js与C#通讯的实例代码,需要的朋友可以参考一下
  • C# 通讯TCP异步

    2019-09-22 15:16:29
    C# 实现的 TCP高并发异步通讯,本人自己所写,并测试! 一起学习!C# 实现的 TCP高并发异步通讯,本人自己所写,并测试! 一起学习!
  • Thrift初探:简单实现C#通讯服务程序
  • 松下PLC与C#通讯(串口调试入门教程),里面内容还可以,希望能帮到有需要的朋友们
  • 【C#串口编程计划】C#通讯类库构建

    千次阅读 2017-03-20 14:22:36
    这篇图文是关于【串口编程计划】的最后一篇“C#通讯类库的构建”,也就是把这次所做的关于串口系统的代码集成到团队的类库中,做到代码的可复用(以后写串口的系统,只需要写数据的定义,协议的解析部分就好,通讯的...

    这篇图文是关于【串口编程计划】的最后一篇“C#通讯类库的构建”,也就是把这次所做的关于串口系统的代码集成到团队的类库中,做到代码的可复用(以后写串口的系统,只需要写数据的定义,协议的解析部分就好,通讯的部分不用在重复写了)、可扩展(串口通讯系统转换成网口通讯系统只需要修改配置文件就好,不需要修改代码)、灵活性好(串口、网口、Wifi、蓝牙等等,做到AddIn,类似工业中的柔性生产线,按照需要定制就好,不需要修改代码)。
    这里写图片描述
    通讯的方式有许多种,比如串口通信、网口通信、WIFI通讯、蓝牙通信、射频卡通讯等等,但所有的通讯无外乎都是打开连接、关闭连接、读取数据、写入数据等操作,所以我们定义一个统一的接口ILSGOCommunication。其中事件DataReceived用于接收到数据时,来触发注入的执行方法。

    无论何种通信,都需要进行配置,所以定义一个通用的配置接口ILSGOCommunicationSetting。利用这个接口来实现通用的通讯配置。由于,我们刚做的系统是基于RS232串口的,所以我们写了两个实体类来实现这两个接口。后面我们会把基于网口Socket的实体类补充进来。其实都是一样,一样的。
    这里写图片描述
    有了这两个接口,我就可以编写依赖于此接口的功能或者软件了。当然,我们还需要写有关协议的分析。
    这里写图片描述
    协议通常分为两种,一种是基于文本的协议,一种是基于二进制的协议。

    虽然协议种类不同但对两种协议的处理逻辑是一样的。

    首先把接收的数据放入自定义的缓存区List buffer中,然后根据协议的约定:

    文本协议:报头+数据+报尾

    二进制协议:报头+长度+数据+校验

    从缓存区中取出每一帧的数据存入byte[] Raw利用Analyze方法进行解析。这样我们定义IAnalyzer接口,以便统一报文的处理逻辑,IAnalyzerCollection是IAnalyzer的集合类,枚举类型lsgoSearchResult用来标识SearchBuffer方法寻找报文的结果。
    这里写图片描述
    抽象类AnalyzeResult是IAnalyzer的默认实现。Data属性为解析后的数据,Raw属性为原始数据,TimeOut为数据失效的等待时间,比如你获取下位机发来的电压,过了几秒了,应该就无效了,所以要考虑定时失效(通过AnalyzeResult中的Timer开启线程检测),Valid标识了解析报文是否成功。GetNew事件为解析成功后,注入的执行方法。之所以这样写,是因为不同的系统,报文解析成功后所要处理的逻辑可能不同,有的系统直接写入数据库,有的系统直接写入文件,有的系统却仅仅需要界面显示一下就好。

    TextAnalyzeResult和BinaryAnalyzeResult是针对解析文本协议和二进制协议写的抽象类。

    BeginOfLine、EndOfLine对应报头与报尾,Encoding对应编码方式。常用的是ASCII码,若要支持中文,则需要设置为Unicode编码。

    Mask、LenLength、Checksum对应二进制协议的报头、长度和校验。由于校验的方法很多,常用的有CRC校验,异或校验等等,所以校验这块写成事件注入的方式(CheckSumHandler)以便实际应用中根据不用的校验方式注入不同的执行代码。
    这里写图片描述
    这些接口可以直接被外部使用,但是这样对不熟悉的使用者感到复杂。所以我们用一个实体类来包装这些接口。所以写一个带有分析功能的类:LSGOComm。

    _lstBuffer是接收到数据进行存放的缓冲区,ReadBufferSize为该缓冲区设置大小。Comm属性配置实际需要的通讯(比如上面写的串口通讯),Result属性配置需要解析的实际协议。OnDraw为接收到原始数据后注入的方法,即接收到原始数据,需要执行的代码。通过Comm_DataReceived方法整合分析的代码注入到通讯(ILSGOCommunication)的DataReceived事件中,这样,接收数据,分析数据,解析数据就全部串联起来了。至此,通讯类库的框架就完成了。而这也是使用通讯类库所需要关注的所有内容。

    下面举两个例子看看如何使用啊!

    第一个是基于文本协议的,后面使用这套代码处理基于文本协议报文的时候,只需要修改这块就好。
    这里写图片描述
    第二个是基于二进制协议的,后面使用这套代码处理二进制报文时就改这块就好。
    这里写图片描述
    这里写图片描述
    这里写图片描述

    展开全文
  • bartender与c#通讯示例

    2020-03-19 15:13:54
    c#与bartender通过调用con 组件实现通讯
  • C#通讯编程实例基于ICE 非常适合C#初学者编程
  • 一叶知秋 C#通讯调试工具v3.0测试版(含源码) 网络监控软件代码
  • Sharp7.cs C#通讯代码

    2019-06-30 22:17:39
    C#通过s7通讯,读写plc内部寄存器数值,可做上位机通讯
  • C#通讯封装

    2014-03-18 10:00:01
    对于高手,这一个封装可看可不看,对于新手,这一个封装可以学习,对于刚入门的,你看起来有点难度。 socket的传输封装,包含了client和server,socket是异步的,用了IOCP。... 工程并不是非常完善,我只是前期写好了,...
  • C#通讯编程(整理)

    千次阅读 2011-05-18 08:16:00
    C#通讯编程(整理)


        public class XmlSocket
        {

            
    //异步socket诊听
            
    // Incoming data from client.从客户端传来的数据

    public static string data =null;

            
    // Thread signal.线程 用一个指示是否将初始状态设置为终止的布尔值初始化 ManualResetEvent 类的新实例

    public static ManualResetEvent allDone =new ManualResetEvent(false);
            
    //static void Main(string[] args)
            
    //{
            
    //    StartListening();
            
    //}

    public static void StartListening()
            {
                
    // Data buffer for incoming data. 传入数据缓冲

    byte[] bytes =new Byte[1024];
                
    // Establish the local endpoint for the socket. 建立本地端口
                
    // The DNS name of the computer
                
    // running the listener is "host.contoso.com".

                IPAddress ipAddress;
                String ipString
    = ConfigurationManager.AppSettings.Get("SocketIP");
                
    if (ipString==null|| ipString ==String.Empty)
                {  
                    IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
                    ipAddress
    = ipHostInfo.AddressList[0];
                }
                
    else
                {
                    ipAddress
    = IPAddress.Parse(ipString);
                }

                
    int port;
                String portString
    = ConfigurationManager.AppSettings.Get("SocketPort");
                
    if (portString==null || portString==String.Empty)
                {
                    port
    =11001;
                }
                
    else
                {
                    port
    =int.Parse(portString);
                }
                IPEndPoint localEndPoint
    =new IPEndPoint(ipAddress, port);

                
    // Create a TCP/IP socket.
                Socket listener =new Socket(AddressFamily.InterNetwork,
                 SocketType.Stream, ProtocolType.Tcp);

                
    // Bind the socket to the local endpoint and listen for incoming connections.绑定端口和数据

    try
                {
                    listener.Bind(localEndPoint);
                    listener.Listen(
    100);

                   
    while (true)
                    {
                        
    // Set the event to nonsignaled state.设置无信号状态的事件
                        allDone.Reset();
                        
    // Start an asynchronous socket to listen for connections.重新 启动异步连接
                        listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
                        
    // Wait until a connection is made before continuing.等待连接创建后继续
                        allDone.WaitOne();
                    }
                }
                
    catch (Exception e)
                {
                   
    //
                }
            }

            
    public static void AcceptCallback(IAsyncResult ar)
            {
                
    try
                {
                   
    // Signal the main thread to continue.接受回调方法 该方法的此节向主应用程序线程发出信号,
                   
    //让它继续处理并建立与客户端的连接
                    allDone.Set();
                   
    // Get the socket that handles the client request.获取客户端请求句柄
                    Socket listener = (Socket)ar.AsyncState;
                    Socket handler
    = listener.EndAccept(ar);
                   
    // Create the state object.
                    StateObject state =new StateObject();
                    state.workSocket
    = handler;
                    handler.BeginReceive(state.buffer,
    0, StateObject.BufferSize, 0,
                     
    new AsyncCallback(ReadCallback), state);
                }
                
    catch (Exception e)
                {
                   
    //
                }
            }

            
    ///
    <summary>
    /// 与接受回调方法一样,读取回调方法也是一个 AsyncCallback 委托。
            
    /// 该方法将来自客户端套接字的一个或多个字节读入数据缓冲区,然后再次调用 BeginReceive 方法,直到客户端发送的数据完成为止。
            
    /// 从客户端读取整个消息后,在控制台上显示字符串,并关闭处理与客户端的连接的服务器套接字。
            
    ///
    </summary>
    ///
    <param name="ar">IAsyncResult 委托</param>

    public static void ReadCallback(IAsyncResult ar)
            {
                
    try
                {
                    String content
    = String.Empty;
                   
    // Retrieve the state object and the handler socket创建自定义的状态对象 from the asynchronous state object.
                    StateObject state = (StateObject)ar.AsyncState;
                    Socket handler
    = state.workSocket;//处理的句柄
                   
    // Read data from the client socket. 读出

    int bytesRead = handler.EndReceive(ar);
                   
    if (bytesRead >0)
                    {
                        
    //业务代码

    string result = DoSomeThing(...);
                        String len
    = Encoding.UTF8.GetBytes(result).Length.ToString().PadLeft(8, '0');
                        log.writeLine(len);
                        Send(len
    + result, handler);
                    }
                }
                
    catch (Exception e)
                {
                   
    //
                }

            }
            
    private static void Send(String data, Socket handler)
            {
                
    try
                {
                   
    // Convert the string data to byte data using UTF8 encoding.

    byte[] byteData = Encoding.UTF8.GetBytes(data);
                   
    // Begin sending the data to the remote device.
                    handler.BeginSend(byteData, 0, byteData.Length, 0,
                     
    new AsyncCallback(SendCallback), handler);
                }
                
    catch (Exception e)
                {
                   
    //
                }
            }
            
    ///
    <summary>
    /// 发送
            
    ///
    </summary>
    ///
    <param name="ar"></param>

    private static void SendCallback(IAsyncResult ar)
            {
                
    try
                {
                   
    // Retrieve the socket from the state object.
                    Socket handler = (Socket)ar.AsyncState;

                   
    // Complete sending the data to the remote device.向远端发送数据

    int bytesSent = handler.EndSend(ar);
                    StateObject state
    =new StateObject();
                    state.workSocket
    = handler;

                    handler.BeginReceive(state.buffer,
    0, StateObject.BufferSize, 0,new AsyncCallback(ReadCallback), state);
                    handler.Shutdown(SocketShutdown.Both);
                    handler.Close();
                }
                
    catch (Exception e)
                {
    //
                }
            }

            
    public static void StopListening()
            {
                allDone.Close();
                log.close();
            }

            
    ///
    <summary>
    /// 具体处理业务的方法
            
    ///
    </summary>
    ///
    <returns></returns>

    private static string DoSomething(int i)
            {
                
    //具体业务代码,返回需要返回的字符串信息
            }
            
    ///
    <summary>
    /// 写日志方法
            
    ///
    </summary>
    ///
    <param name="strLog">写入内容</param>

    public static void WriteLog(string strLog)
            {
                
    //写入日志代码
            }
        }
    展开全文
  • 简单的OPC与C#通讯,别想复杂了

    千次阅读 2020-05-12 21:50:30
    简单的OPC与C#通讯,别想复杂了 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using ...

    简单的OPC与C#通讯,别想复杂了

     

    复制代码

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using OPCAutomation;
    namespace OPC
    {
        public partial class Form1 : Form
        {
            OPCServer ObjOPCServer;
            OPCGroups ObjOPCGroups;
            OPCGroup ObjOPCGroup;
            string OPCServerName;
            public Form1()
            {
                try
                {
                    InitializeComponent();
                    OPCServerName = "{Here comes your OPC server’s name,(OPC服务器名称)}";
                    ObjOPCServer = new OPCServer();
                    ObjOPCServer.Connect(OPCServerName, "";);
                    ObjOPCGroups = ObjOPCServer.OPCGroups;
                    ObjOPCGroup = ObjOPCGroups.Add("OPCGroup1");
                    ObjOPCGroup.DataChange += new DIOPCGroupEvent_DataChangeEventHandler(ObjOPCGroup_DataChange);
                    ObjOPCGroup.OPCItems.AddItem("{tag name or address,(标签名称或地址名) (like {plc name on server}!%mw0)}", 1);
                    ObjOPCGroup.UpdateRate = 10;
                    ObjOPCGroup.IsActive = true;
                    ObjOPCGroup.IsSubscribed = true;
                }
                catch (Exception e){
                    MessageBox.Show(e.ToString());
                }
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
            }
    
            private void ObjOPCGroup_DataChange(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps)
            {
                for (int i = 1; i <= NumItems; i++)
                {
                    if ((Convert.ToInt32(ClientHandles.GetValue(i)) == 1))
                    {
                        textBox1.Text =  ItemValues.GetValue(i).ToString();
                    }
                }
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
                OPCServerClass GlobalOPCServer = new OPCServerClass();
                Array ServerList = (Array)GlobalOPCServer.GetOPCServers(""); 
                for (int i = 1; i <= ServerList.Length; i++)
                {
                    comboBox1.Items.Add(ServerList.GetValue(i).ToString());
                }
            }
        }
    }

    复制代码

    在工业自动化领域,计算机早已成为必不可少的工具,计算机技术的不断发展,大大加速了工业自动化技术的进步,而各种各样的工业控制应用软件正是具体实现这一进程的最重要的工具。

    往,应用软件开发商要为每一种硬件开发驱动程序,由于硬件的种类繁多,特征各异,软件开发商的负担异常繁重,尤其是如果硬件特征发生了变化,整个应用软件
    相应的驱动程序也要相应地修改,这对软件开发商,对整个工程都是很不利的。而且由于驱动程序的不统一,不同应用程序访问同一硬件设备时常常发生冲突。
    OPC(OLE for Process Control)技术标准正是在这种情况下产生的。OPC基于微软的OLE、COM和DCOM技术,而且它本身
    就是一种特殊的COM,也正因为有微软的参与,以及以已经成熟的技术为基础,它比一般的工业标准制定的效率更高,它从开始制定到第一个可运行的规范开始运
    行,只用了不到一年的时间。
    二、OPC原理及应用

    OPC技术为工业自动化软件面向对象的开发提供了统一的
    标准。它大大减轻了软件开发商的负担,软件开发商不必再为每一硬件单独编写驱动程序,只要硬件的特征符合统一的OPC接口程序标准,或者硬件生产商提供
    OPC服务器,如图一所示,不同的应用软件开发商都可以采用OPC标准设计工控软件,以标准规定的统一接口通过OPC服务器存取现场数据。

    样,当现场设备发生变化或系统中加入新设备时,OPC服务器的提供商需要重新实现服务器接口,以适应硬件的变化,但由于服务器所提供的接口的一致性,工控
    软件不作更改即可继续使用,只是某些情况下可能需要重新组态(如添加新的PLC站点等),这样,软件开发商可以节省大量的时间致力于工控软件的性能方面的
    提高,不必再考虑硬件变化带来的影响,大大减小了软件维护的工作量。这正如OPC规范里所说,OPC将应用软件和硬件设备划清了界限。
    2.1 OPC基本结构

    OPC
    服务器有两类接口:定制接口(Custom Interface) 、自动化接口(Automation Interface),定制接口比较低级,它提
    供更多的功能,效率也比后者高,可以用C++语言调用此类接口,自动化接口主要用于VB、DELPHI等开发工具。按照OPC规范,定制接口是服务商必须
    提供的,而自动化接口则是可选的,不过,OPC基金会(管理OPC标准的国际组织)提供了一个叫做“自动化包装器”的动态连接库,用于在两者间转换。如图
    二所示:
    在OPC的早期规范里主要包括OPC数据存取规范、OPC报警和事件、OPC历史数据存取规范。OPC数据存取规范详细规定了客户程序
    和服务器程序进行数据通信的机制,其它类型的OPC服务器往往是在数据存取服务器的基础上通过增加对象、扩展接口而来的,所以该规范也是其它OPC规范的
    基础。OPC数据存取规范规定的基本对象有三类:OPC Server、OPC Group和OPC Item,OPC Server包含服务器的所有信
    息,也是OPC Group的容器,OPC Group除了包含它自身信息外,还负责管理OPC Item。它们的结构如图三所示。每一个
    OPC Item代表到数据源的一个连接,但它没有提供外部接口,客户端程序无法对OPC Item直接进行操作,应用程序必须依靠OPC Item的容
    器OPC Group来对它进行操作,这在下面的程序中会有具体说明。
    2.2 OPC数据访问方式

    OPC客户程序对
    OPC服务器中数据的存取方式分为同步读写方式和异步读写方式。客户程序可按照一定的周期调用OPC Group对象的IOPCSyncIO接口对服务器
    程序进行数据同步存取操作,此时客户方的调用函数一直运行到所有数据读写完成,然后才能执行其它操作,因此,这种方法适合与读取少量数据,如果数据多的
    话,会使系统处于假死状态,无法进行操作。IOPCSyncIO2是从3.0版才出现的,是对IOPCSyncIO的增强。IOPCAsyncIO2和
    IOPCAsyncIO3是异步方式中使用的接口,异步访问时,当客户端对服务器提出访问要求后,立即返回到OPC应用程序执行其它操作,无须等待,当
    OPC服务器完成数据读取后通知OPC应用程序,应用程序从而得到数据。其中前者是在2.0版本中新定义的,具有较高的通信性能;后者则是在3.0版本中
    才刚刚出现,同IOPCSyncIO2类似,IOPCAsyncIO3是对IOPCAsyncIO2的增强。在异步方式下,服务器程序收到读请求后,调用
    客户程序方的IOPCDataCallback接口,将数据发送给客户程序。异步方式中允许服务器将读写操作进行排队,使客户方的调用函数可立刻返回,当
    服务器读写操作完成后再通知客户程序。显然,异步通报方式的通信效率更高,这种方式也是本文所要讨论的方式,但有多个客户程序与服务器相连时,同步读写方
    式更具时效性。对于每个组对象,客户程序可根据需要采用其中一种数据存取方式,而不能两者都使用。
    异步读取还有一种特殊的方式,叫做订阅方式(Subscribe)这种情况下,应用程序不需要发出读请求,OPC服务器在定期更新数据的时候,如果发现数据有一定变化,则自动向应用程序发出通知和传输变化的数据。
    2.3 编写OPC客户端应用程序

    VB
    简单实用,是比较理想的OPC应用程序快速开发工具,若要用VB开发OPC应用程序,必须要使用OPC自动化包装器,这在前文已经提过,这种包装器一般由
    OPC服务器的供应商以DLL形式提供,下面就以SIEMENS提供的sopcdaauto.dll为例,介绍如何开发OPC应用程序,这也正是笔者在最
    近的工程中实际应用到的,这是基于DA2.0的版本。
    首先,新建VB工程后,作图四所示的引用:
    2.3.1 建立OPC对象

    首先申明OPC对象:
    Option Base 1
    Dim WithEvents ServerObj As OPCServer ''OPC Server对象,连接OPC服务器
    Dim GroupsObj As OPCGroups ''OPC Groups对象,添加OPC组
    Dim WithEvents GroupObj As OPCGroup ''OPC Group对象
    Dim ItemsObj As OPCItems ''OPC Item集合
    Dim ServerHandles() As Long ''服务器端OPC Item的句柄
    Dim ClientHandles() as Long ''客户端OPC Item的句柄
    Dim ItemId(2) As String
    Dim Errors() As Long
    接下来,生成各个对象:
    If ServerObj Is Nothing Then Set ServerObj = New OPCServer
    ''连接OPC服务器
    If ServerObj.ServerState = OPCDisconnected Then
    ServerObj.Connect ("OPC.SimaticNET") ''假设OPC服务器运行在本机
    End If
    If GroupsObj Is Nothing Then Set GroupsObj = ServerObj.OPCGroups
    If GroupObj Is Nothing Then Set GroupObj = GroupsObj.Add
    If ItemsObj Is Nothing Then Set ItemsObj = GroupObj.OPCItems
    GroupObj.IsActive = True ''设置组为活动状态
    ''假设有两个数据源,一个是8位开关量输入,一个是8位开关量输出
    ItemId(1) = "S7:[S7 connection_1]IB0"
    ItemId(2) = "S7:[S7 connection_1]QB0"
    ClientHandles(1) = 1
    ClientHandles(2) = 2
    ''添加组项目,ServerHandles数组的值为各个OPC Item的服务器句柄,
    '' ClientHandles数组的值为各个OPC Item的客户端句柄,由应用程序设定
    Call ItemsObj.AddItems(2, ItemId, ClientHandles, ServerHandles, Errors)

    2.3.2异步数据读取

    '' OPC Item的服务器句柄,添加OPC Item时由服务器分配
    Dim TempServerHandles(1) As Long
    ''事务标志符,由客户端产生,它包含的信息提供给OnReadComplete事件
    Dim TransactionID As Long
    ''取消标志符,服务器端产生,用于操作需要被取消的时候
    Dim CancelID As Long
    ''包含读取每个OPC Item时返回的信息
    Dim ErrorNr() As Long
    TempServerHandles(1) = ServerHandles(1) ''对应第一个OPC Item
    GroupObj.AsyncRead 1, TempServerHandles, ErrorNr, TransactionID, CancelID
    第一个参数是要读的OPC Item的个数,这里只含有一个OPC Item。读取的结果由OPC服务器通过IconnectionPointContainer接口配合IOPCDataCallback接口反调用应用程序的事务处理程序:

    Private Sub GroupObj_AsyncReadComplete(ByVal TransactionID As Long, ByVal NumItems As Long, ClientHandles() As Long, ItemValues() As Variant, Qualities() As Long, TimeStamps() As Date, Errors() As Long)
    这里的参数ClientHandles和AddItems方法中的ClientHandles是对应的,用于判断哪一个OPC Item在被读取。其它参数的说明如下:
    TransactionID:客户端自由使用,应用程序开发商自定义;
    NumItems:表示读取的OPC Item的个数;
    ItemValues():各个OPC Item连接的数据源的值,类型为Variant;
    Qualities():OPC Item的品质值;
    TimeStamps():时间戳;
    Errors():记录服务器返回的信息。
    2.3.3 异步数据写入

    Dim TempServerHandles (1) As Long
    Dim VValue(1) As Variant
    Dim ErrorNr() As Long
    Dim TransactionID As Long
    Dim CancelID As Long
    TempServerHandles(1)=ServerHandles(2)
    VValue(1)=1''假设要将“1”写入ClientHandle为2的OPC Item
    GroupObj.AsyncWrite 1,TempServerHandles,VValue, ErrorNr, TransactionID, CancelID

    同样,AsyncWrite也对应一个事务处理程序:
    Private Sub groupObj_AsyncWriteComplete(ByVal TransactionID As Long, ByVal NumItems As Long, ClientHandles() As Long, Errors() As Long)
    它的参数的含义和AsuncReadComplete中的含义是类似的,这里一般需要处理的是写数据之后的返回状态,这里不再赘述。
    2.3.4断开与服务器的连接

    ItemsObj.Remove ItemsObj.Count,ServerHandles, Errors ''清除OPC Item
    Set ItemsObj = Nothing ''释放资源,下同
    If Not GroupObj Is Nothing Then
    GroupsObj.Remove GroupObj.ServerHandle ''删除组
    End If
    If Not GroupsObj Is Nothing Then
    Set GroupsObj = Nothing
    End If
    If Not ServerObj Is Nothing Then
    If ServerObj.ServerState <> OPCDisconnected Then
    ServerObj.Disconnect ''断开与服务器的连接
    End If
    Set ServerObj = Nothing
    End If

    展开全文
  • C#通讯框架改写

    2019-05-22 15:34:00
    现有项目是利用C#的socket与PLC进行实时通讯,PLC有两种通讯模式——常规采集&高频采集。 其中常规采集大概在10ms左右发送一次数据,高频采集大概在2ms左右发送一次数据。 现有代码框架:在与PLC进行连接时,...

    现有项目是利用C#的socket与PLC进行实时通讯,PLC有两种通讯模式——常规采集&高频采集。

    其中常规采集大概在10ms左右发送一次数据,高频采集大概在2ms左右发送一次数据。

    现有代码框架:在与PLC进行连接时,通过建立委托并创建线程的方式,来循环读取数据

    //创建委托
    public delegate void PLC_HD_Receive(byte[] recv_data);
    
    public PLC_HD_Receive PLC_Recv_Delegate_HD;
    
    //给委托绑定方法
    PLC_Recv_Delegate_HD = new PLC_HD_Receive(PLC_Receive_Callback_HD);
    
    //创建线程 PLC_Thread_HD
    = new Thread(new ThreadStart(PLC_ReadThread_HD));
    PLC_Thread_HD.IsBackground
    = true; PLC_Thread_HD.Start();
    //在线程内调用委托
    this.BeginInvoke(this.PLC_Recv_Delegate_HD, new Object[] { recv_buffer_hd });

     只要连接PLC成功后,会一直在后台读取PLC发送来的数据,并解析数据

    现有问题:实时性和数据完整性不够,有些操作会导致socket断掉连接。

    计划:改写现有代码框架,加深对通讯的理解,和对实时数据流的处理。                  2019-5-22

    **************************************************************************************************************************************************

    思路:原有框架读取数据使用的是同步通信,出错时反馈TimeOut错误,先准备改成异步通信

     1                     SocketError socket_error;
     2 
     3                     while (total_length < recv_buffer_len_hd)
     4                     {
     5                        //同步接收数据
     6                         ret_length = m_socket_hd.Receive(recv_buffer_hd, total_length, data_left, SocketFlags.None, out socket_error);
     7                         if (socket_error == SocketError.TimedOut || socket_error == SocketError.Shutdown || socket_error == SocketError.ConnectionAborted || ret_length == 0)
     8                         {
     9                             // 网络不正常,委托退出接收线程
    10                             thread_id = 1;
    11                             this.Invoke(this.PLC_ExitThread_Delegate_HD, new Object[] { thread_id });
    12                             return;
    13                         }
    14                         total_length += ret_length;
    15                         data_left -= ret_length;
    16                     }  

    控制台异步输出数据

    首先搭建一个简单的winform窗口demo,实现控制台异步输出数据

    此处参考链接:https://blog.csdn.net/smartsmile2012/article/details/71172450 异步接收

    但网上搜到的大部分都是服务器接收,项目上的应用是客户端接收,做了一点修改

    搭建的过程中遇到了winform无法直接控制台输出,需要引用AllocConsole()和FreeConsole()

    此处参考链接:https://blog.csdn.net/b510030/article/details/52621312 WinForm添加Console

    在反复点击按钮的过程中发现,AllocConsole()最好在窗口构造函数中使用,否则多次调用AllocConsole()会导致Console.Readkey()报错

      1     public partial class Form1 : Form
      2     {
      3         //winform调用console窗口
      4         [DllImport("Kernel32.dll")]
      5         public static extern Boolean AllocConsole();
      6 
      7         [DllImport("Kernel32.dll")]
      8         public static extern Boolean FreeConsole();
      9         //socket模块
     10         IPAddress ip;
     11         Socket m_sokcet;
     12         IPEndPoint local_endpoint;
     13         byte[] buffer;
    19 public Form1() 20 { 21 buffer = new byte[8]; 22 InitializeComponent();
    25
    AllocConsole(); 26 } 27 28 private void button1_Click(object sender, EventArgs e) 29 { 30 ip = IPAddress.Parse("127.0.0.1"); 31 local_endpoint = new IPEndPoint(ip, 60000); 32 m_sokcet = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 33 m_sokcet.Connect(local_endpoint); 34 m_sokcet.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), m_sokcet); 35 Console.ReadKey(); 36 } 71 void ReceiveCallback(IAsyncResult result) 72 { 73 Socket m_sokcet = (Socket)result.AsyncState; 74 m_sokcet.EndReceive(result); 75 result.AsyncWaitHandle.Close();
    91
    Console.WriteLine("收到消息:{0}", Encoding.ASCII.GetString(buffer));
    94 //清空数据,重新开始异步接收 95 buffer = new byte[buffer.Length]; 96 m_sokcet.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), m_sokcet); 97 } 98 }

    服务端利用socketTool调试工具,发送数据后看控制台窗口的刷新情况,测试结果如下:

    测试结果OK

    **************************************************************************************************************************************************

     流式数据框架构思

     此处参考链接:https://www.csdn.net/article/2014-06-12/2820196-Storm 实时计算

    二.  实时计算的相关技术
    主要分为三个阶段(大多是日志流):
    数据的产生与收集阶段、传输与分析处理阶段、存储对对外提供服务阶段

     

    链接内说的是大数据和流式处理框架Storm,项目上还远远达不到大数据级别,所以只是参考一下思路。

    数据的产生:PLC,数据的接受:Socket,数据的存储:队列,数据的分析处理:解析数据,数据的对外服务:刷新UI

    框架思路有了,接下来就是具体实现

    **************************************************************************************************************************************************

    生产者-消费者模式和队列

    此处参考链接:https://www.cnblogs.com/samgk/p/4772806.html 队列

     1         //队列模块
     2         readonly static object _locker = new object();
     3         Queue<byte[]> _tasks = new Queue<byte[]>();
     4         EventWaitHandle _wh = new AutoResetEvent(false);
     5         Thread _worker;
     6 
     7         //窗口初始化时开始消费者线程
     8         public Form1()
     9         {
    10             buffer = new byte[8];
    11             InitializeComponent();
    12             _worker = new Thread(Work);
    13             _worker.Start();
    14             AllocConsole();
    15         }
    16 
    17        //加了锁和信号量
    18         void Work()
    19         {
    20             while (true)
    21             {
    22                 byte[] work = null;
    23                 lock (_locker)
    24                 {
    25                     if (_tasks.Count > 0)
    26                     {
    27                         work = _tasks.Dequeue(); // 有任务时,出列任务
    28 
    29                         if (work == null)  // 退出机制:当遇见一个null任务时,代表任务结束
    30                             return;
    31                     }
    32                 }
    33 
    34                 if (work != null)
    35                     SaveData(work);  // 任务不为null时,处理并保存数据
    36                 else
    37                     _wh.WaitOne();   // 没有任务了,等待信号
    38             }
    39         }
    40  
    41         //在异步接收的方法中把控制台输出修改为加入队列
    42         void EnqueueTask(byte[] task)
    43         {
    44             lock (_locker)
    45                 _tasks.Enqueue(task);  // 向队列中插入任务 
    46 
    47             _wh.Set();  // 给工作线程发信号
    48         }
    1                 //TODO 将收到的数据放入队列
    2                 EnqueueTask(buffer);
    3                 Thread.Sleep(10);
    4                 //Console.WriteLine("收到消息:{0}", Encoding.ASCII.GetString(buffer));
    5                 //
    1          void SaveData(byte[] buffer)
    2          {
    3             //从队列中取出数据           
    4             Console.WriteLine("收到消息:{0}", Encoding.ASCII.GetString(buffer));
    5          }

     

    这样就把数据先存入队列,再取出数据,通过控制台输出数据,实现了生产者-消费者模式和队列存储数据            2019-5-23

    **************************************************************************************************************************************************

     解析数据&刷新UI

     项目真正的业务需求是解析数据和刷新UI,所以我们需要把SaveData方法改造一下

     PLC会源源不断的输出数据,我们需要在接收到数据后对数据进行处理和刷新UI,不可能对每一个数据都进行处理

     而且项目不是大数据级别的,不使用数据库存放数据,纯粹的实时处理,我们需要定义一下处理数据的采集时间和UI的刷新时间

     原有框架的常规采集是16ms,高频采集是2ms,所以在测试阶段定义10ms采集一次,UI刷新500ms一次

    逻辑是在最后解析&刷新时间记录时间戳,和SaveData当前执行时间戳比较,大于10ms则解析,大于500ms则刷新

     1         int count_UI = 0;
     2         int count_Data = 0;
     3         float time_UI = 0F;
     4         float time_Data = 0F;
     5         float time_over_UI = 0F;
     6         float time_over_Data = 0F;
     7         /// <summary>处理保存</summary>
     8         bool SaveData(byte[] buffer)
     9         {
    11             //从队列中取出数据,解析并刷新UI
    13             //解析数据
    14             time_Data = Environment.TickCount - time_over_Data;
    15             time_UI = Environment.TickCount - time_over_UI;
    16             //if (time_Data > 10)//解析数据——10ms一次
    17             //{
    18             //解析数据函数
    19             count_Data++;
    23 Console.WriteLine("解析成功:{0},耗时{1}ms,序号:{2}", Encoding.ASCII.GetString(buffer), time_Data.ToString(), count_Data.ToString()); 24 time_over_Data = Environment.TickCount; 25 //} 26 27 //刷新UI——500ms一次 28 if (time_UI > 500) 29 { 30 //刷新UI函数 31 count_UI++;
    33 Console.WriteLine("刷新UI:{0},耗时{1}ms,序号:{2}", Encoding.ASCII.GetString(buffer), time_UI.ToString(), count_UI.ToString()); 34 time_over_UI = Environment.TickCount; 35 } 36 Thread.Sleep(200);// 模拟数据保存
    37 return true; 38 }

     

    使用SockeTool发送数据100次,会看到数据被过滤到了一部分

     

    测试到这里我对时间片有一点疑惑,查阅了一些资料和做了一些实际测试

     

     此处参考链接:https://zhidao.baidu.com/question/1051646628145878899.html 时间片

     

    socket处理数据流的速度非常快,如果不加10ms的过滤则每一条数据都会显示在控制台页面,如果加了10ms的过滤则只显示一部分,至于为什么大部分情况下是16ms,和线程调度有关

     

    我们现在把解析数据的函数和UI调用的函数放在指定的地方就可以实测了。

    **************************************************************************************************************************************************

    socket粘包&服务端断开连接异常&异步接收检测socket通断

    1、粘包——在测试过程中发现,如果buffer的大小与每次发送的数据不一致,会发生粘包现象。

     

    项目上PLC发送的数据固定为4096字节,所以和服务端保持一致即可。

     

    2、服务端连接断开——测试的另一个问题是如果服务端断开连接,客户端无法有效监测,回调函数会一直执行。

    3、监测通断——网上查了很多资料,利用select方法和poll方法的,试了一下没有效果,最后采用flag的方式成功在连接异常后终止回调函数

     

    EndReceive方法会反馈当前获取到的字节数,否则没有数据则为0,如果重复接收20次,每次延时100ms都没有为0,则判断为连接已断。

    项目是和PLC连接,和其他互联网应用有一定的差异。

     1         int flag_connect = 0;
     2         void ReceiveCallback(IAsyncResult result)
     3         {
    11             Socket m_sokcet = (Socket)result.AsyncState;
    12             int a = m_sokcet.EndReceive(result);
    13             result.AsyncWaitHandle.Close();
    14             if (a == 0)
    15             {
    16                 if (flag_connect == 20)
    17                 {
    18                     flag_connect = 0;
    19                     return;
    20                 }
    21                 flag_connect++;
    22                 Thread.Sleep(100);
    23             }
    24             else
    25             {
    27                 EnqueueTask(buffer);32             }
    33             //清空数据,重新开始异步接收
    34             buffer = new byte[buffer.Length];
    35             m_sokcet.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), m_sokcet);
    36         }

     

    **************************************************************************************************************************************************

    解析数据连接PLC实测

     

    上面的测试都是笔记本电脑上利用socketTool测试的,现在开始连接PLC做真实的数据解析测试

    1、测试遇到的问题是,如何断开解析数据线程和异步接收回调函数

    一开始直接使用的是Abort方法,但是效果不好,没有办法再次连接

    查询相关资料后,使用flag的方式来退出线程,使用信号量的方式来结束回调函数

    另外考虑到PLC是无限的数据流,对队列的最大数量做了一个限制,如果超过1000个则停止接收

    此处参考链接:https://blog.csdn.net/pc0de/article/details/52841458 Abort异常

    此处参考链接:https://blog.csdn.net/shizhibuyi1234/article/details/78202647 结束线程

    还有两个线程相关的,Mark一下日后学习

    https://www.cnblogs.com/doforfuture/p/6293926.html 线程池相关
    https://www.cnblogs.com/wjcnet/p/6955756.html  Task

    2、连接断开过程中的,队列内的数据处理。经过测试,最后还是采用信号量的方式

    在队列达到最大数量1000时,异步接收回调函数等待。

    在队列为空时,解析数据线程给异步接收回调函数发信号。

    另外,实测Queue为空时,调用Dequeue会报错队列为空。

    完整代码: 

      1     public partial class Form1 : Form
      2     {
      3         //winform调用console窗口
      4         [DllImport("Kernel32.dll")]
      5         public static extern Boolean AllocConsole();
      6 
      7         [DllImport("Kernel32.dll")]
      8         public static extern Boolean FreeConsole();
      9         //socket模块
     10         IPAddress ip;
     11         Socket m_sokcet;
     12         IPEndPoint local_endpoint;
     13         byte[] buffer;
     14         //队列模块
     15         readonly static object _locker = new object();
     16         Queue<byte[]> _tasks = new Queue<byte[]>();
     17         EventWaitHandle _wh;
     18         EventWaitHandle _recieve_call;
     19         Thread _worker;
     20         public Form1()
     21         {
     22             buffer = new byte[256];
     23             InitializeComponent();
     24             AllocConsole();
     25         }
     26 
     27         private void button1_Click(object sender, EventArgs e)
     28         {
     29             connect_status = true;
     30             if (_wh == null)//队列信号量
     31                 _wh = new AutoResetEvent(false);
     32             if (_recieve_call == null)//队列满或空信号量
     33                 _recieve_call = new AutoResetEvent(false);
     34             _worker = new Thread(Work);
     35             _worker.Start();
     36             if (m_sokcet == null)
     37             {
     38                 ip = IPAddress.Parse("169.254.11.22");//TODO IP修改
     39                 local_endpoint = new IPEndPoint(ip, 2001);//TODO 端口修改
     40                 m_sokcet = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
     41                 m_sokcet.Connect(local_endpoint);
     42             }
     43             m_sokcet.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), m_sokcet);
     44             //Console.ReadKey();
     45         }
     46 
     47         bool connect_status = false;
     48         int flag_connect = 0;
     49         void ReceiveCallback(IAsyncResult result)
     50         {
     51             if (_tasks.Count > 10000)
     52             {
     53                 //TODO 区分当前连接状态,执行wait还是return
     54                 _recieve_call.WaitOne();
     55                 //return;
     56             }
     57 
     58             Socket m_sokcet = (Socket)result.AsyncState;
     59             int a = m_sokcet.EndReceive(result);
     60             result.AsyncWaitHandle.Close();
     61             if (a == 0)//判断是否与服务端断开连接
     62             {
     63                 if (flag_connect == 20)
     64                 {
     65                     flag_connect = 0;
     66                     return;
     67                 }
     68                 flag_connect++;
     69                 Thread.Sleep(100);
     70             }
     71             else
     72             {
     73                 //TODO 将收到的数据放入队列
     74                 EnqueueTask(buffer);
     75                 //Thread.Sleep(1);
     76                 //Delay(1);
     77                 //Console.WriteLine("收到消息:{0}", Encoding.ASCII.GetString(buffer));
     78                 //
     79             }
     80             //清空数据,重新开始异步接收
     81             buffer = new byte[buffer.Length];
     82             m_sokcet.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), m_sokcet);
     83         }
     84 
     85         void Work()
     86         {
     87             bool result;
     88             while (connect_status)
     89             {
     90                 byte[] work = null;
     91                 lock (_locker)
     92                 {
     93                     if (_tasks.Count > 0)
     94                     {
     95                         work = _tasks.Dequeue(); // 有任务时,出列任务
     96                     }
     97                     else
     98                     {
     99                         _recieve_call.Set();
    100                         //return;
    101                     }
    102                 }
    103 
    104                 if (work != null)
    105                     result = SaveData(work);  // 任务不为null时,处理并保存数据
    106                 else
    107                     _wh.WaitOne();   // 没有任务了,等待信号
    108             }
    109         }
    110 
    111         /// <summary>插入任务</summary>
    112         void EnqueueTask(byte[] task)
    113         {
    114             lock (_locker)
    115                 _tasks.Enqueue(task);  // 向队列中插入任务 
    116 
    117             _wh.Set();  // 给工作线程发信号
    118         }
    119 
    120         int count_UI = 0;
    121         int count_Data = 0;
    122         float time_UI = 0F;
    123         float time_Data = 0F;
    124         float time_over_UI = 0F;
    125         float time_over_Data = 0F;
    126         /// <summary>处理保存</summary>
    127         bool SaveData(byte[] buffer)
    128         {
    129 
    130             //TODO 从队列中取出数据,解析并刷新UI
    131 
    132             //解析数据——全部解析并保存
    133             time_Data = Environment.TickCount - time_over_Data;
    134             time_UI = Environment.TickCount - time_over_UI;
    135             //if (time_Data > 10)
    136             //{
    137             //解析数据函数
    138             count_Data++;
    139             bool result = PLC_Receive_Callback_HD(buffer);
    140             //Console.WriteLine(count_Data.ToString() + "," + _tasks.Count.ToString() + "," + result.ToString());
    141             //Thread.Sleep(1);
    142             Console.WriteLine("解析成功:{0},耗时{1}ms,序号:{2}", Encoding.ASCII.GetString(buffer), time_Data.ToString(), count_Data.ToString());
    143             time_over_Data = Environment.TickCount;
    144             //}
    145 
    146             //刷新UI——500ms刷新一次
    147             if (time_UI > 500)
    148             {
    149                 //刷新UI函数
    150                 count_UI++;
    151                 //Console.WriteLine(count_UI.ToString() + "," + _tasks.Count.ToString() + "刷新UI成功");
    152                 Console.WriteLine("刷新UI:{0},耗时{1}ms,序号:{2}", Encoding.ASCII.GetString(buffer), time_UI.ToString(), count_UI.ToString());
    153                 time_over_UI = Environment.TickCount;
    154             }
    155             return true;
    156             //Thread.Sleep(200);  // 模拟数据保存
    157         }
    158 
    159         private void button2_Click(object sender, EventArgs e)
    160         {
    161             connect_status = false;
    162             if (_worker != null && _worker.IsAlive)
    163             {
    164                 _wh.Set();
    165                 //_worker.Join();
    166             }
    167         }

     最后加入了解析数据的函数,对4096个字节解析,但是把刷新UI全部屏蔽

    实测PLC_Receive_Callback_HD内900多行代码解析数据很快

    原打算采用异步调用方式调用解析数据函数,现在看来不需要,因为不涉及数据存储

     

     

    通讯框架基本改写完成,剩下的就是把刷新UI的函数加上去

    **************************************************************************************************************************************************

    总结:

     

    参考了网上的很多资料,实现了一个简单的异步通讯和生产者-消费者模式加队列存储,实际测试效果自己还是比较满意的

    果然用轮子不如造轮子,重复造轮子是提升技术的最好方法。                                                                                        2019-5-24

    转载于:https://www.cnblogs.com/frank-hang/p/10906227.html

    展开全文
  • C#通讯调试工具v3.0测试版发布

    千次阅读 热门讨论 2013-08-16 22:57:19
    这也是促使我开发C#通讯调试工具v3.0原因之一,因为3.0版本不光对2.0的各项设计都进行了优化改进,还加入了TCP,UDP的调试功能,故名字也改为了“通讯”调试工具。 我的腾讯微博 计划在下一版本中添加2个关键功能: 1...
  • C#通讯编程

    千次阅读 2014-02-27 13:15:23
    作者:sjm2003 转自:http://bbs.csdn.net/topics/240024868 Socket通讯: C# code? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 3
  • 三菱PLC和C#通讯.zip

    2019-09-28 09:45:38
    本例子是现在工业自动化采用得很多的方法,简单实用,里面的源代码已经测试过了,可以实用。本人已经亲自测试过,可以用
  • 1.C#语言开发,采用NET3.5框架,模块化设计,二次开发使用方便。 2.工程结构类似OPC通讯方式,采用Tag的方式,通过标签名就可以读写寄存器。 3.采用XML配置式标签的方式实时读写PLC内部寄存器,可读写寄存器包括0,1...
  • 在JAVA与C#通讯时, 由于开发语言的差异,发送端与接收端需要做转换才可以得到正确的值。 Java端的 int to byte public static byte[] ConvertIntToByteArray(int i)  {  byte[] arry = new byte[4]; ...
  • C#通讯学习

    2012-11-21 17:45:23
    想学习一下C#通讯的东西,谁能帮忙发点通讯相关的资料
  • Java中使用Socket和C#通讯的解决 2011年05月18日  由于JAVA使用的byte是有符号类型,而C#(包括C++)中的byte是无符号的,  因此,在收发byte[]时都要进行转换处理,发表解决方案如下:  public class Test {...
  • c# public static byte[] StructToBytes(object obj)//传入结构体 { int len = Marshal.SizeOf(obj); byte[] bytes = new byte[len]; IntPtr structPtr = Marshal.AllocHGlobal(len); Marshal.StructureToPtr...
  • [连载]《C#通讯(串口和网络)框架的设计与实现》- 0.前言   目 录 第一章 通讯框架介绍... 2 1.1 通讯的本质... 2 1.2 框架简介... 3 1.3 解决现实问题... 4 1.4 应用场景... 5 1.5 框架应用特点.....
  • 1.C#语言开发,采用NET4.0框架,模块化设计,二次开发使用方便。 2.工程结构类似OPC通讯方式,采用Tag的方式,通过标签名就可以读写寄存器。 3.采用XML配置式标签的方式实时读写PLC内部寄存器,可读写寄存器包括I、Q...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,310
精华内容 2,124
关键字:

c#通讯

c# 订阅