C# OPC_DA客户端
安装 MatrikonOPC软件,用来模拟OPC_DA服务器。
参考这篇博文:OPCServer:使用Matrikon OPC Server Simulation - ioufev - 博客园 (cnblogs.com)
从网上下载OPCDAAuto.dll
参考的这篇知乎文章:OPC DA C# 客户端 - 知乎 (zhihu.com)
注册好OPCDAAuto.dll之后,在项目中引用OPCDAAuto.dll,需要注意的是OPCDAAuto.dll不能复制到项目的文件夹中,需要按照知乎文章重新注册一遍。
由于网上的大部分是使用订阅模式,当节点配置好之后等待数据发生变化之后服务器将数据推送过来。与我项目的要求不符,于是自己整理了一些代码。
测试:
连接上之后会将服务器所有节点都列出在左边的listbox中。选中之后添加到右侧的listbox中。点击读取就能获取选中节点的值。
OPC_client代码:

using System; using System.Collections.Generic; using OPC_DATest; using OPCAutomation; namespace OPC_cli { class OPC_client { public OPCServer Server;//服务器对象 public OPCGroups Groups;//组列表 public OPCGroup Group;//组对象 public OPCItems Items;//节点列表 //public OPCItem Item;//节点对象 public bool ConnectedState { get; private set; } public delegate string ServerShutDown(string Reason); /// <summary> /// 服务器断开连接事件 /// </summary> public event ServerShutDown OnServerShutDown; /// <summary> /// 连接 /// </summary> /// <param name="ServerName"></param> /// <param name="ServerIP"></param> /// <returns></returns> public bool Connect(string ServerName,string ServerIP,out string Message) { try { if (Server == null) Server = new OPCServer(); Server.Connect(ServerName, ServerIP); if (Server.ServerState == (int)OPCServerState.OPCRunning) { Server.ServerShutDown += Server_ServerShutDown; Message = "连接成功"; ConnectedState = true; IniServer(); return true; } else { Message = "连接失败。状态码:" + Server.ServerState.ToString(); ConnectedState = false; return false; } } catch (Exception ex) { Message = ex.Message; return false; } } /// <summary> /// 服务器异常断开连接 /// </summary> /// <param name="Reason"></param> private void Server_ServerShutDown(string Reason) { ConnectedState = false; OnServerShutDown?.Invoke(Reason); } /// <summary> /// 断开连接 /// </summary> public void DisConnect() { Server?.Disconnect(); } /// <summary> /// 添加一个测试组,之后读取都在这个测试组中添加或者删除item /// </summary> private void IniServer() { Groups = Server.OPCGroups; Group = Server.OPCGroups.Add("测试1"); Items = Group.OPCItems; } /// <summary> /// 获取OPCDA服务器中所有的节点<br/> /// 有一些节点服务器自带有Write的,不能读取。 /// </summary> /// <returns></returns> public List<string> GetBrowser() { try { List<string> result = new List<string>(); OPCBrowser browser = Server.CreateBrowser(); browser.ShowBranches(); browser.ShowLeafs(true); foreach (var item in browser) { result.Add(item.ToString()); } return result; } catch (Exception) { return null; } } /// <summary> /// 读取节点数据 /// </summary> /// <param name="tagAddress">需要读取的节点</param> /// <returns></returns> public List<read_result> Read(List<string> tagAddress) { List<read_result> results = new List<read_result>(); try { OPCItem[] items = new OPCItem[tagAddress.Count]; int[] temp = new int[items.Length + 1]; temp[0] = 0;//这个第一个数据不知道为什么一定不能为空,好像是索引是从1开始的 for (int i = 0; i < items.Length; i++) { items[i] = Items.AddItem(tagAddress[i], i); items[i].Read(1, out object ItemValues, out object Qualities, out object TimeStamps); read_result read_ = new read_result() { name = tagAddress[i], value = ItemValues.ToString(), quality = Qualities.ToString(), timestamp = TimeStamps.ToString() }; results.Add(read_); temp[i + 1] = items[i].ServerHandle; } Array serverHandles = (Array)temp; Array errors; Items.Remove(1,serverHandles,out errors); return results; } catch (Exception) { return null; } } /// <summary> /// 数据变化之后推送事件 /// </summary> /// <param name="TransactionID"></param> /// <param name="NumItems"></param> /// <param name="ClientHandles"></param> /// <param name="ItemValues"></param> /// <param name="Qualities"></param> /// <param name="TimeStamps"></param> private void Group_DataChange(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps) { Console.ForegroundColor = ConsoleColor.Green; for (int i = 1; i <= NumItems; i++) { int tmpClientHandle = Convert.ToInt32(ClientHandles.GetValue(i)); string tmpValue = ItemValues.GetValue(i).ToString(); string tmpTime = ((DateTime)(TimeStamps.GetValue(i))).ToString(); Console.WriteLine("Time:" + tmpTime); Console.WriteLine("ClientHandle : " + tmpClientHandle.ToString()); Console.WriteLine("tag value " + tmpValue); } } } }

namespace OPC_DATest { public class read_result { public string name { get; set; } public string value { get; set; } public string quality { get; set; } public string timestamp { get; set; } } }
界面代码:

using OPC_cli; using System; using System.Collections.Generic; using System.ComponentModel; using System.Windows.Forms; namespace OPC_DATest { public partial class MainForm : Form { OPC_client Client; public MainForm() { InitializeComponent(); } /// <summary> /// 连接按钮 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Btn_Connect_Click(object sender, EventArgs e) { switch (Btn_Connect.Text) { case "连接": Client = new OPC_client(); bool success = Client.Connect(Txt_serverName.Text, Txt_ip.Text, out string message); if (success) { MessageBox.Show("连接成功"); IniTags(); Btn_Connect.Text = "断开连接"; } else { MessageBox.Show("连接失败:" + message); } break; case "断开连接": Client.DisConnect(); Btn_Connect.Text = "连接"; break; } } /// <summary> /// 初始化节点 /// </summary> private void IniTags() { lis_Tags.Items.Clear(); List<string> tags = Client.GetBrowser(); foreach (var item in tags) { lis_Tags.Items.Add(item.ToString()); } } /// <summary> /// 窗体关闭 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { Client?.DisConnect(); } /// <summary> /// 窗体加载 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MainForm_Load(object sender, EventArgs e) { Client = new OPC_client();//初始化话OPCClient对象 dgv_ReadResult.DataSource = new BindingList<read_result>();//绑定表格数据源 dgv_ReadResult.Columns[0].HeaderCell.Value = "名称";//初始化表列名称 dgv_ReadResult.Columns[1].HeaderCell.Value = "值"; dgv_ReadResult.Columns[2].HeaderCell.Value = "质量"; dgv_ReadResult.Columns[3].HeaderCell.Value = "时间戳"; } /// <summary> /// 读取按钮 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Btn_Read_Click(object sender, EventArgs e) { if (!Client.ConnectedState) { return; } if (lis_Tags_Read.Items.Count == 0) { return; } List<string> list = new List<string>(); foreach (var item in lis_Tags_Read.Items) { list.Add(item.ToString()); } try { List<read_result> re = Client.Read(list); if (re != null && re.Count > 0) { dgv_ReadResult.DataSource = re; } } catch (Exception ex) { MessageBox.Show(ex.Message); } } /// <summary> /// 选中节点添加到待读取列表 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Btn_Add_Click(object sender, EventArgs e) { foreach (var item in lis_Tags.SelectedItems) { if (!lis_Tags_Read.Items.Contains(item.ToString())) { lis_Tags_Read.Items.Add(item.ToString()); } } } /// <summary> /// 移除待读取节点 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Btn_Remove_Click(object sender, EventArgs e) { try { object[] selected = new object[lis_Tags_Read.Items.Count]; lis_Tags_Read.SelectedItems.CopyTo(selected, 0); foreach (object a in selected)//开始遍历刚刚创立的 数组 { lis_Tags_Read.Items.Remove(a); } } catch (Exception) { } } /// <summary> /// 移除所有待读取节点 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Btn_RemoveAll_Click(object sender, EventArgs e) { lis_Tags_Read.Items.Clear(); } /// <summary> /// 自动读取时间间隔 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void txt_AutoReadTimes_KeyPress(object sender, KeyPressEventArgs e) { if (((int)e.KeyChar < 48 || (int)e.KeyChar > 57) && (int)e.KeyChar != 8 && (int)e.KeyChar != 46) e.Handled = true; } /// <summary> /// 自动读取 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ckb_AutoRead_CheckedChanged(object sender, EventArgs e) { if (ckb_AutoRead.Checked) { int times = int.Parse(txt_AutoReadTimes.Text); if (times < 1000) { times = 1000; timer1.Interval = times; } else { timer1.Interval = times; } Btn_Read.Enabled = false; timer1.Enabled = true; } else { timer1.Enabled = false; Btn_Read.Enabled = true; } } /// <summary> /// 自动读取定时器 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void timer1_Tick(object sender, EventArgs e) { timer1.Enabled = false; Btn_Read_Click(null, null); timer1.Enabled = true; } } }

namespace OPC_DATest { partial class MainForm { /// <summary> /// Required designer variable. /// </summary> private System.ComponentModel.IContainer components = null; /// <summary> /// Clean up any resources being used. /// </summary> /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.Txt_ip = new System.Windows.Forms.TextBox(); this.label1 = new System.Windows.Forms.Label(); this.label2 = new System.Windows.Forms.Label(); this.Txt_serverName = new System.Windows.Forms.TextBox(); this.Btn_Connect = new System.Windows.Forms.Button(); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.lis_Tags = new System.Windows.Forms.ListBox(); this.Btn_Add = new System.Windows.Forms.Button(); this.Btn_Remove = new System.Windows.Forms.Button(); this.groupBox2 = new System.Windows.Forms.GroupBox(); this.lis_Tags_Read = new System.Windows.Forms.ListBox(); this.groupBox3 = new System.Windows.Forms.GroupBox(); this.dgv_ReadResult = new System.Windows.Forms.DataGridView(); this.Btn_Read = new System.Windows.Forms.Button(); this.Btn_RemoveAll = new System.Windows.Forms.Button(); this.timer1 = new System.Windows.Forms.Timer(this.components); this.ckb_AutoRead = new System.Windows.Forms.CheckBox(); this.txt_AutoReadTimes = new System.Windows.Forms.TextBox(); this.groupBox1.SuspendLayout(); this.groupBox2.SuspendLayout(); this.groupBox3.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.dgv_ReadResult)).BeginInit(); this.SuspendLayout(); // // Txt_ip // this.Txt_ip.Location = new System.Drawing.Point(94, 17); this.Txt_ip.Name = "Txt_ip"; this.Txt_ip.Size = new System.Drawing.Size(100, 21); this.Txt_ip.TabIndex = 0; this.Txt_ip.Text = "127.0.0.1"; // // label1 // this.label1.AutoSize = true; this.label1.Location = new System.Drawing.Point(35, 20); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(53, 12); this.label1.TabIndex = 1; this.label1.Text = "IP地址:"; // // label2 // this.label2.AutoSize = true; this.label2.Location = new System.Drawing.Point(11, 47); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(77, 12); this.label2.TabIndex = 3; this.label2.Text = "服务器名称:"; // // Txt_serverName // this.Txt_serverName.Location = new System.Drawing.Point(94, 44); this.Txt_serverName.Name = "Txt_serverName"; this.Txt_serverName.Size = new System.Drawing.Size(183, 21); this.Txt_serverName.TabIndex = 2; this.Txt_serverName.Text = "Matrikon.OPC.Simulation.1"; // // Btn_Connect // this.Btn_Connect.Location = new System.Drawing.Point(202, 17); this.Btn_Connect.Name = "Btn_Connect"; this.Btn_Connect.Size = new System.Drawing.Size(75, 23); this.Btn_Connect.TabIndex = 4; this.Btn_Connect.Text = "连接"; this.Btn_Connect.UseVisualStyleBackColor = true; this.Btn_Connect.Click += new System.EventHandler(this.Btn_Connect_Click); // // groupBox1 // this.groupBox1.Controls.Add(this.lis_Tags); this.groupBox1.Location = new System.Drawing.Point(13, 71); this.groupBox1.Name = "groupBox1"; this.groupBox1.Size = new System.Drawing.Size(264, 251); this.groupBox1.TabIndex = 5; this.groupBox1.TabStop = false; this.groupBox1.Text = "浏览节点"; // // lis_Tags // this.lis_Tags.Dock = System.Windows.Forms.DockStyle.Fill; this.lis_Tags.FormattingEnabled = true; this.lis_Tags.ItemHeight = 12; this.lis_Tags.Location = new System.Drawing.Point(3, 17); this.lis_Tags.Name = "lis_Tags"; this.lis_Tags.SelectionMode = System.Windows.Forms.SelectionMode.MultiExtended; this.lis_Tags.Size = new System.Drawing.Size(258, 231); this.lis_Tags.TabIndex = 0; // // Btn_Add // this.Btn_Add.Location = new System.Drawing.Point(280, 170); this.Btn_Add.Name = "Btn_Add"; this.Btn_Add.Size = new System.Drawing.Size(75, 23); this.Btn_Add.TabIndex = 6; this.Btn_Add.Text = "添加"; this.Btn_Add.UseVisualStyleBackColor = true; this.Btn_Add.Click += new System.EventHandler(this.Btn_Add_Click); // // Btn_Remove // this.Btn_Remove.Location = new System.Drawing.Point(280, 209); this.Btn_Remove.Name = "Btn_Remove"; this.Btn_Remove.Size = new System.Drawing.Size(75, 23); this.Btn_Remove.TabIndex = 7; this.Btn_Remove.Text = "移除"; this.Btn_Remove.UseVisualStyleBackColor = true; this.Btn_Remove.Click += new System.EventHandler(this.Btn_Remove_Click); // // groupBox2 // this.groupBox2.Controls.Add(this.lis_Tags_Read); this.groupBox2.Location = new System.Drawing.Point(364, 71); this.groupBox2.Name = "groupBox2"; this.groupBox2.Size = new System.Drawing.Size(264, 251); this.groupBox2.TabIndex = 8; this.groupBox2.TabStop = false; this.groupBox2.Text = "添加节点"; // // lis_Tags_Read // this.lis_Tags_Read.Dock = System.Windows.Forms.DockStyle.Fill; this.lis_Tags_Read.FormattingEnabled = true; this.lis_Tags_Read.ItemHeight = 12; this.lis_Tags_Read.Location = new System.Drawing.Point(3, 17); this.lis_Tags_Read.Name = "lis_Tags_Read"; this.lis_Tags_Read.Size = new System.Drawing.Size(258, 231); this.lis_Tags_Read.TabIndex = 0; // // groupBox3 // this.groupBox3.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.groupBox3.Controls.Add(this.dgv_ReadResult); this.groupBox3.Location = new System.Drawing.Point(13, 328); this.groupBox3.Name = "groupBox3"; this.groupBox3.Size = new System.Drawing.Size(522, 153); this.groupBox3.TabIndex = 9; this.groupBox3.TabStop = false; this.groupBox3.Text = "读取节点"; // // dgv_ReadResult // this.dgv_ReadResult.AllowUserToAddRows = false; this.dgv_ReadResult.AllowUserToDeleteRows = false; this.dgv_ReadResult.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill; this.dgv_ReadResult.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.dgv_ReadResult.Dock = System.Windows.Forms.DockStyle.Fill; this.dgv_ReadResult.Location = new System.Drawing.Point(3, 17); this.dgv_ReadResult.Name = "dgv_ReadResult"; this.dgv_ReadResult.RowTemplate.Height = 23; this.dgv_ReadResult.Size = new System.Drawing.Size(516, 133); this.dgv_ReadResult.TabIndex = 0; // // Btn_Read // this.Btn_Read.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right))); this.Btn_Read.Font = new System.Drawing.Font("宋体", 20F, System.Drawing.FontStyle.Bold); this.Btn_Read.Location = new System.Drawing.Point(538, 430); this.Btn_Read.Name = "Btn_Read"; this.Btn_Read.Size = new System.Drawing.Size(90, 48); this.Btn_Read.TabIndex = 10; this.Btn_Read.Text = "读取"; this.Btn_Read.UseVisualStyleBackColor = true; this.Btn_Read.Click += new System.EventHandler(this.Btn_Read_Click); // // Btn_RemoveAll // this.Btn_RemoveAll.Location = new System.Drawing.Point(280, 252); this.Btn_RemoveAll.Name = "Btn_RemoveAll"; this.Btn_RemoveAll.Size = new System.Drawing.Size(75, 23); this.Btn_RemoveAll.TabIndex = 11; this.Btn_RemoveAll.Text = "移除全部"; this.Btn_RemoveAll.UseVisualStyleBackColor = true; this.Btn_RemoveAll.Click += new System.EventHandler(this.Btn_RemoveAll_Click); // // timer1 // this.timer1.Interval = 1000; this.timer1.Tick += new System.EventHandler(this.timer1_Tick); // // ckb_AutoRead // this.ckb_AutoRead.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right))); this.ckb_AutoRead.AutoSize = true; this.ckb_AutoRead.Location = new System.Drawing.Point(541, 372); this.ckb_AutoRead.Name = "ckb_AutoRead"; this.ckb_AutoRead.Size = new System.Drawing.Size(72, 16); this.ckb_AutoRead.TabIndex = 12; this.ckb_AutoRead.Text = "自动读取"; this.ckb_AutoRead.UseVisualStyleBackColor = true; this.ckb_AutoRead.CheckedChanged += new System.EventHandler(this.ckb_AutoRead_CheckedChanged); // // txt_AutoReadTimes // this.txt_AutoReadTimes.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right))); this.txt_AutoReadTimes.Location = new System.Drawing.Point(538, 345); this.txt_AutoReadTimes.Name = "txt_AutoReadTimes"; this.txt_AutoReadTimes.Size = new System.Drawing.Size(87, 21); this.txt_AutoReadTimes.TabIndex = 13; this.txt_AutoReadTimes.Text = "2000"; this.txt_AutoReadTimes.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.txt_AutoReadTimes_KeyPress); // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(649, 493); this.Controls.Add(this.txt_AutoReadTimes); this.Controls.Add(this.ckb_AutoRead); this.Controls.Add(this.Btn_RemoveAll); this.Controls.Add(this.Btn_Read); this.Controls.Add(this.groupBox3); this.Controls.Add(this.groupBox2); this.Controls.Add(this.Btn_Remove); this.Controls.Add(this.Btn_Add); this.Controls.Add(this.groupBox1); this.Controls.Add(this.Btn_Connect); this.Controls.Add(this.label2); this.Controls.Add(this.Txt_serverName); this.Controls.Add(this.label1); this.Controls.Add(this.Txt_ip); this.Name = "MainForm"; this.Text = "MainForm"; this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing); this.Load += new System.EventHandler(this.MainForm_Load); this.groupBox1.ResumeLayout(false); this.groupBox2.ResumeLayout(false); this.groupBox3.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.dgv_ReadResult)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.TextBox Txt_ip; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label2; private System.Windows.Forms.TextBox Txt_serverName; private System.Windows.Forms.Button Btn_Connect; private System.Windows.Forms.GroupBox groupBox1; private System.Windows.Forms.ListBox lis_Tags; private System.Windows.Forms.Button Btn_Add; private System.Windows.Forms.Button Btn_Remove; private System.Windows.Forms.GroupBox groupBox2; private System.Windows.Forms.ListBox lis_Tags_Read; private System.Windows.Forms.GroupBox groupBox3; private System.Windows.Forms.Button Btn_Read; private System.Windows.Forms.Button Btn_RemoveAll; private System.Windows.Forms.DataGridView dgv_ReadResult; private System.Windows.Forms.Timer timer1; private System.Windows.Forms.CheckBox ckb_AutoRead; private System.Windows.Forms.TextBox txt_AutoReadTimes; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】