转载连接: http://blog.csdn.net/zhuweisky/article/details/11827797

在网络上,交互的双方基于TCP或UDP进行通信,通信协议的格式通常分为两类:文本消息、二进制消息。

文本协议相对简单,通常使用一个特殊的标记符作为一个消息的结束。

二进制协议,通常是由消息头(Header)和消息体(Body)构成的,消息头的长度固定,而且,通过解析消息头,可以知道消息体的长度。如此,我们便可以从网络流中解析出一个个完整的二进制消息。

两种类型的协议格式各有优劣:文本协议直观、容易理解,但是在文本消息中很难嵌入二进制数据,比如嵌入一张图片;而二进制协议的优缺点刚刚相反。

在 客户端服务器通信demo(附源码)一文中,我们演示了如何使用了相对简单的文本协议,这篇文章我们将构建一个使用二进制消息进行通信的Demo。本Demo所做的事情是:客户端提交运算请求给服务端,服务端处理后,将结果返回给客户端。demo中定义消息头固定为8个字节:前四个字节为一个int,其值表示消息体的长度;后四个字节也是一个int,其值表示消息的类型。

1.Demo简介

该Demo总共包括三个项目:

(1)StriveEngine.BinaryDemoServer:基于StriveEngine开发的二进制通信服务端,处理来自客户端的请求并返回结果。

(2)StriveEngine.BinaryDemo:基于StriveEngine开发的二进制通信客户端,提交用户请求,并显示处理结果。

(3)StriveEngine.BinaryDemoCore:用于定义客户端和服务端都要用到的公共的消息类型和消息协议的基础程序集。

Demo运行起来后的截图如下所示:

2.消息头

首先,我们按照前面的约定,定义消息头MessageHead。

    public class MessageHead{public const int HeadLength = 8;public MessageHead() { }public MessageHead(int bodyLen, int msgType){this.bodyLength = bodyLen;this.messageType = msgType;}private int bodyLength;/// <summary>/// 消息体长度/// </summary>public int BodyLength{get { return bodyLength; }set { bodyLength = value; }}private int messageType;/// <summary>/// 消息类型/// </summary>public int MessageType{get { return messageType; }set { messageType = value; }}public byte[] ToStream(){byte[] buff = new byte[MessageHead.HeadLength];byte[] bodyLenBuff = BitConverter.GetBytes(this.bodyLength) ;byte[] msgTypeBuff = BitConverter.GetBytes(this.messageType) ;Buffer.BlockCopy(bodyLenBuff,0,buff,0,bodyLenBuff.Length) ;Buffer.BlockCopy(msgTypeBuff,0,buff,4,msgTypeBuff.Length) ;return buff;}}

消息头由两个int构成,正好是8个字节。而且在消息头的定义中增加了ToStream方法,用于将消息头序列化为字节数组。

通过ToStream方法,我们已经可以对消息转化为流(即所谓的序列化)的过程窥见一斑了,基本就是操作分配空间、设置偏移、拷贝字节等。

3.消息类型

根据业务需求,需要定义客户端与服务器之间通信消息的类型MessageType。

    public static class MessageType{/// <summary>/// 加法请求/// </summary>public const int Add = 0;/// <summary>/// 乘法请求/// </summary
        public const int Multiple = 1;/// <summary>/// 运算结果回复/// </summary
        public const int Result = 2;        }

消息类型有两个请求类型,一个回复类型。请注意消息的方向,Add和Multiple类型的消息是由客户端发给服务器的,而Result类型的消息则是服务器发给客户端的。

4.消息体

一般的消息都由消息体(MessageBody),用于封装具体的业务数据。当然,也有些消息只有消息头,没有消息体的。比如,心跳消息,设计时,我们只需要使用一个消息类型来表示它是一个心跳就可以了,不需要使用消息体。

本demo中,三种类型的消息都需要消息体来封装业务数据,所以,demo中本应该定义了3个消息体,但demo中实际上只定义了两个:RequestContract、ResponseContract。这是因为Add和Multiple类型的消息公用的是同一个消息体RequestContract。

    [Serializable]public class RequestContract{public RequestContract() { }public RequestContract(int num1, int num2){this.number1 = num1;this.number2 = num2;}private int number1;/// <summary>/// 运算的第一个数。/// </summary>public int Number1{get { return number1; }set { number1 = value; }}private int number2;/// <summary>/// 运算的第二个数。/// </summary>public int Number2{get { return number2; }set { number2 = value; }}}[Serializable]public class ResponseContract{public ResponseContract() { }public ResponseContract(int num1, int num2 ,string opType,int res){this.number1 = num1;this.number2 = num2;this.operationType = opType;this.result = res;}private int number1;/// <summary>/// 运算的第一个数。/// </summary>public int Number1{get { return number1; }set { number1 = value; }}private int number2;/// <summary>/// 运算的第二个数。/// </summary>public int Number2{get { return number2; }set { number2 = value; }}private string operationType;/// <summary>/// 运算类型。/// </summary>public string OperationType{get { return operationType; }set { operationType = value; }}private int result;/// <summary>/// 运算结果。/// </summary>public int Result{get { return result; }set { result = value; }}}

关于消息体的序列化,demo采用了.NET自带的序列化器的简单封装(即SerializeHelper类)。当然,如果客户端不是.NET平台,序列化器不一样,那就必须像消息头那样一个字段一个字段就构造消息体了。

5.服务端

关于StriveEngine使用的部分,在 客户端服务器通信demo(附源码)一文中已有说明,我们这里就不重复了。我们直接关注业务处理部分:

void tcpServerEngine_MessageReceived(IPEndPoint client, byte[] bMsg)
{//获取消息类型int msgType = BitConverter.ToInt32(bMsg, 4);//消息类型是 从offset=4处开始 的一个整数//解析消息体RequestContract request = (RequestContract)SerializeHelper.DeserializeBytes(bMsg, MessageHead.HeadLength, bMsg.Length - MessageHead.HeadLength); int result = 0;string operationType = "";if (msgType == MessageType.Add){result = request.Number1 + request.Number2;operationType = "加法";}else if (msgType == MessageType.Multiple){result = request.Number1 * request.Number2;operationType = "乘法";}else{operationType = "错误的操作类型";}//显示请求string record = string.Format("请求类型:{0},操作数1:{1},操作数2:{2}", operationType, request.Number1 , request.Number2);this.ShowClientMsg(client, record);//回复消息体ResponseContract response = new ResponseContract(request.Number1, request.Number2, operationType, result);byte[] bReponse = SerializeHelper.SerializeObject(response);      //回复消息头MessageHead head = new MessageHead(bReponse.Length, MessageType.Result);byte[] bHead = head.ToStream();//构建回复消息byte[] resMessage = new byte[bHead.Length + bReponse.Length];Buffer.BlockCopy(bHead, 0, resMessage, 0, bHead.Length);Buffer.BlockCopy(bReponse, 0, resMessage, bHead.Length, bReponse.Length);//发送回复消息this.tcpServerEngine.PostMessageToClient(client, resMessage);
}

其主要流程为:

(1)解析消息头,获取消息类型和消息体的长度。

(2)根据消息类型,解析消息体,并构造协议对象。

(3)业务处理运算。(如 加法或乘法)

(4)根据业务处理结果,构造回复消息。

(5)发送回复消息给客户端。

6.客户端

(1)提交请求

    private void button1_Click(object sender, EventArgs e){this.label_result.Text = "-";int msgType = this.comboBox1.SelectedIndex == 0 ? MessageType.Add : MessageType.Multiple;//请求消息体RequestContract contract = new RequestContract(int.Parse(this.textBox1.Text), int.Parse(this.textBox2.Text));            byte[] bBody = SerializeHelper.SerializeObject(contract);//消息头MessageHead head = new MessageHead(bBody.Length,msgType) ;byte[] bHead = head.ToStream();//构建请求消息byte[] reqMessage = new byte[bHead.Length + bBody.Length];Buffer.BlockCopy(bHead, 0, reqMessage, 0, bHead.Length);Buffer.BlockCopy(bBody, 0, reqMessage, bHead.Length, bBody.Length);//发送请求消息this.tcpPassiveEngine.PostMessageToServer(reqMessage);}

其流程为:构造消息体、构造消息头、拼接为一个完整的消息、发送消息给服务器。

注意:必须将消息头和消息体拼接为一个完整的byte[],然后通过一次PostMessageToServer调用发送出去,而不能连续两次调用PostMessageToServer来分别发送消息头、再发送消息体,这在多线程的情况下,是非常有可能在消息头和消息体之间插入其它的消息的,如果这样的情况发生,那么,接收方就无法正确地解析消息了。

(2)显示处理结果

    void tcpPassiveEngine_MessageReceived(System.Net.IPEndPoint serverIPE, byte[] bMsg){//获取消息类型int msgType = BitConverter.ToInt32(bMsg, 4);//消息类型是 从offset=4处开始 的一个整数if (msgType != MessageType.Result){return;}//解析消息体ResponseContract response = (ResponseContract)SerializeHelper.DeserializeBytes(bMsg, MessageHead.HeadLength, bMsg.Length - MessageHead.HeadLength);string result = string.Format("{0}与{1}{2}的答案是 {3}" ,response.Number1,response.Number2,response.OperationType,response.Result);this.ShowResult(result);}

过程与服务端处理接收到的消息是类似的:从接收到的消息中解析出消息头、再根据消息类型解析出消息体,然后,将运算结果从消息体中取出并显示在UI上。

7.源码下载

二进制通信demo源码

客户端服务器通信demo(续) -- 使用二进制协议 (附源码)相关推荐

  1. android仿疯狂猜图源码,Android开发实现高仿优酷的客户端图片左右滑动切换功能实例【附源码下载】...

    本文实例讲述了Android开发实现高仿优酷的客户端图片左右滑动切换功能.分享给大家供大家参考,具体如下: 本例是用ViewPager去做的实现,支持自动滑动和手动滑动,不仅优酷网,实际上有很多商城和 ...

  2. 上传图片到linux返回url,Springboot 将前端传递的图片上传至Linux服务器并返回图片的url(附源码)...

    问题由来: 用户个人信息需要添加头像功能 当前端程序是微信小程序时,前端将直接将图片 url 传送至服务端 但是当前端是 Web 页面时,前端传递的参数是一张图片,服务端需要将图片保存至 Linux ...

  3. 无痕微信群发服务器,[原创]微信无痕清粉分析过程-附源码地址

    不知道为啥我不能发引用,希望你能看到吧. 微信版本2.9.0.123#define WxAddWxUserParam1 0x13EB348                //添加好友 #define  ...

  4. Modbus通信协议+Modbus串口调试工具+Java版协议解析源码

    网络与串口二合一调试助手TCPCOM: https://download.csdn.net/download/liuyuan_java/87454762 Modbus调试工具,模拟串口调试工具 htt ...

  5. Linux-什么是二进制包,源码包,RPM包,软件仓库

    博文说明[前言]: 本文将通过个人口吻介绍什么是二进制包,RPM包,源码RPM包(SRPM包),源码包,以及RPM常用命令,源码rpm的安装(*.src.rpm),源码包的安装步骤知识(./confi ...

  6. Netty实战:Springboot+Netty+protobuf开发高性能服务器 (附源码下载)

    Springboot-cli 开发脚手架系列 Netty系列:Springboot使用Netty集成protobuf开发高性能服务器 文章目录 Springboot-cli 开发脚手架系列 简介 1. ...

  7. 源码系列:基于FPGA的PS2通信电路设计(附源码)

    今天给大侠带来基于FPGA的PS2通信电路设计,附源码,获取源码,请在"FPGA技术江湖"公众号内回复"PS2源码",可获取源码文件.话不多说,上货. 设计背景 ...

  8. DSP:6678开发板NDK网口通信完整实现(附源码)

    如果出现图片打不开,或是显示异常,请点击下方链接阅读原文!!! DSP:6678开发板NDK网口通信完整实现(附源码) - 子木的文章 - 知乎 https://zhuanlan.zhihu.com/ ...

  9. Spring Web Flow 入门demo(三)嵌套流程与业务结合 附源码

    转载地址 ; http://blog.csdn.net/hejingyuan6/article/details/46723021 上篇博客我们说spring web Flow与业务结合的方式主要有三种 ...

最新文章

  1. 《评人工智能如何走向新阶段》后记(再续19)
  2. 美团实习面试:熟悉红黑树是吧?能不能写一下?
  3. xa 全局锁_分布式事务如何实现?深入解读 Seata 的 XA 模式
  4. python3.7.4安装教程-Python3.7.4图文安装教程
  5. Python + Django 如何支撑了 7 亿月活用户的 Instagram?
  6. 如何为Kalman Studio编写T4模板
  7. 统计分析软件_专业统计分析软件 SPSS 25 来了!手把手教你安装
  8. 阿里网盘阿里云盘----手机端PC端
  9. php7实践指南-ch15MySQL数据库的使用
  10. 电脑的锁屏密码忘记了怎么办?
  11. Windows XP系统中实用的命令及操作技巧
  12. 基于百度飞浆平台(EasyDL)设计的人脸识别考勤系统
  13. 从NASA图片发现的“太阳UFO” 近似形状物体
  14. 软考网络工程师上午考试知识点总结整理
  15. oracle idc排名,idc 排行_2015中国IDC排行榜TOP20
  16. 蓝牙控制摇摇棒(电子综合设计)
  17. 求2的n次方对1e9+7的模
  18. 线性代数学习笔记——行列式的性质及拉普拉斯定理——4. 行列式的性质1~性质3
  19. 《图解HTTP》第七章个人学习
  20. 在线工具网站uzer.me使用Google Chrome浏览器打开,出现页面显示异常的解决办法

热门文章

  1. 带宽,速率,吞吐量区别
  2. 计算机显卡维修,pci-e显卡基本维修教程
  3. 因果森林总结:基于树模型的异质因果效应估计
  4. 淘晶驰串口屏_ 串口屏卡顿的原因
  5. 关于ASP木马提升权限
  6. DAX:LOOKUPVALUE 函数
  7. 开关电源的开关管一般用MOS管而不是三极管原因
  8. pathon真的那么好用吗?
  9. Day07:常用模块,面向对象编程(对象类)及内置函数
  10. pandas处理excel单元格合并后的列