清晰易懂TCP通信原理解析(附demo、简易TCP通信库源码、解决沾包问题等)C#版
目录
- 说明
- TCP与UDP通信的特点
- TCP中的沾包现象
- 自定义应用层协议
- TCPLibrary通信库介绍
- Demo演示
- 未完成功能
- 源码下载
说明
我前面博客中有多篇文章讲到了.NET中的网络编程,与TCP和UDP相关的有:
1.http://www.cnblogs.com/xiaozhi_5638/p/3167794.html
2.http://www.cnblogs.com/xiaozhi_5638/p/3169641.html
3.http://www.cnblogs.com/xiaozhi_5638/p/3290283.html
4.http://www.cnblogs.com/xiaozhi_5638/p/3313959.html
另外也有一些讲的是通过Socket模拟浏览器访问Web服务器,或者模拟Web服务器接收浏览器的请求:
1.http://www.cnblogs.com/xiaozhi_5638/p/3912668.html
2.http://www.cnblogs.com/xiaozhi_5638/p/3917943.html
(之前文章的排版不太好,不好意思!)
之所有对.NET中网络编程写得比较多,主要原因有两个,一是我公司做的项目多数跟通信这个有关;二是研究Socket通信工作模式有益于对软件架构设计的理解,因为它里面到处都使用到了“泵”结构,而这个结构几乎是所有框架、大型模块所必需具备的。另外,工作之余写的一本书(即将要出版)中有一章专门讲到了“泵”结构在软件系统中的作用。
这次写这篇文章主要是看了网上一个人提的有关TCP编程的问题,所以就再次整理了一下这方面的知识,并且做了一个“简易通信库”发出来给大家看看,代码很简单,功能也不是特别全,但是具备很好的扩展性,基本上可以用来说明.NET中TCP通信的工作模式。
TCP与UDP通信的特点
关于对这两者的比较,网上一搜一大片,讲得也比较清楚。TCP通信就像打电话,双方通信之前需要建立连接、双方就位后方可开始会话;而UDP通信就像发短信,一方给另一方发送数据前,并不需要对方就位。
上面两幅图显示了TCP与UDP通信过程建立的区别。
除了它们通信过程建立的不同之外,两者还有以下区别:
- TCP通信特点
1)可靠性;
通信双方均就位,一方发送数据,另一方收到后会做出回应,如果超时未发送成功,会自动重发,数据不会丢失。
2)顺序性;
既然数据是按顺序走在建立的一条隧道中,那么数据遵循“先走先达到”的规则,并且隧道中的数据以“流”的形式传输,发送方发送的前后两次数据之间没有边界,需要接收方自己根据事先规定好的“协议”去判断数据边界。
3)高损耗。
“高损耗”包括机器性能损耗高、宽带流量损耗高。因为通信双方时刻需要维持着连接的存在,这必然会损耗通信双方主机性能,要想维持隧道的通畅,通信双方必须不断地发送检测包和应答包,同时,它还支持数据重发等数据纠错功能,这些都将导致网络流量的增加。
- UDP通信特点
1)不可靠性;
既然无连接,发送方只管发送数据,而不管对方是否能够正确地接收到数据,更不负责数据超时重发等功能。
2)无序性;
数据以“数据报”的形式发送,可以把“数据报”看成是一个“包”。如果把TCP传输数据比如成“河里的流水”,那么UDP传输数据就是‘邮局寄信’。发送方先发送的数据可能后到达,后发送的数据可能先到达,这个跟短消息类似。
3)低损耗。
“低损耗”包括机器性能损耗低、宽带流量损耗低。UDP通信不需要维持一个连接的存在,所以它不需要消耗额外的机器性能。同时它也没有像TCP通信那样为了保持隧道的通畅,而必须不停地发送检测包和应答包,更不会进行一些数据检测纠错、重发等行为。
这次我们只讨论TCP通信。
TCP通信中的“沾包”现象
上面提到过,TCP通信中,数据是以“流”的形式传输的。前一次发送的数据和后一次发送的数据之间并没有明显的界限,这就会出现一个问题:当你收到一部分数据时,你无法判断接收到的数据是否是完整的?
如上图,发送方发送三次数据,而接收方可能一共分四次接收。并且每次接收到的数据量不确定(虽然每次收到的数据不确定,但是将四次接收到的数据拼接起来,与发送时的一致)。这样以来,当我们每次收到一份数据时,我们无法轻易判断(几乎不能)收到的数据是否完整(是否可以正确地被处理)。
以上现象我们称之为“沾包”。TCP通信过程中,要想解决“沾包”问题,我们必须人工采取一些措施,比如在发送数据时遵循一些“规则”,在接收到数据时,再按照相同的“规则”去解析数据,最终得到一份完整的数据,并进行正确的处理。没错,这里说的“规则”便是我们通常听到的“协议”。
关于协议,讲到的地方也很多。简单的说,协议就是一种“数据结构”,合作双方必须同时按照相同的数据结构发送/接收数据,比如传输层的TCP/UDP协议,又比如应用层的HTTP/FTP等协议。B/S结构系统使用到的协议见下图:
在TCP通信中,在发送和接收数据的时候,如果我们遵循事先定义的一种“协议”(属于一种应用层协议)。比如,在发送数据时,按照“数据头(4Byte)+内容长度(4Byte)+内容正文(NByte)+附加信息(8Byte)”这种形式去“格式化”需要发送的数据;同理,在接收到数据后,按照这种形式去“反格式化”数据,这样我们便可以判断数据边界,轻松得到一条完整数据。
自定义应用层协议
是的。我们自己完全可以定义一个类似HTTP这样的应用层协议,只要你能力足够强,系统足够大。今天在这里,我只举个简单的例子,假设一个TCP通信系统中,客户端连接上服务器后,客户端向服务器发送一个字符串,并发送一个字符串转换指令(比如大小写转换、除去特殊字符等指令),服务器接收到数据后,按照对应的指令,将字符串转换后发送回给客户端。那么这里的应用层协议可以这样设计:
字符串转换指令
序号 |
指令值(byte) |
说明 |
1 |
0x01 |
将字符串中小写字符转换成大写 |
2 |
0x02 |
将字符串中大写字符转换成小写 |
3 |
0x03 |
去掉字符串中的百分号(%)字符 |
4 |
0x04 |
将字符串中的百分号(%)替换为空格 |
如上表所示,假设一共有四种字符串转换请求,那么我们可以按下面图设计应用层协议的数据结构:
如上图所示,开头一个字节代表字符串转换指令类型,后续四个字节存放一个Int32的整型数据,表示字符串的长度(字符串采用Unicode编码),最后N个字节表示字符串内容。数据发送方必须按照此协议格式发送数据,数据接收方必须按照此协议格式接收数据。
发送数据时按照协议格式化数据很简单,但是,接收数据后,按照协议去解析数据该怎样呢?事实上,这个相对来讲稍微复杂一点。我们可以将每次接收到的数据(字节流)写入一个缓冲区,然后判断缓冲区中是否存在一条完整的数据,如果存在,则处理这条完整的数据;否则,继续接收数据,将接收到的数据再次写入缓冲区...以此循环。
TCPLibrary通信库介绍
其实我只是将一些代码单独拿出来生成了一个dll,这部分代码可以为我们搭建起TCP通信的框架,包括服务端侦听、(服务端/客户端)接收数据、上下线、消息处理并通知Application以及“沾包”问题处理等等。功能并不全面,如果要拿去实际项目中使用还需要自己完善,文章末会列出未完成的功能。
TCP通信过程建立之后,大概结构如下:
整个通信库中,只包含5个抽象类,以及5个默认实现类(所以说简易):
使用该通信库的前提是要定义好程序使用到的“协议”,然后重点实现ZMessage.RawData属性和ZDataBuffer.TryReadMessage方法,前者可以按照协议格式化需要发送的数据,后者可以按照协议解析一条完整的消息。库中包含5个默认实现类(以Base开头的),它默认使用以下的协议进行通信:
其中,BaseDataBuffer.TryReadMessage方法具体实现为:
/// <summary>
/// 按照规定协议,重写TryReadMessage方法
/// </summary>
/// <returns></returns>
internal override ZMessage TryReadMessage()
{
if (_length >= ) // 4 + 4 + N
{
using (MemoryStream ms = new MemoryStream(_buffer))
{
BinaryReader br = new BinaryReader(ms);
int msgtype = br.ReadInt32(); //读取消息类型
int msglength = br.ReadInt32(); //读取消息长度
if (_length - >= msglength) //如果缓冲区中存在一条完整消息,则读取
{
byte[] msgcontent = br.ReadBytes(msglength); //读取消息内容
BaseMessage bm = new BaseMessage(msgtype, msgcontent); //还原成一条完整的消息
Remove( + msglength); //注意! 移除已读数据 return bm; //返回读取到的消息
}
else
{
return null;
}
}
}
else
{
return null;
}
}
BaseMessage.RawData属性具体的实现为:
/// <summary>
/// 按照规定协议,重写RawData属性
/// </summary>
public override byte[] RawData
{
get
{
byte[] rawdata = new byte[ + + MsgContent.Length]; //消息类型 + 消息长度 + 消息内容
using (MemoryStream ms = new MemoryStream(rawdata))
{
BinaryWriter bw = new BinaryWriter(ms);
bw.Write(MsgType); //先写入MsgType
bw.Write(MsgContent.Length); //再写入MsgContent的长度
bw.Write(MsgContent); //最后写入消息内容
return rawdata;
}
}
}
可以看到,上面一个按照协议格式化数据,而另一个按照协议解析数据。它们两个完全遵守同一个协议。
Demo演示
使用TCPLibrary中的默认实现类(以Base开头的类型),我做了一个简单的Demo。该Demo可以完成字符串、可序列化对象(图片)的发送与接收。Demo源码很简单:
l Server端初始化:
private void Form1_Load(object sender, EventArgs e)
{
_server = new BaseServerSocket();
_server.Connected += new ConnectedEventHandler(_server_Connected);
_server.DisConnected += new DisConnectedEventHandler(_server_DisConnected);
_server.MessageReceived += new MessageReceivedEventHandler(_server_MessageReceived);
_server.StartAccept();
textBox1.AppendText("服¤t务?器¡Â启?动¡¥,ê?监¨¤听¬y端?口¨² " + + "...\r\n");
}
l Client端的初始化:
private void Form1_Load(object sender, EventArgs e)
{
_client = new BaseClientSocket();
_client.Connected += new ConnectedEventHandler(_client_Connected);
_client.DisConnected += new DisConnectedEventHandler(_client_DisConnected);
_client.MessageReceived += new MessageReceivedEventHandler(_client_MessageReceived);
_client.Connect("127.0.0.1",);
}
可以看到,使用起来很简单。注册事件后,既可以开始运行了。
下面可以看一下Demo截图:
注意,这个Demo只是利用库中的默认实现类来完成的。你完全可以自己定义一个协议,按照你自己的方式发送数据,比如“头(4Byte)+是否加密(1Byte)+发送方程序版本(8Byte)+消息长度(4Byte)+消息内容(NByte)+附加信息(8Byte)”这种方式发送数据/接收数据。只要你正确的实现了上面强调的方法和属性。
未完成功能
刚开始就说过,TCPLibrary功能不足,很多功能都没有。列举几个如下
1.线程安全
2.心跳检测
3.都只有开始,没有结束的功能
4.。。。
可以把源码下下来,自己尝试补充这些功能。
源码下载
下载地址:http://files.cnblogs.com/xiaozhi_5638/TCPDemo.rar
Win7+VS2010
希望有帮助!
清晰易懂TCP通信原理解析(附demo、简易TCP通信库源码、解决沾包问题等)C#版的更多相关文章
- C#版清晰易懂TCP通信原理解析(附demo)
[转] C#版清晰易懂TCP通信原理解析(附demo) (点击上方蓝字,可快速关注我们) 来源:周见智 cnblogs.com/xiaozhi_5638/p/4244797.html 对.NET中网络 ...
- kafka原理和实践(四)spring-kafka消费者源码
系列目录 kafka原理和实践(一)原理:10分钟入门 kafka原理和实践(二)spring-kafka简单实践 kafka原理和实践(三)spring-kafka生产者源码 kafka原理和实践( ...
- kafka原理和实践(三)spring-kafka生产者源码
系列目录 kafka原理和实践(一)原理:10分钟入门 kafka原理和实践(二)spring-kafka简单实践 kafka原理和实践(三)spring-kafka生产者源码 kafka原理和实践( ...
- Generator函数执行器-co函数库源码解析
一.co函数是什么 co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行.短小精悍只有短短200余行,就可以免去手动编写G ...
- andorid jar/库源码解析之Bolts
目录:andorid jar/库源码解析 Bolts: 作用: 用于链式执行跨线程代码,且传递数据 栗子: Task.call(new Callable<Boolean>() { @Ove ...
- andorid jar/库源码解析之EventBus
目录:andorid jar/库源码解析 EventBus: 作用: 用于不同Activity,Service等之间传递消息(数据). 栗子: A页面:onCreate定义 EventBus.ge ...
- andorid jar/库源码解析之Dagger/Dagger2
目录:andorid jar/库源码解析 Dagger.Dagger2: 作用: 1.用于解耦Activity和业务逻辑 2.在使用业务的时候,不需要重复编写new代码. 3.当业务变化的时候,不需要 ...
- andorid jar/库源码解析之okhttp3
目录:andorid jar/库源码解析 Okhttp3: 作用: 用于网络编程(http,https)的快速开发. 栗子: // okHttpClient定义成全局静态,或者单例,不然重复new可能 ...
- andorid jar/库源码解析之okio
目录:andorid jar/库源码解析 Okio: 作用: 说白了,就是一个IO库,基于java原生io.来进行操作,内部做了优化,简洁,高效.所以受到了一部分人的喜欢和使用 栗子: 读写文件. p ...
- andorid jar/库源码解析之retrofit2
目录:andorid jar/库源码解析 Retrofit2: 作用: 通过封装okhttp库,来进行web通讯,并且使用动态代理的方式,来调用接口地址,通过回调赋值结果. 栗子: 定义一个接口,用于 ...
随机推荐
- XMLFeedSpider例子
from scrapy import log from scrapy.contrib.spiders import XMLFeedSpider from myproject.items import ...
- 【Android】一种提高Android应用进程存活率新方法
[Android]一种提高Android应用进程存活率新方法 SkySeraph Jun. 19st 2016 Email:skyseraph00@163.com 更多精彩请直接访问SkySeraph ...
- Unity引擎IOS执行档大小优化
简介 苹果对于IOS执行档的大小是有明确的限制的,其中TEXT段的大小不能超过80M,否则提审将会被苹果拒绝,同时,如果TEXT段过于太大,那么在苹果进行加密之后,很容易出现解压失败等各种异常,最终导 ...
- View动画和属性动画
在应用中, 动画效果提升用户体验, 主要分为View动画和属性动画. View动画变换场景图片效果, 效果包括平移(translate), 缩放(scale), 旋转(rotate), 透明(alph ...
- Http、Https请求工具类
最近在做微信开发,使用http调用第三方服务API,有些是需要https协议,通过资料和自己编码,写了个支持http和https的工具类,经验证可用,现贴出来保留,也供需要的人使用(有不足的地方,也请 ...
- 【Hawk】高级教程——post参数采集万方医学网论文
目标——万方医学网论文列表 http://med.wanfangdata.com.cn/Author/General/A000000001 和普通网页不一样的地方在于点击下一页的时候,URL没有发生变 ...
- 使用极光/友盟推送,APP进程杀死后为什么收不到推送(转)
为什么会存在这样的 问题,刚开始的时候我也搞不清楚,之前用极光的时候杀死程序后也会收到推送,但最近重新再去集成时就完全不好使了,这我就纳闷了,虽然Google在高版本上的android上面不建议线程守 ...
- WPF中获取鼠标相对于桌面位置
var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice; var mouse ...
- 一致性hash算法详解
转载请说明出处:http://blog.csdn.net/cywosp/article/details/23397179 一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT) ...
- Synchronized同步性与可见性
Synchronized是具有同步性与可见性的,那么什么是同步性与可见性呢? (1)同步性:同步性就是一个事物要么一起成功,要么一起失败,可谓是有福同享有难同当,就像A有10000去银行转5000给身 ...