先上图
列举一个通信协议
网关发送环境数据
此网关设备所对应的所有传感器参数,格式如下:
网关发送:
包长度+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; ////// 是否成功收到消息 /// public bool IsSuccse { get { return _IsSuccse; } set { _IsSuccse = value; } } private ProtocolModel pModel; ////// 数据发送接收之间需要的数据模型 /// public ProtocolModel PModel { get { return pModel; } set { pModel = value; } } private MemoryStream sendStream; ////// //发送内容 /// public MemoryStream SendStream { get { return sendStream; } set { sendStream = value; } } ////// 是否读取消息类型,默认读取 /// 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); } } ////// UDP发送接收主函数 /// public BinaryReader UdpMain() { TasktaskRecive = Task .Factory.StartNew(() => ReciveMsg()); Task taskSend = Task.Factory.StartNew(() => sendMsg()); taskRecive.Wait(); return taskRecive.Result; } /// /// 向特定ip的主机的端口发送数据报 /// 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秒 } } ////// 接收发送给本机ip对应端口号的数据报 /// 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; } } ////// 根据模型获取基础发送内容 /// ///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; } /// /// 根据当前模型,获取接受信息 /// /// ///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; } }
最后列举刚刚的通信协议调用部分代码
////// 写入手动控制操作信息 文档2 手动控制,打开关闭设备; 未完成(检查开关状态) /// /// /// /// true:打开;false:关闭 /// 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使用后立马关闭连接,反而会导致服务器内存使用居高不下。