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

简介: 前段时间,有几个研究ESFramework通信框架的朋友对我说,ESFramework有点庞大,对于他们目前的项目来说有点“杀鸡用牛刀”的意思,因为他们的项目不需要文件传送、不需要P2P、不存在好友关系、也不存在组广播、不需要服务器均衡、不需要跨服务器通信、甚至都不需要使用UserID,只要客户端能与服务端进行简单的稳定高效的通信就可以了。

前段时间,有几个研究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 = 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服务端

关于StriveEngine使用的部分,在 轻量级通信引擎StriveEngine —— C/S通信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.StriveEngine通讯组件Demo客户端

(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.StriveEngine通讯组件Demo源码下载

二进制通信demo源码

 

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

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

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

 

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

 

目录
相关文章
|
7天前
|
数据采集 安全 JavaScript
C#医院手术麻醉信息管理系统源码 对接院内HIS、LIS、PACS
手麻系统的功能涵盖了麻醉临床业务管理、麻醉运营业务管理以及手术进程管理等,实现了将多种麻醉病历文书与医院HIS系统的有效关联,让手术室人员、设备资源和信息资源高度共享;实现了手术安排、各种统计报表等科室管理和科研工作的需求,可借其收集临床数据、进行整合分析,为围术期临床信息、管理、科研提供整体解决方案;该系统的运行,为医护人员提供了流程化、信息化、自动化、智能化的临床业务综合管理。
23 5
|
1月前
|
数据处理 C# C++
如何使用C#和C++结构体实现Socket通信
如何使用C#和C++结构体实现Socket通信
|
4天前
|
开发框架 前端开发 JavaScript
采用C#.Net +JavaScript 开发的云LIS系统源码 二级医院应用案例有演示
技术架构:Asp.NET CORE 3.1 MVC + SQLserver + Redis等 开发语言:C# 6.0、JavaScript 前端框架:JQuery、EasyUI、Bootstrap 后端框架:MVC、SQLSugar等 数 据 库:SQLserver 2012
|
21天前
|
安全 JavaScript 前端开发
C#医院手麻系统源码,手术麻醉管理系统源码
手术麻醉管理系统贯穿患者入院至出院全程,整合术前术后流程,如手术排班、麻醉计划、术中监护及术后恢复。采用C#.net6.0、Vue、Ant-Design等技术,与sqlserver2019数据库结合。系统提供麻醉记录、手术安全核查、费用管理等功能,确保信息数字化和流程规范化。通过自动化记录和数据分析,增强手术安全,优化资源分配,并支持医疗质控与研究。
C#医院手麻系统源码,手术麻醉管理系统源码
|
22天前
|
存储 监控 安全
C#手术麻醉系统源码 大型医院手麻系统4大需求是什么?
C#编写的大型医院手术麻醉系统旨在满足四大核心需求:智慧医院建设,要求实时患者信息共享与监控;医院等级评级,强调安全评估与术后管理;电子病历评级,规定手术预约、麻醉信息的标准化;科室需求,包括系统互联、流程信息化、数据追溯、操作简便、文书完整、快速响应、生命体征动态采集及质量控制。系统提升手麻工作的效率与安全性,确保手术顺利进行。
|
1月前
|
算法 安全 C#
C#版开源免费的Bouncy Castle密码库
C#版开源免费的Bouncy Castle密码库
|
1月前
|
数据挖掘 C# 开发工具
采用C#语言开发的全套医院体检系统PEIS源码功能介绍
体检系统,是专为体检中心/医院体检科等体检机构,专门开发的全流程管理系统,通过软件实现检测仪器数据自动提取,内置多级医生工作台,细化工作将体检检查结果汇总,生成体检报告登记到计算机系统中。通过软件系统进行数据分析统计与评判以及建立体检相关的体检档案。从而实现体检流程的信息化,提高工作效率,减少手动结果录入的一些常犯错误。 在实际应用中,医院体检系统能够解决传统体检中手工操作带来的问题,如工作量大、效率低下、易漏检、重检或错检等。通过与医院信息系统(如HIS、LIS、PACS等)的连接,系统能够满足体检中心的日常工作流程,提供更好的管理、统计和查询分析功能。同时,基于网络基础的系统可以在网上传输
23 1
|
2月前
|
前端开发 JavaScript BI
C#区域医院云LIS信息管理系统源码
区域医院云LIS可协助区域内所有临床实验室相互协调并完成日常检验工作,对区域内的检验数据进行集中管理和共享,通过对质量控制的管理,最终实现区域内检验结果互认。其目标是以医疗服务机构为主体,以医疗资源和检验信息共享为目标,集成共性技术及医疗服务关键技术,建立区域协同检验。
33 2
|
1月前
|
C#
24. C# 编程:用户设定敌人初始血值的实现
24. C# 编程:用户设定敌人初始血值的实现
18 0
|
2月前
|
SQL 数据库连接 应用服务中间件
C#WinForm基础编程(三)
C#WinForm基础编程
71 0