该文讲述通过网络传输序列化数据的两个类,BinaryFormatter和SoapFormatter类。这些类可以将类的实例转化成字节流通过网络传输到远程系统,也可以转换回原来的数据。
一、 使用序列化类
序列化一个类,并通过网络传输需要三步:
1、将要序列化的类创建成一个library对象。
2、编写一个发送程序来创建要序列化类的实例,并发送。
3、编写一个接收程序从流中读取数据,并重新创建原来的序列化类。
① 编写要序列化的类
每个要通过网络传输数据的类必须在原代码文件里使用[Serializable]标签。这表明,类中所有的数据在传输时都将要被序列化。下面展示了如何创建一个可以序列化的类。
using System;
[Serializable]
public class SerialEmployee
{
public int EmployeeID
public string LastName;
public string FirstName;
public int YearsService;
public double Salary;
public SerialEmployee()
{
EmployeeID = 0;
LastName = null;
FirstName = null;
YearsService = 0;
Salary = 0.0;
}
}
为了使用该类来传输数据,必须现创建一个library文件:
csc /t:library SerialEmployee.cs
② 编写一个传输程序
创建数据类以后,可以创建一个程序来传输数据。可以使用BinaryFormatter和SoapFormatter类来序列化数据。
BinaryFormatter将数据序列化为二进制流。通常在实际数据中,增加一些信息,例如类名和版本号信息。
也可以使用SoapFormatter类使用XML格式来传输数据。使用XML的好处就是可以在任何系统和程序间传递数据。
第一必须创建一个流的实例来传递数据。可以是任何类型的流,包括FileStream,MemoryStream,NetworkStream。然后,可以创建一个序列化类,使用Serialize()方法来通过流对象传递数据:
Stream str = new FileStream( "testfile.bin", FileMode.Create, FileAccess.ReadWrite);
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(str, data);
Iformatter类创建了一个用来序列化的类的实例(BinaryFormatter或者SoapFormatter),使用Serialize()类来将数据序列化
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;
class SoapTest
{
public static void Main()
{
SerialEmployee emp1 = new SerialEmployee();
SerialEmployee emp2 = new SerialEmployee();
emp1.EmployeeID = 1;
emp1.LastName = "Blum";
emp1.FirstName = "Katie Jane";
emp1.YearsService = 12;
emp1.Salary = 35000.50;
emp2.EmployeeID = 2;
emp2.LastName = "Blum";
emp2.FirstName = "Jessica";
emp2.YearsService = 9;
emp2.Salary = 23700.30;
Stream str = new FileStream("soaptest.XML", FileMode.Create,
FileAccess.ReadWrite);
IFormatter formatter = new SoapFormatter();
formatter.Serialize(str, emp1);
formatter.Serialize(str, emp2);
str.Close();
}
}
SoapFormatter类包含在System.Runtime.Serialization.Formatters.Soap命名空间,BinaryFormatter类包含在System.Runtime.Serialization.Formatters.Binary命名空间,Iformatter接口包含在System.Runtime.Serialization命名空间。
编译代码:CSC /r:SerialEmployee.dll SoapTest.cs
运行SoapTest.exe程序后,可以查看产生的soaptest.XML文件
XMLns:xsd="http://www.w3.org/2001/XMLSchema" XMLns:SOAP-ENC=  "http://schemas.XMLsoap.org/soap/encoding/" XMLns:SOAP-ENV=  "http://schemas.XMLsoap.org/soap/envelope/" XMLns:clr=  "http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle=  "http://schemas.XMLsoap.org/soap/encoding/"> "http://schemas.microsoft.com/clr/assem/SerialEmployee%2C%20Version%3D0. 0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
XMLns:xsd="http://www.w3.org/2001/XMLSchema" XMLns:SOAP-ENC=  "http://schemas.XMLsoap.org/soap/encoding/" XMLns:SOAP-ENV=  "http://schemas.XMLsoap.org/soap/envelope/" XMLns:clr=  "http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle=  "http://schemas.XMLsoap.org/soap/encoding/"> "http://schemas.microsoft.com/clr/assem/SerialEmployee%2C%20Version%3D0. 0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
查看soaptest.XML文件,我们可以发现在序列化类中SOAP是如何定义每个数据元素。一个值得注意的重要XML数据特点如下:
"http://schemas.microsoft.com/clr/assem/SerialEmployee%2C%20Version%3D0.Â0.0.0.%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull"> 这里,XML中定义的数据使用了序列化数据类的实际类名。如果接收程序使用了另一个不同的类名,会和从流中读取的XML数据不匹配。类不匹配,读取将会失败。 下面的代码展示了如何序列化数据,将数据传送到远程系统。 using System; using System.Net; using System.Net.Sockets; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; class BinaryDataSender { public static void Main() { SerialEmployee emp1 = new SerialEmployee(); SerialEmployee emp2 = new SerialEmployee(); emp1.EmployeeID = 1; emp1.LastName = "Blum"; emp1.FirstName = "Katie Jane"; emp1.YearsService = 12; emp1.Salary = 35000.50; emp2.EmployeeID = 2; emp2.LastName = "Blum"; emp2.FirstName = "Jessica"; emp2.YearsService = 9; emp2.Salary = 23700.30; TcpClient client = new TcpClient("127.0.0.1", 9050); IFormatter formatter = new BinaryFormatter(); NetworkStream strm = client.GetStream(); formatter.Serialize(strm, emp1); formatter.Serialize(strm, emp2); strm.Close(); client.Close(); } } 因为BinaryFormatter和SoapFormatter类需要一个Stream对象来传递序列化的数据,所以要使用一个TCP Socket对象或者一个TcpClient对象来传递数据,不能直接使用UDP。 ③编写一个接收程序 using System; using System.Net; using System.Net.Sockets; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; class BinaryDataRcvr { public static void Main() { TcpListener server = new TcpListener(9050); server.Start(); TcpClient client = server.AcceptTcpClient(); NetworkStream strm = client.GetStream(); IFormatter formatter = new BinaryFormatter(); SerialEmployee emp1 = (SerialEmployee)formatter.Deserialize(strm); Console.WriteLine("emp1.EmployeeID = {0}", emp1.EmployeeID); Console.WriteLine("emp1.LastName = {0}", emp1.LastName); Console.WriteLine("emp1.FirstName = {0}", emp1.FirstName); Console.WriteLine("emp1.YearsService = {0}", emp1.YearsService); Console.WriteLine("emp1.Salary = {0}\n", emp1.Salary); SerialEmployee emp2 = (SerialEmployee)formatter.Deserialize(strm); Console.WriteLine("emp2.EmployeeID = {0}", emp2.EmployeeID); Console.WriteLine("emp2.LastName = {0}", emp2.LastName); Console.WriteLine("emp2.FirstName = {0}", emp2.FirstName); Console.WriteLine("emp2.YearsService = {0}", emp2.YearsService); Console.WriteLine("emp2.Salary = {0}", emp2.Salary); strm.Close(); server.Stop(); } } 二、 程序改进 在前面的程序中有一个假设:发送者的所有数据都被接收者接收。如果数据丢失,调用Deserialize()方法会发生错误。一个简单的解决方法是将序列化数据放到MemoryStream对象中。MemoryStream对象将所有的序列化数据保存在内存中,可以很容易得到序列化数据的大小。当传递数据时,将数据大小和数据一起传递。 using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Soap; class BetterDataSender { public void SendData (NetworkStream strm, SerialEmployee emp) { IFormatter formatter = new SoapFormatter(); MemoryStream memstrm = new MemoryStream(); formatter.Serialize(memstrm, emp); byte[] data = memstrm.GetBuffer(); int memsize = (int)memstrm.Length; byte[] size = BitConverter.GetBytes(memsize); strm.Write(size, 0, 4); strm.Write(data, 0, memsize); strm.Flush(); memstrm.Close(); } public BetterDataSender() { SerialEmployee emp1 = new SerialEmployee(); SerialEmployee emp2 = new SerialEmployee(); emp1.EmployeeID = 1; emp1.LastName = "Blum"; emp1.FirstName = "Katie Jane"; emp1.YearsService = 12; emp1.Salary = 35000.50; emp2.EmployeeID = 2; emp2.LastName = "Blum"; emp2.FirstName = "Jessica"; emp2.YearsService = 9; emp2.Salary = 23700.30; TcpClient client = new TcpClient("127.0.0.1", 9050); NetworkStream strm = client.GetStream(); SendData(strm, emp1); SendData(strm, emp2); strm.Close(); client.Close(); } public static void Main() { BetterDataSender bds = new BetterDataSender(); } } 接收数据程序如下: using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Soap; class BetterDataRcvr { private SerialEmployee RecvData (NetworkStream strm) { MemoryStream memstrm = new MemoryStream(); byte[] data = new byte[4]; int recv = strm.Read(data, 0, 4); int size = BitConverter.ToInt32(data, 0); int offset = 0; while(size > 0) { data = new byte[1024]; recv = strm.Read(data, 0, size); memstrm.Write(data, offset, recv); offset += recv; size -= recv; } IFormatter formatter = new SoapFormatter(); memstrm.Position = 0; SerialEmployee emp = (SerialEmployee)formatter.Deserialize(memstrm); memstrm.Close(); return emp; } public BetterDataRcvr() { TcpListener server = new TcpListener(9050); server.Start(); TcpClient client = server.AcceptTcpClient(); NetworkStream strm = client.GetStream(); SerialEmployee emp1 = RecvData(strm); Console.WriteLine("emp1.EmployeeID = {0}", emp1.EmployeeID); Console.WriteLine("emp1.LastName = {0}", emp1.LastName); Console.WriteLine("emp1.FirstName = {0}", emp1.FirstName); Console.WriteLine("emp1.YearsService = {0}", emp1.YearsService); Console.WriteLine("emp1.Salary = {0}\n", emp1.Salary); SerialEmployee emp2 = RecvData(strm); Console.WriteLine("emp2.EmployeeID = {0}", emp2.EmployeeID); Console.WriteLine("emp2.LastName = {0}", emp2.LastName); Console.WriteLine("emp2.FirstName = {0}", emp2.FirstName); Console.WriteLine("emp2.YearsService = {0}", emp2.YearsService); Console.WriteLine("emp2.Salary = {0}", emp2.Salary); strm.Close(); server.Stop(); } public static void Main() { BetterDataRcvr bdr = new BetterDataRcvr(); } }

