** 数据移动,序列化 ** ** **
该文讲述通过网络传输序列化数据的两个类, 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 文件
1<soap-env:envelope "="" "http:="" 1.0"="" clr="" encoding="" envelope="" schemas.microsoft.com="" schemas.xmlsoap.org="" soap="" soap-env:encodingstyle="Â" xmlns:clr="Â" xmlns:soap-enc="Â" xmlns:soap-env="Â" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" â="">
2<soap-env:body>
3<a1:serialemployee "http:="" 0.0.0%2c%20culture%3dneutral%2c%20publickeytoken%3dnull"="" assem="" clr="" id="ref-1" schemas.microsoft.com="" serialemployee%2c%20version%3d0.â="" xmlns:a1="Â">
4<employeeid>1</employeeid>
5<lastname id="ref-3">Blum</lastname>
6<firstname id="ref-4">Katie Jane</firstname>
7<yearsservice>12</yearsservice>
8<salary>35000.5</salary>
9</a1:serialemployee>
10</soap-env:body>
11</soap-env:envelope>
1<soap-env:envelope "="" "http:="" 1.0"="" clr="" encoding="" envelope="" schemas.microsoft.com="" schemas.xmlsoap.org="" soap="" soap-env:encodingstyle="Â" xmlns:clr="Â" xmlns:soap-enc="Â" xmlns:soap-env="Â" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" â="">
2<soap-env:body>
3<a1:serialemployee "http:="" 0.0.0%2c%20culture%3dneutral%2c%20publickeytoken%3dnull"="" assem="" clr="" id="ref-1" schemas.microsoft.com="" serialemployee%2c%20version%3d0.â="" xmlns:a1="Â">
4<employeeid>2</employeeid>
5<lastname id="ref-3">Blum</lastname>
6<firstname id="ref-4">Jessica</firstname>
7<yearsservice>9</yearsservice>
8<salary>23700.3</salary>
9</a1:serialemployee>
10</soap-env:body>
11</soap-env:envelope>
查看 soaptest.xml 文件,我们可以发现在序列化类中 SOAP 是如何定义每个数据元素。一个值得注意的重要 XML 数据特点如下:
1<a1:serialemployee "http:="" assem="" clr="" id="ref-1" schemas.microsoft.com="" serialemployee%2c%20version%3d0.â0.0.0.%2c%20culture%3dneutral%2c%20publickeytoken%3dnull"="" xmlns:a1="Â">
2
3这里, XML 中定义的数据使用了序列化数据类的实际类名。如果接收程序使用了另一个不同的类名,会和从流中读取的 XML 数据不匹配。类不匹配,读取将会失败。
4
5下面的代码展示了如何序列化数据,将数据传送到远程系统。
6
7
8 using System;
9
10
11 using System.Net;
12
13
14 using System.Net.Sockets;
15
16
17 using System.Runtime.Serialization;
18
19
20 using System.Runtime.Serialization.Formatters.Binary;
21
22
23 class BinaryDataSender
24
25
26 {
27
28
29 public static void Main()
30
31
32 {
33
34
35 SerialEmployee emp1 = new SerialEmployee();
36
37
38 SerialEmployee emp2 = new SerialEmployee();
39
40
41 emp1.EmployeeID = 1;
42
43
44 emp1.LastName = "Blum";
45
46
47 emp1.FirstName = "Katie Jane";
48
49
50 emp1.YearsService = 12;
51
52
53 emp1.Salary = 35000.50;
54
55
56 emp2.EmployeeID = 2;
57
58
59 emp2.LastName = "Blum";
60
61
62 emp2.FirstName = "Jessica";
63
64
65 emp2.YearsService = 9;
66
67
68 emp2.Salary = 23700.30;
69
70
71 TcpClient client = new TcpClient("127.0.0.1", 9050);
72
73
74 IFormatter formatter = new BinaryFormatter();
75
76
77 NetworkStream strm = client.GetStream();
78
79
80 formatter.Serialize(strm, emp1);
81
82
83 formatter.Serialize(strm, emp2);
84
85
86 strm.Close();
87
88
89 client.Close();
90
91
92 }
93
94
95 }
96
97因为 BinaryFormatter 和 SoapFormatter 类需要一个 Stream 对象来传递序列化的数据,所以要使用一个 TCP Socket 对象或者一个 TcpClient 对象来传递数据,不能直接使用 UDP 。
98
99** ③编写一个接收程序 ** ** **
100
101
102 using System;
103
104
105 using System.Net;
106
107
108 using System.Net.Sockets;
109
110
111 using System.Runtime.Serialization;
112
113
114 using System.Runtime.Serialization.Formatters.Binary;
115
116
117 class BinaryDataRcvr
118
119
120 {
121
122
123 public static void Main()
124
125
126 {
127
128
129 TcpListener server = new TcpListener(9050);
130
131
132 server.Start();
133
134
135 TcpClient client = server.AcceptTcpClient();
136
137
138 NetworkStream strm = client.GetStream();
139
140
141 IFormatter formatter = new BinaryFormatter();
142
143
144 SerialEmployee emp1 = (SerialEmployee)formatter.Deserialize(strm);
145
146
147 Console.WriteLine("emp1.EmployeeID = {0}", emp1.EmployeeID);
148
149
150 Console.WriteLine("emp1.LastName = {0}", emp1.LastName);
151
152
153 Console.WriteLine("emp1.FirstName = {0}", emp1.FirstName);
154
155
156 Console.WriteLine("emp1.YearsService = {0}", emp1.YearsService);
157
158
159 Console.WriteLine("emp1.Salary = {0}\n", emp1.Salary);
160
161
162 SerialEmployee emp2 = (SerialEmployee)formatter.Deserialize(strm);
163
164
165 Console.WriteLine("emp2.EmployeeID = {0}", emp2.EmployeeID);
166
167
168 Console.WriteLine("emp2.LastName = {0}", emp2.LastName);
169
170
171 Console.WriteLine("emp2.FirstName = {0}", emp2.FirstName);
172
173
174 Console.WriteLine("emp2.YearsService = {0}", emp2.YearsService);
175
176
177 Console.WriteLine("emp2.Salary = {0}", emp2.Salary);
178
179
180 strm.Close();
181
182
183 server.Stop();
184
185
186 }
187
188}
189
190二、 程序改进
191
192在前面的程序中有一个假设:发送者的所有数据都被接收者接收。如果数据丢失,调用 Deserialize() 方法会发生错误。一个简单的解决方法是将序列化数据放到 MemoryStream 对象中。 MemoryStream 对象将所有的序列化数据保存在内存中,可以很容易得到序列化数据的大小。当传递数据时,将数据大小和数据一起传递。
193
194
195 using System;
196
197
198 using System.IO;
199
200
201 using System.Net;
202
203
204 using System.Net.Sockets;
205
206
207 using System.Runtime.Serialization;
208
209
210 using System.Runtime.Serialization.Formatters.Soap;
211
212
213 class BetterDataSender
214
215
216 {
217
218
219 public void SendData (NetworkStream strm, SerialEmployee emp)
220
221
222 {
223
224
225 IFormatter formatter = new SoapFormatter();
226
227
228 MemoryStream memstrm = new MemoryStream();
229
230
231 formatter.Serialize(memstrm, emp);
232
233
234 byte[] data = memstrm.GetBuffer();
235
236
237 int memsize = (int)memstrm.Length;
238
239
240 byte[] size = BitConverter.GetBytes(memsize);
241
242
243 strm.Write(size, 0, 4);
244
245
246 strm.Write(data, 0, memsize);
247
248
249 strm.Flush();
250
251
252 memstrm.Close();
253
254
255 }
256
257
258 public BetterDataSender()
259
260
261 {
262
263
264 SerialEmployee emp1 = new SerialEmployee();
265
266
267 SerialEmployee emp2 = new SerialEmployee();
268
269
270 emp1.EmployeeID = 1;
271
272
273 emp1.LastName = "Blum";
274
275
276 emp1.FirstName = "Katie Jane";
277
278
279 emp1.YearsService = 12;
280
281
282 emp1.Salary = 35000.50;
283
284
285 emp2.EmployeeID = 2;
286
287
288 emp2.LastName = "Blum";
289
290
291 emp2.FirstName = "Jessica";
292
293
294 emp2.YearsService = 9;
295
296
297 emp2.Salary = 23700.30;
298
299
300 TcpClient client = new TcpClient("127.0.0.1", 9050);
301
302
303 NetworkStream strm = client.GetStream();
304
305
306 SendData(strm, emp1);
307
308
309 SendData(strm, emp2);
310
311
312 strm.Close();
313
314
315 client.Close();
316
317
318 }
319
320
321 public static void Main()
322
323
324 {
325
326
327 BetterDataSender bds = new BetterDataSender();
328
329
330 }
331
332
333 }
334
335接收数据程序如下:
336
337
338 using System;
339
340
341 using System.IO;
342
343
344 using System.Net;
345
346
347 using System.Net.Sockets;
348
349
350 using System.Runtime.Serialization;
351
352
353 using System.Runtime.Serialization.Formatters.Soap;
354
355
356 class BetterDataRcvr
357
358
359 {
360
361
362 private SerialEmployee RecvData (NetworkStream strm)
363
364
365 {
366
367
368 MemoryStream memstrm = new MemoryStream();
369
370
371 byte[] data = new byte[4];
372
373
374 int recv = strm.Read(data, 0, 4);
375
376
377 int size = BitConverter.ToInt32(data, 0);
378
379
380 int offset = 0;
381
382
383 while(size > 0)
384
385
386 {
387
388
389 data = new byte[1024];
390
391
392 recv = strm.Read(data, 0, size);
393
394
395 memstrm.Write(data, offset, recv);
396
397
398 offset += recv;
399
400
401 size -= recv;
402
403
404 }
405
406
407 IFormatter formatter = new SoapFormatter();
408
409
410 memstrm.Position = 0;
411
412
413 SerialEmployee emp = (SerialEmployee)formatter.Deserialize(memstrm);
414
415
416 memstrm.Close();
417
418
419 return emp;
420
421
422 }
423
424
425 public BetterDataRcvr()
426
427
428 {
429
430
431 TcpListener server = new TcpListener(9050);
432
433
434 server.Start();
435
436
437 TcpClient client = server.AcceptTcpClient();
438
439
440 NetworkStream strm = client.GetStream();
441
442
443 SerialEmployee emp1 = RecvData(strm);
444
445
446 Console.WriteLine("emp1.EmployeeID = {0}", emp1.EmployeeID);
447
448
449 Console.WriteLine("emp1.LastName = {0}", emp1.LastName);
450
451
452 Console.WriteLine("emp1.First</a1:serialemployee>