先上圖
列舉一個通信協議
網關發送環境數據
此網關設備所對應的所有傳感器參數,格式如下:
網關發送:
包長度+KEY值+請求類型+發送者+接收者+消息類型+消息內容
說明:
包長度:short int型(16位),除本字段外,后面所跟的所有數據字節數;
標識符:int32型,固定值:0x987656789;
請求類型:int32型,數據通信:3;
發送者:string,終端編號,如:11170303001,格式:長度(int)+發送者字符串的Unicode編碼(長度是指發送者的Unicode編碼后的字節數);
接收者:string,服務器編號,如:server,格式:長度(int)+發送者字符串的Unicode編碼(長度是指發送者的Unicode編碼后的字節數);
消息內型:int32型,當前環境參數:146;
消息內容:所有控制口開關狀態及環境參數,其格式如下
輸出狀態+輸入狀態+傳感器數量N+傳感器數據 * N
說明:
輸出狀態:控制器開關量輸出狀態,long 型(64位),此變量每一位表示一個開關的狀態,1表示開,0表示關;
輸入狀態:控制器開關量輸入狀態,long 型(64位),此變量每一位表示一個輸入的狀態,1表示有輸入,0表示無輸入。最低位用於手自動狀態指示。
傳感器數量:byte型,指的是傳感器數量
傳感器數據:格式為:位置+類型+地址+參數個數+數據
說明:
位置:byte型,表示此組傳感器所擺放位置;如:1表示放置點為1區;
類型:byte
地址:byte型,此組傳感器的地址,每個終端所管理的傳感器中不能有重復地址;
參數個數:byte,每個參數對應一個Float數據
數據: float數組,數組元素個數由參數個數決定
服務器應答:
包長度+KEY值+應答類型
數據包長度:int16,除本字段外其他所有字段的字節 總數;
KEY值:int32,值=123454321;
應答類型:int32,值=100:發送成功,可根據此應答 判斷客戶端是否在線
//---------------------------------------------------------------------------------我是華麗的分割線--------------------------------------------------------------------------------------------------
核心代碼
public class StaticUdpDal { private static Socket socket = null; private bool IsRun = true, _IsSuccse = false; private int num = 0, recNum; private int _LoopNum = 3;//循環計數器和循環等待次數 private bool _IsReadMsgType = true;//是否讀取消息類型 public int LoopNum { get { return _LoopNum; } set { _LoopNum = value; } } private EndPoint point; /// <summary> /// 是否成功收到消息 /// </summary> public bool IsSuccse { get { return _IsSuccse; } set { _IsSuccse = value; } } private ProtocolModel pModel; /// <summary> /// 數據發送接收之間需要的數據模型 /// </summary> public ProtocolModel PModel { get { return pModel; } set { pModel = value; } } private MemoryStream sendStream; /// <summary> /// //發送內容 /// </summary> public MemoryStream SendStream { get { return sendStream; } set { sendStream = value; } } /// <summary> /// 是否讀取消息類型,默認讀取 /// </summary> public bool IsReadMsgType { get { return _IsReadMsgType; } set { _IsReadMsgType = value; } } public StaticUdpDal() { //綁定IP端口 if (socket == null) { socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); string UdpSendIP = ConfigurationManager.AppSettings["UdpSendIP"].ToString(); string UdpSendPort = ConfigurationManager.AppSettings["UdpSendPort"].ToString(); IPEndPoint ipep = new IPEndPoint(string.IsNullOrWhiteSpace(UdpSendIP) ? IPAddress.Any : IPAddress.Parse(UdpSendIP), string.IsNullOrWhiteSpace(UdpSendPort) ? 50090 : int.Parse(UdpSendPort));//本機預使用的IP和端口 socket.Bind(ipep); } } /// <summary> /// UDP發送接收主函數 /// </summary> public BinaryReader UdpMain() { Task<BinaryReader> taskRecive = Task<BinaryReader>.Factory.StartNew(() => ReciveMsg()); Task taskSend = Task.Factory.StartNew(() => sendMsg()); taskRecive.Wait(); return taskRecive.Result; } /// <summary> /// 向特定ip的主機的端口發送數據報 /// </summary> public void sendMsg() { while (true) { if (num >= _LoopNum || !IsRun) { IsRun = false; return; } num++; try { byte[] buffer = sendStream.ToArray(); point = new IPEndPoint(IPAddress.Parse(pModel.DisplayIP), pModel.DisplayPort); socket.SendTo(buffer, buffer.Length, SocketFlags.None, point);//發送 } catch (Exception ex) { ZP.Comm.ErrHandler.WriteError(ex, "UDP通信異常-發送"); IsRun = false; return; } Thread.Sleep(1000);//時間間隔為1秒 } } /// <summary> /// 接收發送給本機ip對應端口號的數據報 /// </summary> public BinaryReader ReciveMsg() { byte[] data = new byte[1500]; while (true) { recNum++; if (recNum > 30 || !IsRun) // 3秒未接收到消息,或標記變為停止,停止 { IsRun = false; return null; } if (socket == null || socket.Available < 1) { Thread.Sleep(200); continue; } try { int len = socket.Receive(data);//接收 socket.Receive(data,SocketFlags.None); // } catch (Exception ex) { // 在出現未處理的錯誤時運行的代碼 ZP.Comm.ErrHandler.WriteError(ex, "UDP通信異常-接收"); IsRun = false; return null; } BinaryReader reader = GetReciveData(data);//基礎判斷 if (!_IsSuccse) { Thread.Sleep(200); continue; } //socket.Close(); IsRun = false;//修改運行標記 return reader; } } /// <summary> /// 根據模型獲取基礎發送內容 /// </summary> /// <returns></returns> public MemoryStream GetBaseContent(ProtocolModel _pModel) { pModel = _pModel; sendStream = new MemoryStream(); BinaryWriter writer = new BinaryWriter(sendStream); writer.Write(System.Net.IPAddress.HostToNetworkOrder((short)0));//長度 writer.Write(System.Net.IPAddress.HostToNetworkOrder((int)pModel.Key));//key writer.Write(System.Net.IPAddress.HostToNetworkOrder((int)pModel.TalkType));//請求類型 writer.Write(System.Net.IPAddress.HostToNetworkOrder((int)pModel.Sender.Length * 2));//發送者 writer.Write(Encoding.BigEndianUnicode.GetBytes(pModel.Sender)); writer.Write(System.Net.IPAddress.HostToNetworkOrder((int)pModel.Receiver.Length * 2));//接收者 writer.Write(Encoding.BigEndianUnicode.GetBytes(pModel.Receiver)); writer.Write(System.Net.IPAddress.HostToNetworkOrder((int)pModel.MsgType));//消息類型 if (pModel.UseType == 2) { writer.Write(System.Net.IPAddress.HostToNetworkOrder((int)pModel.DisplayNo.Length * 2)); //終端編號 日光溫室 writer.Write(Encoding.BigEndianUnicode.GetBytes(pModel.DisplayNo)); } return sendStream; } /// <summary> /// 根據當前模型,獲取接受信息 /// </summary> /// <param name="data"></param> /// <returns></returns> public BinaryReader GetReciveData(byte[] data) { MemoryStream stream2 = new MemoryStream(data); BinaryReader reader = new BinaryReader(stream2); short packageLen = System.Net.IPAddress.NetworkToHostOrder(reader.ReadInt16());//長度 int key = System.Net.IPAddress.NetworkToHostOrder(reader.ReadInt32());//key if (!((!pModel.IsConServer && key == (int)GhMsgEnum.Key) || (pModel.IsConServer && key == (int)GhMsgEnum.Key1)))//987656789 123454321 return reader; //-----------------------------------------不同的地方-------------------------------------------------- int replyKind = System.Net.IPAddress.NetworkToHostOrder(reader.ReadInt32());//回復的類型 3 if (replyKind != PModel.AnswerType)//3 return reader; int senderLen = System.Net.IPAddress.NetworkToHostOrder(reader.ReadInt32());//發送者的長度 byte[] temp = new byte[1024]; temp = reader.ReadBytes(senderLen); string No = Encoding.BigEndianUnicode.GetString(temp); int receiveLen = System.Net.IPAddress.NetworkToHostOrder(reader.ReadInt32());//接收者的長度 string receive; if (receiveLen > 0) receive = Encoding.BigEndianUnicode.GetString(reader.ReadBytes(receiveLen)); //-----------------------------------------不同的地方-------------------------------------------------- if (IsReadMsgType) { int msgType = System.Net.IPAddress.NetworkToHostOrder(reader.ReadInt32());//消息類型 160 設備信息 if (msgType != PModel.AnswerMsgType)//160,155,156,158 return reader; } //獲取公用參數 _IsSuccse = true; return reader; } }
最后列舉剛剛的通信協議調用部分代碼
/// <summary> /// 寫入手動控制操作信息 文檔2 手動控制,打開關閉設備; 未完成(檢查開關狀態) /// </summary> /// <param name="GreenhouseID"></param> /// <param name="UserName"></param> /// <param name="isDoing">true:打開;false:關閉</param> /// <param name="coilStatus"></param> public long WriteDevice(int GreenhouseID, string UserName, bool isDoing, long coilStatus) { GreenHouseDal ghDal = new GreenHouseDal(); D_GreenhouseInfo ghModel = ghDal.GetModel(new D_GreenhouseInfo() { GreenhouseID = GreenhouseID });//獲取終端 ProtocolModel pModel = new ProtocolModel() { UseType = 1,//使用類型,溫室 IsConServer = ghModel.ConnectType != 1,//是否服務器轉發 TalkType = (int)GhMsgEnum.TALK,//請求類型 AnswerType = (int)GhMsgEnum.TALK,//應答類型 Sender = string.IsNullOrWhiteSpace(UserName) ? "server" : UserName,//發送者 Receiver = (ghModel.ConnectType != 1) ? ghModel.DisplayNo : "dev0",//接收者,網關編號 MsgType = isDoing ? (int)GhMsgEnum.SWITCHCTLON : (int)GhMsgEnum.SWITCHCTLOFF,//消息類型為 512、打開開關;513、關閉開關 AnswerMsgType = (int)GhMsgEnum.OutputCoil,//應答類型 //DisplayIP ="192.168.101.31",// (ghModel.ConnectType != 1) ? _DisplayIP : ghModel.DisplayIP,//目標IP //DisplayPort =8085// (ghModel.ConnectType != 1) ? _DisplayPort : (int)ghModel.DisplayPort,//目標端口 DisplayIP = (ghModel.ConnectType != 1) ? _DisplayIP : ghModel.DisplayIP,//目標IP DisplayPort = (ghModel.ConnectType != 1) ? _DisplayPort : (int)ghModel.DisplayPort,//目標端口 }; StaticUdpDal bud = new StaticUdpDal(); MemoryStream stream = bud.GetBaseContent(pModel);//獲取基礎發送內容 BinaryWriter writer = new BinaryWriter(stream); writer.Write(System.Net.IPAddress.HostToNetworkOrder((long)coilStatus));//寫入消息內容 writer.Seek(0, SeekOrigin.Begin); writer.Write(System.Net.IPAddress.HostToNetworkOrder((short)(stream.Length - 2)));//寫入長度 bud.SendStream = stream; bud.LoopNum = 1; BinaryReader reader = bud.UdpMain();//開始發送和接收 if (reader == null || !bud.IsSuccse) return (long)-1;//---------------------------------? long OutputStatus = System.Net.IPAddress.NetworkToHostOrder(reader.ReadInt64()); return OutputStatus; }
百度上有很多講 socket通信的,都比較基礎,我也是看着別人的代碼總結到自己的項目中的,這里有個關鍵點就是聲明一個靜態的socket變量,並且使用之后並不關閉,而是常駐內存,收到信號就開啟就收線程,然后再將信號轉發給服務器。之前做的都是socket使用后立馬關閉連接,反而會導致服務器內存使用居高不下。