在网络中传输数据(I)
我们都曾经出去旅游,并且会带回一些纪念品。一般情况,这些纪念品可以放在随身带的旅行包中带回家,甚至因为纪念品足够小,可以放在口袋里带回来。如果你到巴黎旅行,看到埃菲尔铁塔,觉得非常壮观,你很想同你的朋友分享,那么拍下照片,寄给朋友。
假设一切允许,法国政府允许你把埃菲尔铁塔带回你的国家,展览数月,那么你怎样带回去呢?
不要告诉我说,找世界上最大的船王给你造一艘可以将埃菲尔铁塔整个装下的超级货船,因为即使可以造出这艘船,你还需要一辆超级卡车,一个超级起重机,一条超级道路,才可以把埃菲尔铁塔整个从市区运送到码头。
很明显,你不需要超级货船,更不需要超级卡车,只需要绘制铁塔结构图,给组成埃菲尔铁塔的每根钢铁骨架编号,记录,再逐个拆开,用集装箱运送到码头,装上货船,运送到你的国家,根据结构图,按照编号,逐个钢铁骨架组装起来就可以了。(说起来简单,做起来复杂,不过确实可行,记得埃及人为了建阿斯旺水坝,就是用这种方法把古代神庙作了搬迁)。
不要搬迁埃菲尔铁塔,如果有兴趣可以到巴黎参观,因为它是法国文化的一部分;不过我们要面对网络传输数据,可能是很大的数据,因为这是网络文化的一部分。
我们经常同网络另一端的朋友使用聊天工具聊天,分享照片,发送文件,在网络中,传输的并不是聊天的信息,照片和文件,而是网络可以传输的基本单位-字节(byte),尽管实质是Bit,但是我们通常指的网络传输基本单位是字节(8Bit)。
说明一个概念,我们常说的百兆网络,指得是百兆Bit,即100/8=12.5M Byte,所以我们常在局域网发送文件,却看到传输速度最高也就7-8M,这是因为你的文件大小的单位是Byte,百兆网络传输的理论值最高值为12.5M Byte,取除干扰,衰减以及网络传输的控制信息,用来传送数据的有效带宽肯定低于理论值。
我们通过网络发送的任何东西,都是使用字节传输的,为了描述方便,使用数组的概念,即传送文件,实际就是传送字节数组,因为文件的基本组成单位就是字节数组。如果要接收方能够正常使用网络传送的文件,那么需要同运送埃菲尔铁塔一样,要绘制结构图,给传输的基本单位编号,因为计算机系统遵循特定的规则,不需要你绘制结构图了,只需要给传输的基本单位编号就可以了。
对于现在的网络世界,Tcp协议是经常用到的协议,Tcp可以确保网络传输数据的有序性,可靠性,那么你的工作就更加简单了,只需要经文件转换为字节数组,送到网络传输,有接收方把收到的字节组装起来就可以了。
下面说一下将各种数据类型转换为字节数组的方法:
BitConverter类可以将数值型数据转换为字节数组,同样可以将字节数组装换位数值。
//把test转化为byte[]
byte [] data = BitConverter.GetBytes(test);
//将byte[]转换为Int16
说明:BitConverter类存在ToString(),但是使用它的结果,可能同你预想的不同。
//
string test = BitConverter.ToString(Encoding.ASCII.GetBytes(data));
Console.WriteLine( " data = '{0}' " , data);
Console.WriteLine( " test = '{0}' " , test);
结果:
C:\>test
data = 'this is a test'
test = '74-68-69-73-20-69-73-20-61-20-74-65-73-74'
C:\>
BitConvert.ToString()是把字节数组中的值用16进制显示出来,所以如果要显示文本,要用Encoding.ASCII.GetString()方法。
如果在2台基于Intel处理器,Ms Windows操作系统的机器上传送数值型数据,那么是没有问题的;但是如果传送给其他计算机,就不见得没有问题。
因为BitConvert.GetBytes()方法是把数值转换为字节并按照一定次序放入数组,这个次序同处理器和操作系统有关。
这个问题同CPU存储二进制数据不同有关,字节数组的存储有2种形式:
低位优先:先存放不重要的数据
高位优先:先存放重要的数据
所以对于相同系统的计算机,处理数值型字节数组没有问题,而对于不同系统的计算机,就会带来问题。
//
using System.Net;
using System.Text;
class BinaryDataTest
{
public static void Main()
{
int test1 = 45;
double test2 = 3.14159;
int test3 = -1234567890;
bool test4 = false;
byte[] data = new byte[1024];
string output;
data = BitConverter.GetBytes(test1);
output = BitConverter.ToString(data);
Console.WriteLine("test1 = {0}, string = {1}", test1, output);
data = BitConverter.GetBytes(test2);
output = BitConverter.ToString(data);
Console.WriteLine("test2 = {0}, string = {1}", test2, output);
data = BitConverter.GetBytes(test3);
output = BitConverter.ToString(data);
Console.WriteLine("test3 = {0}, string = {1}", test3, output);
data = BitConverter.GetBytes(test4);
output = BitConverter.ToString(data);
Console.WriteLine("test4 = {0}, string = {1}", test4, output);
}
}
结果:
C:\>BinaryDataTest
test1 = 45, string = 2D-00-00-00
test2 = 3.14159, string = 6E-86-1B-F0-F9-21-09-40
test3 = -1234567890, string = 2E-FD-69-B6
test4 = False, string = 00
C:\>
可以看到,执行这个程序的计算机是低位优先的,因为字节放入数组的次序是先0,后2D,先存放的是不重要的数据;如果是高位优先的系统,那么应该是00-00-00-2D。
这个问题在Unix世界非常明显,因为运行Unix系统的计算机种类非常多,你不能保证本机处理数值型数据同远程计算机相同。
解决办法:使用相同类型的存储格式传输数值型数据。
网络字节顺序,即高位优先的定义,要求所有网络传输的数值型数据都要按照相同标准。这样,即使不同平台的计算机也可以通过网络互相交换数值型数据而不会有错误。
.Net 提供了HostToNetworkOrder()可以将本机的数值型数据的数组格式转换为网络字节顺序。
NetworkToHost()可以把网络字节顺序的数组转换为本机数值型数据字节数据。
//
using System.Net;
using System.Text;
class BinaryNetworkByteOrder
{
public static void Main()
{
short test1 = 45;
int test2 = 314159;
long test3 = -123456789033452;
byte[] data = new byte[1024];
string output;
data = BitConverter.GetBytes(test1);
output = BitConverter.ToString(data);
Console.WriteLine("test1 = {0}, string = {1}", test1, output);
data = BitConverter.GetBytes(test2);
output = BitConverter.ToString(data);
Console.WriteLine("test2 = {0}, string = {1}", test2, output);
data = BitConverter.GetBytes(test3);
output = BitConverter.ToString(data);
Console.WriteLine("test3 = {0}, string = {1}", test3, output);
short test1b = IPAddress.HostToNetworkOrder(test1);
data = BitConverter.GetBytes(test1b);
output = BitConverter.ToString(data);
Console.WriteLine("test1 = {0}, nbo = {1}", test1b, output);
int test2b = IPAddress.HostToNetworkOrder(test2);
data = BitConverter.GetBytes(test2b);
output = BitConverter.ToString(data);
Console.WriteLine("test2 = {0}, nbo = {1}", test2b, output);
long test3b = IPAddress.HostToNetworkOrder(test3);
data = BitConverter.GetBytes(test3b);
output = BitConverter.ToString(data);
Console.WriteLine("test3 = {0}, nbo = {1}", test3b, output);
}
}
//
C:\>BinaryNetworkByteOrder
test1 = 45, string = 2D-00
test2 = 314159, string = 2F-CB-04-00
test3 = -123456789033452, string = 14-CE-F1-79-B7-8F-FF-FF
test1 = 11520, nbo = 00-2D
test2 = 801833984, nbo = 00-04-CB-2F
test3 = 1499401231033958399, nbo = FF-FF-8F-B7-79-F1-CE-14
C:\>