上一篇中说到了“泵”在编程中的作用以及一些具体用处,但没有实际demo,可能不好理解,这篇文章我分享一个UDP通信的demo,大概实现了类似“飞鸽传书”在局域网中文本消息和文件传输的功能。功能不全也不是很完善,但足以说明“泵”在代码中的具体应用。

先来回忆一下上篇中“泵”的含义,首先它是可持续运作的,其次它可以将“数据”从一个地方传递到另外一个地方,供其他人使用。搬一张上篇的图:

图1 程序中“泵”结构图

如上图所示,每个泵有一个“待处理”的数据容器(缓冲区),泵循环里面还可以有一个“预处理”的地方,可以在数据被传递之前,先做处理,最后,泵会输出数据,供别人使用。完成“数据源”->“泵”->“使用者”这样一个过程,明显“泵”起到动力的作用。我们可以看见,“使用者”使用数据的过程包含在“泵循环”之内,也就是说,如果使用数据时,耗时太长,一次“泵循环”不能及时返回,那么,缓冲区中的数据就会累积,得不到及时处理,影响“泵”的工作效率。能解决该问题有两种途径:

  • 1)在使用数据时,不做长时间耗时操作;
  • 2)先不直接使用数据,先将“泵”传递出来的数据存入另外一个容器(缓冲区),再另外开辟线程去处理缓冲区中的数据。

其中1)明显治标不治本,不能解决实际问题,2)倒是可以一试,因为它将“使用数据”和“泵循环”分离开来,使“使用数据”不会直接影响到“泵”的工作。其实上一篇中说到“UDP通信结构”时就已经按照2)所说的实现的,我那时候说不能将数据处理、数据分析都放在“数据接收泵”中,因为有一个主要原因就是,那样做会影响数据接收的效率。后来我画了一张UDP通信结构图,里面用到了三个“泵”(数据接收泵、数据分析泵、数据处理泵),如下图:

图2 UDP通信中“泵”的使用

如上图,“数据分析泵”使用到了“数据接收泵”传递出来的数据,“数据处理泵”使用到了“数据分析泵”传递出来的数据,它们都没有直接使用,都是先将数据存入对应缓冲区,然后开辟新的“泵”(也是另开辟线程吧)去处理。这样一来,每个环节互不影响其他环节的工作效率。

在实现这个UDP通信demo时,我定义了三个“泵”类,一个数据发送类,和一个帮助类,分别如下:

  • UDPSocket:主要实现了数据接受泵,以及socket绑定等;
  • DataAnalyse:数据分析泵;
  • DataDeal:数据处理泵;
  • UDPSender:数据发送;
  • Help.cs中一些类型:辅助功能。

其中,DataAnalyse是UDPSocket中“泵”传递出来数据的使用者,DataDeal是DataAnalyse中“泵”传递出来数据的使用者,然后我们(开发者)是DataDeal中“泵”传递出来数据的使用者。

需要说明的是,默认请款下,DataDeal中的“泵”是不会传递出来任何数据,DataAnalyse中的“泵”也不会传递出来任何数据,我们需要根据具体需求,实现自己的DataDeal和DataAnalyse类,重写Deal和Analyse虚方法。我定义了两个类,分别为MyDataDeal和MyDataAnalyse,具体代码如下:

  1. /// <summary>
  2. /// 自定义数据处理类 重写Deal方法
  3. /// 负责处理某一些数据,其余交给基类
  4. /// 可以继续从MyDataDeal派生新的类,继续重写Deal方法
  5. /// </summary>
  6. public class MyDataDeal : DataDeal
  7. {
  8. public event LoginEventHandler Login;
  9. public event LoginBackEventHandler LoginBack;
  10. public event TxtMessageReceivedEventHandler TxtMessageReceived;
  11. public event TxtMessageBackEventHandler TxtMessageBack;
  12. public event LogoutEventHandler Logout;
  13. public event AskSendFileEventHandler AskSendFile;
  14. public event AskSendFileBackEventHandler AskSendFileBack;
  15. public event SendFileDataEventHandler SendFileData;
  16. public event ReceiveFileOKEventHandler ReceiveFileOK;
  17. public event SendFileStopEventHandler SendFileStop;
  18. public event ReceiveFileStopEventHandler ReceiveFileStop;
  19. public event SendFileDataBackEventHandler SendFileDataBack;
  20.  
  21. public MyDataDeal(DataAnalyse dataAnalyse)
  22. : base(dataAnalyse)
  23. {
  24.  
  25. }
  26. protected override void Deal(Data data)
  27. {
  28. switch (data.Msg)
  29. {
  30. case Msg.ZMsg1:
  31. {
  32. if (Login != null)
  33. {
  34. Login(data.RemoteIP, data.RemotePort, data.Content.ToString());
  35. }
  36. break;
  37. }
  38. case Msg.ZMsg2:
  39. {
  40. if (LoginBack != null)
  41. {
  42. LoginBack(data.RemoteIP, data.RemotePort, data.Content.ToString());
  43. }
  44. break;
  45. }
  46. case Msg.ZMsg3:
  47. {
  48. if (TxtMessageReceived != null)
  49. {
  50. TxtMessageReceived(data.RemoteIP, data.RemotePort, data.Content.ToString());
  51. }
  52. break;
  53. }
  54. case Msg.ZMsg4:
  55. {
  56. if(TxtMessageBack != null)
  57. {
  58. TxtMessageBack(data.RemoteIP, data.RemotePort, data.Content.ToString());
  59. }
  60. break;
  61. }
  62. case Msg.ZMsg5:
  63. {
  64. if (Logout != null)
  65. {
  66. Logout(data.RemoteIP, data.RemotePort, data.Content.ToString());
  67. }
  68. break;
  69. }
  70. case Msg.ZMsg6:
  71. {
  72. if (AskSendFile != null)
  73. {
  74. object[] objs = data.Content as object[];
  75. int uid = (int)objs[];
  76. long length = (long)objs[];
  77. string filename = objs[].ToString();
  78.  
  79. AskSendFile(data.RemoteIP, data.RemotePort, filename, length, uid);
  80. }
  81. break;
  82. }
  83. case Msg.ZMsg7:
  84. {
  85. if (AskSendFileBack != null)
  86. {
  87. object[] objs = data.Content as object[];
  88. int uid = (int)objs[];
  89. int result = (int)objs[];
  90. AskSendFileBack(data.RemoteIP, data.RemotePort, result, uid);
  91. }
  92. break;
  93. }
  94. case Msg.ZMsg8:
  95. {
  96. if (SendFileData != null)
  97. {
  98. object[] objs = data.Content as object[];
  99. int uid = (int)objs[];
  100. int part = (int)objs[];
  101. byte[] dat = objs[] as byte[];
  102. SendFileData(data.RemoteIP, data.RemotePort, dat, uid, part);
  103. }
  104. break;
  105. }
  106. case Msg.ZMsg9:
  107. {
  108. if (ReceiveFileOK != null)
  109. {
  110. int uid = (int)data.Content;
  111. ReceiveFileOK(data.RemoteIP, data.RemotePort, uid);
  112. }
  113. break;
  114. }
  115. case Msg.ZMsg10:
  116. {
  117. if (SendFileStop != null)
  118. {
  119. int uid = (int)data.Content;
  120. SendFileStop(data.RemoteIP, data.RemotePort, uid);
  121. }
  122. break;
  123. }
  124. case Msg.ZMsg11:
  125. {
  126. if (ReceiveFileStop != null)
  127. {
  128. int uid = (int)data.Content;
  129. ReceiveFileStop(data.RemoteIP, data.RemotePort, uid);
  130. }
  131. break;
  132. }
  133. case Msg.ZMsg12:
  134. {
  135. if (SendFileDataBack != null)
  136. {
  137. object[] objs = data.Content as object[];
  138. int uid = (int)objs[];
  139. int part = (int)objs[];
  140. SendFileDataBack(data.RemoteIP, data.RemotePort, uid, part);
  141. }
  142. break;
  143. }
  144. default:
  145. {
  146. base.Deal(data); //其他由基类处理
  147. break;
  148. }
  149. }
  150. }
  151. }
  152. public delegate void LoginEventHandler(string remoteIP,int remotePort,string remoteName);
  153. public delegate void LoginBackEventHandler(string remoteIP,int remotePort,string remoteName);
  154. public delegate void TxtMessageReceivedEventHandler(string remoteIP,int remotePort,string txtMsg);
  155. public delegate void TxtMessageBackEventHandler(string remoteIP,int remotePort,string txtMsgBack);
  156. public delegate void LogoutEventHandler(string remoteIP,int remotePort,string remoteName);
  157. public delegate void AskSendFileEventHandler(string remoteIP,int remotePort,string filename,long length,int uid);
  158. public delegate void AskSendFileBackEventHandler(string remoteIP,int remotePort,int result,int uid);
  159. public delegate void SendFileDataEventHandler(string remoteIP,int remotePort,byte[] data,int uid,int part);
  160. public delegate void ReceiveFileOKEventHandler(string remoteIP,int remotePort,int uid);
  161. public delegate void SendFileStopEventHandler(string remoteIP,int remotePort,int uid);
  162. public delegate void ReceiveFileStopEventHandler(string remoteIP,int remotePort,int uid);
  163. public delegate void SendFileDataBackEventHandler(string remoteIP,int remotePort,int uid,int part);

MyDataDeal

  1. /// <summary>
  2. /// 自定义数据分析类 重写Analyse方法
  3. /// 负责分析某一些数据,其余交给基类
  4. /// 可以继续从MyDataAnalyse派生新的类,继续重写Analyse方法
  5. /// </summary>
  6. public class MyDataAnalyse : DataAnalyse
  7. {
  8. public MyDataAnalyse(UDPSocket udpSocket)
  9. : base(udpSocket)
  10. {
  11.  
  12. }
  13. protected override void Analyse(RawData rawData)
  14. {
  15. string ip = (rawData.RemoteEP as IPEndPoint).Address.ToString(); //远端IP
  16. int port = (rawData.RemoteEP as IPEndPoint).Port; //远端端口
  17. if (rawData.Buffer.Length > )
  18. {
  19. Msg msg = (Msg)(rawData.Buffer[]); //头
  20. switch (msg)
  21. {
  22. case Msg.ZMsg1: //登录
  23. {
  24. string data = Encoding.Unicode.GetString(rawData.Buffer, , rawData.Length - ); //从1开始 因为前面有一个 头 下同,注意是 rawData.Length(实际接收数据) 不是rawData.Buffer.Length
  25. OnAnalysed(msg, data, ip, port);
  26. break;
  27. }
  28. case Msg.ZMsg2: //登录反馈
  29. {
  30. string data = Encoding.Unicode.GetString(rawData.Buffer, , rawData.Length - );
  31. OnAnalysed(msg, data, ip, port);
  32. break;
  33. }
  34. case Msg.ZMsg3: //文本信息
  35. {
  36. string data = Encoding.Unicode.GetString(rawData.Buffer, , rawData.Length - );
  37. OnAnalysed(msg, data, ip, port);
  38. break;
  39. }
  40. case Msg.ZMsg4: //文本信息反馈
  41. {
  42. string data = Encoding.Unicode.GetString(rawData.Buffer, , rawData.Length - );
  43. OnAnalysed(msg, data, ip, port);
  44. break;
  45. }
  46. case Msg.ZMsg5: //退出
  47. {
  48. string data = Encoding.Unicode.GetString(rawData.Buffer, , rawData.Length - );
  49. OnAnalysed(msg, data, ip, port);
  50. break;
  51. }
  52. case Msg.ZMsg6: //请求发送文件
  53. {
  54. int uid = BitConverter.ToInt32(rawData.Buffer, ); //文件唯一标示
  55. long length = BitConverter.ToInt64(rawData.Buffer, ); //文件大小
  56. string filename = Encoding.Unicode.GetString(rawData.Buffer, , rawData.Length - ); //文件名称
  57. OnAnalysed(msg, new object[] { uid, length, filename }, ip, port); //
  58. break;
  59. }
  60. case Msg.ZMsg7: //请求发送文件反馈
  61. {
  62. int uid = BitConverter.ToInt32(rawData.Buffer, ); //文件唯一标示
  63. int result = BitConverter.ToInt32(rawData.Buffer, ); //反馈结果
  64. OnAnalysed(msg, new object[] { uid, result }, ip, port);
  65. break;
  66. }
  67. case Msg.ZMsg8: //发送文件数据
  68. {
  69. int uid = BitConverter.ToInt32(rawData.Buffer, ); //文件唯一标示
  70. int part = BitConverter.ToInt32(rawData.Buffer, ); //段ID
  71. byte[] data = new byte[rawData.Length - ]; //实际文件数据
  72. Buffer.BlockCopy(rawData.Buffer, , data, , data.Length);
  73. OnAnalysed(msg, new object[] { uid, part, data }, ip, port);
  74. break;
  75. }
  76. case Msg.ZMsg9: //接收文件完毕
  77. {
  78. int uid = BitConverter.ToInt32(rawData.Buffer, ); //文件唯一标示
  79. OnAnalysed(msg, uid, ip, port);
  80. break;
  81. }
  82. case Msg.ZMsg10: //发送方中止发送文件
  83. {
  84. int uid = BitConverter.ToInt32(rawData.Buffer, ); //文件唯一标示
  85. OnAnalysed(msg, uid, ip, port);
  86. break;
  87. }
  88. case Msg.ZMsg11: //接收方中止接收文件
  89. {
  90. int uid = BitConverter.ToInt32(rawData.Buffer, ); //文件唯一标示
  91. OnAnalysed(msg, uid, ip, port);
  92. break;
  93. }
  94. case Msg.ZMsg12: //发送文件数据反馈
  95. {
  96. int uid = BitConverter.ToInt32(rawData.Buffer, ); //文件唯一标示
  97. int part = BitConverter.ToInt32(rawData.Buffer, ); //段ID
  98. OnAnalysed(msg, new object[] { uid, part }, ip, port);
  99. break;
  100. }
  101. default:
  102. {
  103. base.Analyse(rawData); //其他由基类分析
  104. break;
  105. }
  106. }
  107. }
  108. }
  109. }

MyDataAnalyse

如代码所示,各位下载源码后,可以将UDPSocket、UDPSender、DataDeal、DataAnalyse以及Help.cs文件中的一些类型封装起来,生成一个通用程序集,实际使用中,只需要引用该程序集,并且实现自己的DataDeal和DataAnalyse就行。

另外,demo中我还定义了一套自己的“通信协议”,如下:

  1. ZMsg1:用户新上线 头+用户信息
  2. ZMsg2:用户新上线反馈 头+反馈用户信息
  3. ZMsg3:文本消息 头+密码+消息正文
  4. ZMsg4:文本消息反馈 头+原始消息
  5. ZMsg5:离线 头+任意文本
  6. ZMsg6:请求发送文件 头+发送文件的唯一标示+大小+文件名
  7. ZMsg7:请求发送文件反馈 头+文件的唯一标示+10 1代表接收 0代表拒绝
  8. ZMsg8:发送文件数据 头+文件唯一标示+段ID+文件数据(byte[])
  9. ZMsg9:接收文件完毕 头+文件唯一标示
  10. ZMsg10:发送方中止发送文件 头+文件唯一标示
  11. ZMsg11:接收方中止接收文件 头+文件唯一标示
  12. ZMsg12:发送文件数据反馈 头+文件唯一标示+段ID

消息协议

通信协议根据具体需求而不同。

总结一下,在其他项目中具体使用步骤如下:

  1. 根据具体需求,定义一套“消息协议”;
  2. 从DataAnalyse派生出一个新的类,根据“消息协议”重写Analyse方法,进行数据分析;
  3. 从DataDeal派生出一个新的类,根据具体业务逻辑重写Deal方法,进行数据处理;
  4. 数据发送方必须严格按照“消息协议”发送数据;
  5. 最后,你要做的就是注册DataDeal派生类的一些事件,使用“泵”传递出来的程序可识别数据。

源码下载地址:http://files.cnblogs.com/xiaozhi_5638/UDPMessager.rar

希望对各位有帮助。附几张效果图

.Net开发笔记(十四) 基于“泵”的UDP通信(接上篇)的更多相关文章

  1. 《C++游戏开发》笔记十四 平滑过渡的战争迷雾(二) 实现:真正的迷雾来了

    本系列文章由七十一雾央编写,转载请注明出处.  http://blog.csdn.net/u011371356/article/details/9712321 作者:七十一雾央 新浪微博:http:/ ...

  2. python3.4学习笔记(十四) 网络爬虫实例代码,抓取新浪爱彩双色球开奖数据实例

    python3.4学习笔记(十四) 网络爬虫实例代码,抓取新浪爱彩双色球开奖数据实例 新浪爱彩双色球开奖数据URL:http://zst.aicai.com/ssq/openInfo/ 最终输出结果格 ...

  3. zlib开发笔记(四):zlib库介绍、编译windows vs2015x64版本和工程模板

    前言   Qt使用一些压缩解压功能,介绍过libzip库编译,本篇说明zlib库.需要用到zlib的msvc2015x64版本,编译一下.   版本编译引导 zlib在windows上的mingw32 ...

  4. QQ 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件

    QQ 编辑 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件.腾讯QQ支持在线聊天.视频通话.点对点断点续传文件.共享文件.网络硬盘.自定义面板.QQ邮箱等多种功 ...

  5. Java开发笔记(四十五)成员属性与成员方法

    前面介绍了许多数据类型,除了基本类型如整型int.双精度型double.布尔型boolean之外,还有高级一些的如包装整型Integer.字符串类型String.本地日期类型LocalDate等等,那 ...

  6. Java开发笔记(四十)日期与字符串的互相转换

    前面介绍了如何通过Date工具获取各个时间数值,但是用户更喜欢形如“2018-11-24 23:04:18”这种结构清晰.简洁明了的字符串,而非啰里八唆依次汇报每个时间单位及其数值的描述.既然日期时间 ...

  7. Java开发笔记(四十二)日历工具的常见应用

    前面介绍了日历工具Calendar的基本用法,乍看起来Calendar与Date两个半斤八两,似乎没有多大区别,那又何苦庸人自扰鼓捣一个新玩意呢?显然这样小瞧了Calendar,其实它的作用大着呢,接 ...

  8. Java开发笔记(四十八)类的简单继承

    前面介绍了类的基本用法,主要是如何封装一个类的各项要素,包括成员属性.成员方法.构造方法等,想必大家对类的简单运用早已驾轻就熟.所谓“物以类聚,人以群分”,之所以某些事物会聚在一起,乃是因为它们拥有类 ...

  9. Java开发笔记(四十四)本地日期时间与字符串的互相转换

    之前介绍Calendar的时候,提到日历实例无法直接输出格式化后的时间字符串,必须先把Calendar类型转换成Date类型,再通过格式化工具SimpleDateFormat获得字符串.而日期时间的格 ...

随机推荐

  1. C 标准库系列之errno.h

    errno.h 提供了一个整数全局变量errno,当系统调用或者库函数的错误事件发生时可能会修改该值,指明错误的原因,该值可在任何需要的地方被修改:一般情况不为0的值表示出现了异常或者错误. errn ...

  2. Codeforces CF#628 Education 8 A. Tennis Tournament

    A. Tennis Tournament time limit per test 1 second memory limit per test 256 megabytes input standard ...

  3. 栈的JS实现

    栈,是一种特殊的线性表,其插入及删除的操作都在线性表的同一端进行.这一端称为栈顶,另一端称为栈底.就类似于餐厅里的一摞盘子,后放的盘子在上方,也会先被人拿走.栈具有"后进先出"的逻 ...

  4. VS2013开启滚动条缩略图和双击选中高亮,效果杠杠滴!

    1.双击代码或选中代码高亮,用以下插件,反应很灵敏,我安装的是第三个 2.代码编辑器的滚动条缩略图是VS自带的,需要打开菜单----工具----选项,如下图设置: 3.VS默认的选中颜色,需要打开菜单 ...

  5. RESTFUL Architecture

    Just review some articles about RESTFUL stuff, my understanding is RESTFUL is another more general v ...

  6. AppDomain对于静态对象的独享引用

    AppDomain可以理解为一个独立的沙箱,当有独立的第静态对象在appDomain中被访问时,会在appDomain中产生独立的内存对象.比如appDomain1 appDomain2同时对 静态对 ...

  7. 新入门node.js必须要知道的概念

    一.对于一个刚入门node.js的朋友来说,一定要了解一些基础概念: 今年我正式进入社会后,发现自己所知道的IT方面的知识,真的只是牛毛,原来人外有人,山外有山,还需要继续努力.下面是一些我的自学习心 ...

  8. 优化curl并发使用

    经典curl并发的处理流程:首先将所有的URL压入并发队列, 然后执行并发过程, 等待所有请求接收完之后进行数据的解析等后续处理. 在实际的处理过程中, 受网络传输的影响, 部分URL的内容会优先于其 ...

  9. Android 怎么退出整个应用程序?

    方法一: 我们在写android应用程序时,经常会遇到想退出当前Acitivity,或者直接退出应用程序.我之前的一般操作是按返回键,或者直接按home键直接返回,其实这两种操作都没有关闭当前应用程序 ...

  10. OpenCV 3.1 Set Camera Resolution 设置相机的分辨率

    在OpenCV中,有强大的处理相机事件的api,但是貌似没有直接获取相机的最大分辨率的函数,通过属性CV_CAP_PROP_FRAME_HEIGHT和CV_CAP_PROP_FRAME_WIDTH直接 ...