前段时间,有几个研究ESFramework通信框架的朋友对我说,ESFramework有点庞大,对于他们目前的项目来说有点“杀鸡用牛刀”的意思,因为他们的项目不需要文件传送、不需要P2P、不存在好友关系、也不存在组广播、不需要服务器均衡、不需要跨服务器通信、甚至都不需要使用UserID,只要客户端能与服务端进行简单的稳定高效的通信就可以了。于是,他们建议我,整一个轻量级的C#通讯组件来满足类似他们这种项目的需求。我觉得这个建议是有道理的,于是,花了几天时间,我将ESFramework的内核抽离出来,经过修改封装后,形成了StriveEngine通讯组件,其最大的特点就是稳定高效、易于使用。

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

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

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

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

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

1.StriveEngine通讯组件Demo简介

该Demo总共包括三个项目:

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

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

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

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

2.消息头

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

    public class MessageHead
{
public const int HeadLength = ; 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,,buff,,bodyLenBuff.Length) ;
Buffer.BlockCopy(msgTypeBuff,,buff,,msgTypeBuff.Length) ;
return buff;
}
}

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

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

3.消息类型

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

    public static class MessageType
{
/// <summary>
/// 加法请求
/// </summary>
public const int Add = ; /// <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服务端

关于StriveEngine使用的部分,在 轻量级通信引擎StriveEngine —— C/S通信demo(附源码)一文中已有说明,我们这里就不重复了。我们直接关注业务处理部分:

void tcpServerEngine_MessageReceived(IPEndPoint client, byte[] bMsg)
{
//获取消息类型
int msgType = BitConverter.ToInt32(bMsg, );//消息类型是 从offset=4处开始 的一个整数
//解析消息体
RequestContract request = (RequestContract)SerializeHelper.DeserializeBytes(bMsg, MessageHead.HeadLength, bMsg.Length - MessageHead.HeadLength);
int result = ;
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, , resMessage, , bHead.Length);
Buffer.BlockCopy(bReponse, , resMessage, bHead.Length, bReponse.Length); //发送回复消息
this.tcpServerEngine.PostMessageToClient(client, resMessage);
}

其主要流程为:

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

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

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

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

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

6.StriveEngine通讯组件Demo客户端

(1)提交请求

    private void button1_Click(object sender, EventArgs e)
{
this.label_result.Text = "-";
int msgType = this.comboBox1.SelectedIndex == ? 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, , reqMessage, , bHead.Length);
Buffer.BlockCopy(bBody, , 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, );//消息类型是 从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.StriveEngine通讯组件Demo源码下载

二进制通信demo源码

 附相关系列:文本协议通信demo源码 说明文档

             打通B/S与C/S通信demo源码与说明文档

  另附:简单即时通讯Demo源码及说明

 

版权声明:本文为博主原创文章,未经博主允许不得转载。

C#轻量级通通讯组件StriveEngine —— C/S通信开源demo(2) —— 使用二进制协议 (附源码)的更多相关文章

  1. 轻量级C#网络通信组件StriveEngine —— C/S通信开源demo(附源码)

    前段时间,有几个研究ESFramework网络通讯框架的朋友对我说,ESFramework有点庞大,对于他们目前的项目来说有点“杀鸡用牛刀”的意思,因为他们的项目不需要文件传送.不需要P2P.不存在好 ...

  2. 轻量级通信引擎StriveEngine —— C/S通信demo(2) —— 使用二进制协议 (附源码)

    在网络上,交互的双方基于TCP或UDP进行通信,通信协议的格式通常分为两类:文本消息.二进制消息. 文本协议相对简单,通常使用一个特殊的标记符作为一个消息的结束. 二进制协议,通常是由消息头(Head ...

  3. C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(三:附源码)

    前言:之前的两篇封装了一些基础的表单组件,这篇继续来封装几个基于bootstrap的其他组件.和上篇不同的是,这篇的有几个组件需要某些js文件的支持. 本文原创地址:http://www.cnblog ...

  4. 日志组件Log2Net的介绍和使用(附源码开源地址)

    Log2Net是一个用于收集日志到数据库或文件的组件,支持.NET和.NetCore平台. 此组件自动收集系统的运行日志(服务器运行情况.在线人数等).异常日志.程序员还可以添加自定义日志. 该组件支 ...

  5. 轻量级通信引擎StriveEngine —— C/S通信demo(附源码)

    前段时间,有几个研究ESFramework的朋友对我说,ESFramework有点庞大,对于他们目前的项目来说有点“杀鸡用牛刀”的意思,因为他们的项目不需要文件传送.不需要P2P.不存在好友关系.也不 ...

  6. android记帐本、涂鸦、仿腾讯新闻、仿bilibili、Markdwon便签、资讯APP等源码

    Android精选源码 kotlin版仿哔哩哔哩动画Android客户端源码 android实现图片涂鸦效果源码 Android 开源记账本项目源码 android高仿腾讯新闻app源码 androi ...

  7. DevExpress - 使用 GaugeControl 标尺组件制作抽奖程序 附源码

    前不久,公司举办了15周年庆,其中添加了一个抽奖环节,要从在读学员中随机抽取幸运学员,当然,这个任务就分到了我这里. 最后的效果如下,启动有个欢迎页面,数据是来自Excel的,点击开始则上面的学号及姓 ...

  8. 编写轻量ajax组件03-实现(附源码)

    前言 通过前两篇的介绍,我们知道要执行页面对象的方法,核心就是反射,是从请求获取参数并执行指定方法的过程.实际上这和asp.net mvc框架的核心思想很类似,它会解析url,从中获取controll ...

  9. Android 高仿 频道管理----网易、今日头条、腾讯视频 (可以拖动的GridView)附源码DEMO

    距离上次发布(android高仿系列)今日头条 --新闻阅读器 (二) 相关的内容已经半个月了,最近利用空闲时间,把今日头条客户端完善了下.完善的功能一个一个全部实现后,就放整个源码.开发的进度就是按 ...

随机推荐

  1. 100-days: twenty-nine

    Title: The promise and perils of synthetic biology promise n.希望成功的前景 peril n.巨大的危险:险情,险境 释义:the peri ...

  2. CentOS 7 安装phpredis和redis(接上一篇centos7安装lnmp)

    一.安装扩展phpredis 1.PHP7 安装redis 扩展phpredis cd /root/software wget https://github.com/edtechd/phpredis/ ...

  3. 消息中间件和JMS介绍

    在一个公司创立初期,他可能只有几个应用,系统之间的关联也不是那么大,A系统调用B系统就直接调用B提供的API接口:后来这个公司做大了,他一步步发展有了几十个系统,这时候A系统要调用B系统的接口,但是B ...

  4. 微信小程序发送ajax

    微信小程序通过 wx.request发送ajax请求 1. GET wx.request({ url: app.globalData.pubSiteUrl + 'user-information/ge ...

  5. java之servlet学习基础(一)

    这一阵子在学java三大框架.却在学习过程中发现前面的知识已经忘记了.所以决定写一篇博客来总结回顾之前的学习. 1.Servlet是什么? servlet是一个运行在服务器端的小应用程序.通过HTTP ...

  6. docker--容器和镜像的导入导出及部署

    一.镜像导出 save 1.查看镜像 docker images 2.导出镜像 docker save -o test.tar image_name 或 docker save image_name ...

  7. 201771010134杨其菊《面向对象程序设计(java)》第十六周学习总结

    第十六周学习总结 第一部分:理论知识 1. 程序是一段静态的代码,它是应用程序执行的蓝本.进程是程序的一次动态执行,它对应了从代码加载.执行至执行完毕的一个完整过程.操作系统为每个进程分配一段独立的内 ...

  8. List Leave

    本次作业是建立二叉树并输出叶结点 (1)首先是定义结点,包括左孩子,右孩子 typedef struct { int lch;//左孩子 int rch;//右孩子 }Node; (2)建立二叉树 c ...

  9. 使用fiddler模拟session失效的测试方法

    1.Fiddler的基本界面 2.json串放入本地txt文件中 例如, test.txt: {"retCode": "200","Msg" ...

  10. Js 常用调试的方法

    A  使用alert() 和document.write() 方法监视变量值 如果要中断代码的运行,监视变量的值,则使用alert() 方法: 如果需要查看的值很多,则使用document.write ...