本章可以不看了,已经对格式内容进行了修正和优化.见文章手把手教你学习IEC104协议和编程实现 一 起章-CSDN博客
从今天开始,我准备将我编程实现IEC104协议软件的思路给各位分享下,里面会穿插IEC104协议的具体分析,对于学习IEC104协议和编程实现都有一定的意义,也算科普一下吧.
本章仅对网络部分做一个讲解.
众所周知,我们的IEC104是国家电网公司经常使用的一个协议,在电力系统工作的人,通讯这一块有接触的话,都要对这个协议有一个深入的理解.
IEC104协议有三个版本,分别是1997版,2002版,2009版,其中我们使用最多的可能是200版.
各版本在规约处理流程上没有什么变化,不同之处在于:
(1)2002版在1997版的基础上,扩展了遥测、遥信、遥控等信息体基体址。
(2)2009版在2002版的基础上,增加了协议的传输序列和互操作性的改进,以及对冗余连接处理方面的新功能。
具体的内容我们后面会讲到.
我们首先要知道的是IEC104协议传输的介质是网络.所以她是通过socket方式进行传输的,这个就涉及到网络ip和网络端口,这2个参数才能构成一个网络接口参数,缺一不可.
所以今天这一章我们先学习实现网络部分,仅和编程有关系,具体IE104的具体内容见下一章,毕竟我们要循序渐进的,嘿嘿.
clientsocket类
我们先建立一个类,名称为clientsocket,代表客户端的模式下的socket类,毕竟,万一我们以后使用server模式呢.
这个类主要的函数为connect,close,receivestr,sendstr,分别用于链接,断开(关闭),接收数据,发送数据函数.
函数1:connect
/// <summary>
/// connect 网络连接
/// </summary>
/// <param name="ipadd"></param>
/// <param name="port"></param>
#region 连接ip,端口号
public bool Connect(string ipadd, int port)
{
byte[] data = new byte[4096];
newclient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ie = new IPEndPoint(IPAddress.Parse(ipadd), port);
try
{
newclient.Connect(ie);
Connected = true;
ThreadStart myThreaddelegate = new ThreadStart(Receivestr);
myThread = new Thread(myThreaddelegate);
myThread.Start();
}
catch (Exception ex)
{
string str = "对象名称: " + ex.Source + "\n" +
"函数: " + ex.TargetSite + "\n" +
"故障点: " + ex.StackTrace.Substring(ex.StackTrace.IndexOf("\\")) + "\n" +
"错误提示 : " + ex.Message + "\r\n";
writeLogFile(str);
Connected = false;
if(myThread!=null)
myThread.Join(0);
}
return Connected;
}
#endregion
我们需要先定义变量:
public Socket newclient;
public bool Connected;
public Thread myThread;
我了能够正常使用,我们还需要先包含相关的工作空间
using System.Net;
using System.Net.Sockets;
using System.Threading;
对于catch()部分,这样我们可以在发生异常的时候,将异常文件的函数和行数等信息输出出来,更加精确的分析故障发生的原因.如果不是很理解,这一部分可以删除掉,直接catch()也OK.
这个函数的功能就是使用线程的方式对定义好的网络参数连接成功,开始准备发送和接收数据.
函数2: close
/// <summary>
/// close 关闭网络
/// </summary>
#region 关闭
public void close()
{
if (Connected)
{
newclient.Close();
}
}
#endregion
用于程序结束的时候,需要先关闭网络.
函数3: receivestr
#endregion
/// <summary>
/// receivestr 接收字符串
/// </summary>
#region 接收数据
public void Receivestr()
{
receive_str = "";
stringdata = "";
int recv=0;
//修正当手动断开连接时,cpu占用过高的问题,原来是while(true)
while (Connected)
{
Console.WriteLine("receivestr is runing \r\n");
if (!Connected) return;
try
{
recv = newclient.Receive(buffer);
for (int i = 0; i < recv; i++)
{
data.Add(buffer[i]);
receive_str += data[i].ToString("X2");
}
stringdata = Encoding.UTF8.GetString(buffer, 0, recv);
}
catch
{
Connected = false;
//return;
}
}
}
#endregion
这个函数需要先定义几个变量
public string send_str,receive_str;//发送字符串,接收字符串
public string stringdata="";
public byte[] buffer = new byte[4096]; //数据存放的缓冲区
public List<byte> data = new List<byte>();//链表,用于循环组合报文.
函数4:sendstr
/// <summary>
/// sendstr 发送字符串
/// </summary>
/// <param name="str"></param>
/// <param name="hexflag"></param>
#region 发送数据
public void sendstr(string str,int hexflag)
{
if (!Connected) return;
//str = str.Replace(" ", "");
int m_length = str.Length/2;
byte[] data = new byte[m_length];
if (hexflag == 0)
{
data = Encoding.UTF8.GetBytes(str);
}
else
{
//在这里我们引入了一个新的函数,作用是将字符串转换成16进制的数组,函数实现在后面
int len = strtohexarray(str, data);
}
//data[4] = Convert.ToByte(sendindex);
try
{
sendlen = newclient.Send(data);
}
catch
{
//Console.Write(ex.Message);
Connected = false;
return;
}
}
#endregion
附加函数strtohexarray
/// <summary>
/// strtohexarray 将字符串转换成byte[]
/// </summary>
/// <param name="str"></param>
/// <param name="b"></param>
/// <returns></returns>
#region
public int strtohexarray(string str,byte[] b)
{
int i = 0;
try
{
str = str.Replace(" ", "");
str = str.Replace("-", "");
str = str.Replace(">", "");
if (str.Length % 2 != 0)
str = str.Substring(0, str.Length - 1);
for (i = 0; i < str.Length / 2; i++)
{
b[i] = System.Convert.ToByte(str.Substring(i * 2, 2), 16);
}
return i;
}
catch
{
return i;
}
}
#endregion
这个函数的作用就是将我们的组织好的报文发送出去,具体我们使用的,将报文组织好,直接调用这个函数就OK.其中,可以选择是按照16进制的模式,还是使用字符模式,这2中模式下的发送内容处理方式是不一样.有兴趣的朋友可以深入研究下.
另外为 更好的调试或者运行log,我们设计了辅助函数writelogfile,将收到的报文以数组或者字符串的格式保存到文件中.
函数5:writelog
函数用途将报文写入到log文件,由于我们在处理的过程可能会涉及到不同的模式,所以我们使用2个函数writeLogFile(string str),writeLogFile(byte[] bb,int len)来实现.具体如下:
#region 写log文件
//将字符串类型的数据存储到log文件中
public int writeLogFile(string str)
{
if(str=="") return 1;
string filePath = Form1.form.datadir + @"run.log";//这里文件的路径是当前执行程序下的run.log文件
//当文件存在,并且大小大于20M的时候,删除文件,这样是为了保证存储log的速度
if (File.Exists(filePath))
{
FileInfo fileInfo = new FileInfo(filePath);
if (fileInfo.Length > 2000000)
File.Delete(filePath);
}
try
{
FileStream aFile = new FileStream(filePath, FileMode.OpenOrCreate | FileMode.Append);
StreamWriter sw = new StreamWriter(aFile);
DateTime tt = DateTime.Now;
str = "[" + tt.ToString() + "] " + str;
sw.WriteLine(str);
sw.Close();
aFile.Close();
}
catch { }
return 1;
}
#endregion
#region 写log文件
//将数组数据保存到log文件中,使用len的目的是我们可以选择从开始的一部分数据来储存
public int writeLogFile(byte[] bb,int len)
{
if (len > bb.Length) len = bb.Length;
string str = "";
for (int i = 0; i < len; i++)
{
str += bb[i].ToString("X2");
str += " ";
}
string filePath = Form1.form.datadir + @"run.log";
if (File.Exists(filePath))
{
FileInfo fileInfo = new FileInfo(filePath);
if (fileInfo.Length > 2000000)
File.Delete(filePath);
}
try
{
FileStream aFile = new FileStream(filePath, FileMode.OpenOrCreate | FileMode.Append);
StreamWriter sw = new StreamWriter(aFile);
DateTime tt = DateTime.Now;
str = "[" + tt.ToString() + "] " + str+"\r\n";
sw.WriteLine(str);
sw.Close();
aFile.Close();
return 1;
}
catch
{
return 1;
}
}
#endregion
这样我们的网络类就完成了,在真正使用这个类的时候,可以直接调用这个类就OK了.
下一章我们就直接讲解IEC104的规约结构了,最好能提前看看,呵呵.
好了就写到这里.
创作不易,点赞不止,您的关注与点赞是我创作的最大动力!