在网络中传输数据(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:\>
构造函数是特殊的方法,用在建立对象后初始化时。当建立引用类型的对象时,如果你不显式建立构造函数,系统也会建立一个默认的构造函数。
我们建立对象时的步骤:
1.分配内存:使用new 修饰符 在堆上分配内存
2.使用构造函数初始化对象
上面的2个步骤,实际只用1行代码即可以完成,如建立对象when:
详细说明以上2个步骤:
对于步骤1:给对象分配内存。所有的对象都使用修饰符new来建立,这个没有例外的。我们建立对象时可以显式的使用new,如果我们没有这样做,那么编译器会做这部分工作。
请看下面的代码和应用中完整的代码:
int[ ] array = {1,2,3,4}; int[ ] array = new int[4]{1,2,3,4};
- new 对性能的影响
一般来说,new在2个方面对性能有影响。
1.bool测试
堆,指的是特定大小的连续内存。有一个特定的指针标记当前内存分配的位置,指针的一端,内存已经被使用new分配完毕,另一端,还是可用的。Bool测试即指根据指针的当前位置和堆的末尾位置计算剩余的可用的内存数;然后会用这个数值同新的new请求的内存作比较。
2.指针向末尾移动
如果堆中剩余的内存大于new请求的内存,那么指针将移动到请求内存增加后的位置,标记这部分内存已被使用。分配的内存地址将被返回。
以上步骤将保证动态分配堆的内存同动态分配堆栈内存一样迅速。
注意:上面的说法只是对单个变量有效。对于分配多个变量,基于堆栈的变量可以一次性的分配,但是基于堆的变量需要多个步骤完成。
对于步骤2:使用构造函数初始化对象
构造函数获取使用new分配的内存。
存在2种构造函数:
实例的构造函数: 用来初始化对象的
静态的构造函数:用来初始化类自身的
new和构造函数实例是怎样协同工作的呢?
New的唯一目的是:获取原始的,没有初始化的内存。
构造函数实例的唯一目的是:初始化分配的内存并转换为可以使用的对象。
New不参与对象的初始化,而构造函数实例也不参与请求内存。
虽然new和构造函数实例起的作用不同,但是在程序中确实用一行代码实现的,不可以单独使用这2项功能。这是C#可以确保使用的内存的有效性的方法。
在C++中,允许单独分配内存而不初始化,也可以初始化曾将分配做的内存,这个在C#中不允许。
使用默认的构造函数:
默认构造函数的特点:
public的可访问性
名称同类名相同
没有返回类型
没有参数
将所有的字段初始化为false,zero 或者null
当你建立一个对象时,C# 编译器会建立一个默认的构造函数,如:
{
private int ccyy, mm, dd;
}
class Test
{
static void Main( )
{
Date when = new Date( );
}
}
上面的Test.Main()方法,建立了一个叫when的Date对象的实例,同时执行了Date实例的默认构造函数,虽然我们没有建立构造函数,没有初始化任何字段。因为编译器为我们建立了默认的构造函数,代码如下:
{
public Date( )
{
ccyy = 0;
mm = 0;
dd = 0;
}
private int ccyy, mm, dd;
}
可以看到:
Public的可访问性。
名称同类名相同,
没有返回类型,
没有参数,
所有字段初始化为0,对于编译器建立的构造函数,会初始化所有non-static字段,并赋初始值:
对于数值型字段,初始化为0;
对于Bool型字段,初始化为false;
引用类型初始化为null;
结构类型的字段初始化后,其所有元素的不包含数值。
重写默认构造函数:
默认的构造函数给所有字段赋初值,但是不见得是我们想要的,如出现下列状况:
1.不需要Public的可访问性:
如公式函数需要Private的构造函数,一些特殊模式的方法需要non-public的构造函数。
2.不需要基于0的字段初始值。
3.看不到的代码不易于维护。
我们来写字己的构造函数:
{
public int a, b;
public DefaultInit( )
{
a = 42;
// b retains default initia lization to zero
}
}
class Test
{
static void Main( )
{
DefaultInit di = new DefaultInit( );
Console.WriteLine(di.a); // Writes 42
Console.WriteLine(di.b); // Writes zero
}
}
对于处理构造函数中的异常,只可以使用thow new Exception方法。
重载构造函数:
构造函数也是一种方法,允许重载:
相同的使用范围,相同的名称,不同的参数;
允许使用多种方法初始化对象。
注意: 如果我们给类写了构造函数,那么编译器就不会再定义一个构造函数。
参考:MSDN Training
Introduction to C# Programming
for the Microsoft® .NET Platform
(Prerelease)
Workbook
Course Number: 2124A
现在存在很多企业级的数据库,如Oracle,SqlServer等等,功能强大,使用方便,唯一的缺点是 占用资源多,一般情况下,应该把一台计算机作为单独的数据库服务器,这不是所有人都能做到的。如果把应用程序,数据库装在同一台计算机,效率会低一些的。
那么使用简单的数据库工具可以解决这个问题,当然也可以使用其它解决方案,如xml文件,不过这里只讨论常见的数据库工具-Access。
本人使用Access的原因,基于处理一下问题:
服务器管理用户状态,使用web.config设置,存在3种方式:
<SessionState>
mode="Off":关闭用户Session管理;
mode="InProc":IIS管理用户Session;
mode="StateServer":可以在其它运行AspState Service的Win2000机器上管理;
mode="SqlServer":使用SqlServer数据库管理;
如果要维护用户状态,那么最后3种方式可以使用,不论是“InProc”还是“StateServer”,都要占用系统资源,对于少量信息维护,可以胜任,对于大量数据维护,“InProc”方式力不从心,“StateServer”方式的扩展性不好。
用数据库方式,可以管理海量数据,扩展性好,唯一不足是速度比其他稍慢。
微软提供了使用SqlServer管理Session的方法,前台将web.config的SessionState中的mode设为“SqlSerer”,后台的数据库脚本在/windows/Microsoft.NET\Framework\v1.0.3215\installsqlsate.sql获得,2者配合,可以使用。
不过不是所有人都使用SqlServer的,对于其他数据库,微软没有提供解决方法。微软的数据库脚本文件,我没有仔细看,因为对SqlServer不熟悉。
根据对Session的理解,自己写管理方法,数据库为常见的Access。
使用Access后,总结几点经验:
1.虽然Access小巧,但是使用起来比操作大型数据库,如Oracle, 麻烦的多,因为小巧的缘故吧。
2.Access的表中少用Date型字段,因为使用Asp.net 不好操作,没有找到对应的数据类型,解决办法是改为字符型的,然后使用CDate()作转换。
3.Access中可以使用存储过程(即查询) ,没有触发器;
关于使用查询:
使用的方法,同调用Oracle等大型数据库的存储过程相同,如:
if(Con.State==ConnectionState.Closed)
{
Con.Open();
}
OleDbCommand Cmd=Con.CreateCommand();
Cmd.CommandType=CommandType.StoredProcedure;
Cmd.CommandText="CheckTimeout"; //CheckTimeout是视图名
int count=Cmd.ExecuteNonQuery();
其中视图CheckTimeout的代码:
WHERE (((Now()-CDate([timeout]))>Minute(15)));
属于没有参数的,如果调用有参数的,Asp.Net 部分加入OleDbParameter,其他相同,
视图的代码可为:
DELETE *
FROM PersonInfo
WHERE [PersonInfo].[PersonID]=[PersonID];
其中PersonId为参数。
还在测试Session管理的代码,如果基本可用,将贴出来供大家讨论。
Microsoft的产品,入手简单,深入复杂,支持外部的简单的是后台严谨的复杂,我们常在开发Asp.Net程序时使用Session,但是知道Ms怎样处理Session的原理,恐怕知道的人不多,自己写一个Session处理,帮助自己理解Session。
如果一个系统中存在多个Web服务器,那么有一台服务器作为Session服务器,应该是分布式系统的一个好的选择。
我想把我的想法作为一个开源项目,对此感兴趣的所有人都可以阅读,测试,为了方便测试,选择了Access,如果此项目可用,那么可以容易的应用到其他数据库平台。
有了大家的帮助,我们可以进步更快。
关于数据库分类的概念,谢谢春鱼。
附Access的介绍(从其他网站摘录):
在办公软件Office套件中,最为广大用户熟悉的是Word和Excel,因为它们功能强大且方便易用,更因为它们不仅可用于办公,还可用于个人写作和家庭记帐理财等。同为Office套件中一部分的Access,虽然有着同样强大的功能,但使用的人却相对少些,不像Word和Excel那样广泛。事实上,真正用过Access的用户,对其强大功能和灵活应用均称赞“不错,很好的……。”
Access 数据库管理系统是Microsoft Office 套件的重要组成部分,是Access的最新版本,可在Windows 95环境下运行。Access适用于小型商务活动,用以存贮和管理商务活动所需要的数据。Access不仅是一个数据库,而且它具有强大的数据管理功能,它可以方便地利用各种数据源,生成窗体(表单),查询,报表和应用程序等。
什么是Access 数据库
数据库是有结构的数据集合,它与一般的数据文件不同,(其中的数据是无结构的)是一串文字或数字流。数据库中的数据可以是文字、图象、声音等。
Microsoft Access是一种关系式数据库,关系式数据库由一系列表组成,表又由一系列行和列组成,每一行是一个记录,每一列是一个字段,每个字段有一个字段名,字段名在一个表中不能重复。图1是一个“产品”表的例子。“产品”表由10个记录组成,一个记录占一行,每一个记录由产品ID、产品名称、库存量、订货量、单价和折扣率6个字段组成。“产品ID”是字段名,其下面的1,2等是字段的值。
表与表之间可以建立关系(或称关联,连接),以便查询相关联的信息。Access数据库以文件形式保存,文件的扩展名是MDB。
Access 的6种对象
Access 数据库由六种对象组成,它们是表、查询、窗体、报表、宏和模块。
表(Table) ——表是数据库的基本对象,是创建其他5种对象的基础。表由记录组成,记录由字段组成,表用来存贮数据库的数据,故又称数据表。
查询(Query)——查询可以按索引快速查找到需要的记录,按要求筛选记录并能连接若干个表的字段组成新表。
窗体(Form)——窗体提供了一种方便的浏览、输入及更改数据的窗口。还可以创建子窗体显示相关联的表的内容。窗体也称表单。
报表(Report)——报表的功能是将数据库中的数据分类汇总,然后打印出来,以便分析。
宏(Macro)——宏相当于DOS中的批处理,用来自动执行一系列操作。Access列出了一些常用的操作供用户选择,使用起来十分方便。
模块(Module)——模块的功能与宏类似,但它定义的操作比宏更精细和复杂,用户可以根据自己的需要编写程序。模块使用Visual Basic编程。
与Access 以前的版本比较,Access 新增了许多功能,字段类型增加了OLE对象和超级链接,特别是与Internet的融合,在数据库中可以直接链接到指定的Web页面或网络文件,也可以把Web页面上的表格导入到数据库。Access 可以方便地利用各种数据源,包括dBASE, FoxBase,FoxPro,Excel,Word 等。Access 增加了数据库访问的安全机制,可对表一级设置访问许可权。Access 还可以方便地利用FoxPro数据库、Excel电子表格的数据,还可以和Word混合使用,打印通用信函或信封。
MS ACCESS使用VBA語法來開發,在mdb的檔案格式中,提供資料與程式同時建置,或是利用資料庫分割,將程式與資料分隔,有
點類似主從架構,使用mdb最大的好處就是可以盡情使用ACCESS的參數,它可以節省寫SQL語法與過多的VBA程式,不過mdb的缺點
是比較適合單機作業,如果想多人使用資料庫,那麼就必須將資料轉換為SQL存取,只要分別在使用端設定ODBC來指向SQL SERVER
,這樣子就符合多人使用的目的與效果了!
在ACCESS 2000/XP中出現新的格式-ADP,ADP是直接連取SQL SERVER來做資料存取,速度當然比透過ODBC來的快,不過您必
須逐行撰寫 VBA與 SQL語法,在ACCESS 2000中必須安裝OFFICE 2000 SP1更新檔才可以直接存取SQL 2000,無論是MDB或ADP也
好,ACCESS最大的優點在於報表的製作,例如:做識別證,來賓證等,它簡單的報表設定,都是最具有親合力的操作,ACCESS的報表當
然也具有試算的能力,只要懂一點ACCESS的使用著都會自己調整報表列印位置,不必依賴程式設計者,給予使用者莫大的幫助。
浏览器访问WebPage时,虽然使用的地址不变,但是端口会发生变化,用刷新页面作测试,2分钟内刷新,用户的端口不变,超过2分钟,那么端口会发生变化。
Http1.1中说,Connection报头中有持久性连接控制,默认情况下,所有Http1.1连接都认为是持久性的,除非某个请求或者响应包括了一个Connection:close报头。这个持久性,指的好像不是维持相同的Tcp端口。
如果用Http协议维持会话,那么需要给客户端发送标记符SessionId,客户端再次访问时,提供这个标记,好被Server识别。这个标记存在形式是cookie或者查询字符串,要手工处理Session,需要一些功能来实现,带来如下问题:
要实现Access存储Session,那么WebApp的配置文件的
<SessionState>
mode="Off":关闭用户Session管理;
因为不了解IIS的Session管理机制。
那就需要知道怎样处理标记用户请求的SessionID,关于SessionId还在查资料,那位对此有经验,请给与支持。
SessionID:msdn中说,是长度为120Bit的ASC字符,可以在Url中使用,但是经过测试,
string id=Session.SessionID;
//显示Id的内容及长度
Response.Write(id+":"+id.Length.ToString()+"<br>");
byte[] by=System.Text.Encoding.ASCII.GetBytes(id);
Response.Write(by.Length.ToString()+"<br>");
使用Session.SessionID生成的SessionId长度为24个ASC字符,即192Bit,应该是原始的SessionID被加密了,长度发生了变化。