Hướng dẫn python serialize deserialize - python serialize deserialize
Trong bài học này chúng ta sẽ xem xét khái niệm và các kỹ thuật trình tự hóa dữ liệu (object serialization) nhằm chuyển đổi object về dạng trung gian phục vụ cho việc truyền dữ liệu qua mạng. Show Trong tất cả các bài tập ví dụ từ đầu đến giờ chúng ta đều chỉ truyền đi các chuỗi ký tự. Tuy nhiên, trong hầu hết các project, chúng ta gặp phải một vấn đề khác: truyền một object hoặc danh sách các object từ tiến trình này tới tiến trình khác. Để thực hiện việc truyền các object phức tạp, chúng ta phải sử dụng đến các công cụ giúp trình tự hóa object (object serialization). Khái niệm SerializationQuá trình chuyển đổi một object về dạng trung gian để lưu trữ hoặc truyền thông được gọi là trình tự hóa dữ liệu (serialization); Quá trình khôi phục lại object từ dạng trung gian được gọi là giải trình tự hóa (deserialization). Trong lập trình socket, serialization là giai đoạn chuẩn bị dữ liệu trước khi gọi tới dịch vụ truyền thông mạng. Do môi trường truyền thông làm việc với hai loại dữ liệu chính là văn bản và mảng byte, quá trình serialization có thể xem là chuyển đổi object về một mảng byte hoặc về một chuỗi văn bản. Trường hợp chuyển object về mảng byte được gọi là trình tự hóa nhị phân (binary serialization). Việc chuyển object về chuỗi ký tự được gọi là trình tự hóa văn bản (text serialization). Yêu cầu cơ bản nhất của việc chuyển đổi này là phải đảm bảo thực hiện được việc khôi phục lại object từ dạng trung gian. Serialization và stream thường hoạt động cùng nhau. Trong đó, serialization sử dụng stream để đọc/ghi dữ liệu. Text serializationVí dụHãy cùng thực hiện ví dụ sau: Giả sử có một class Student với các trường Id (int), FirstName (string), LastName (string), DateOfBirth (DateTime). Hãy truyền một object của Student từ client đến server. Tạo cấu trúc solution và projectBước 1. Tạo một solution trống đặt tên là TextSerialization.. Tạo một solution trống đặt tên là TextSerialization. Bước 2. Tạo mới ba project trong solution này: Client (Console App), Server (Console App), Common (Class Library (.NET framework)).. Tạo mới ba project trong solution này: Client (Console App), Server (Console App), Common (Class Library (.NET framework)). Bước 3. Thiết lập Multiple startup projects cho Client và Server.. Thiết lập Multiple startup projects cho Client và Server. Bước 4. Thêm hai file mã nguồn Student.cs và TextSerializer.cs vào Common. Thêm hai file mã nguồn Student.cs và TextSerializer.cs vào Common Bước 5. Thiết lập cho Client tham chiếu tới thư viện Common.. Thiết lập cho Client tham chiếu tới thư viện Common. Tương tự thiết lập để Server cùng tham chiếu tới thư viện Common Viết code cho thư viện Commonusing System; namespace Common { public class Student { public int Id { get; set; } = 1; public string FirstName { get; set; } = ""; public string LastName { get; set; } = ""; public DateTime DateOfBirth { get; set; } = DateTime.Now; } } using System; using System.Collections.Generic; namespace Common { public class TextSerializer { // Chuyển đổi một object của Student sang chuỗi ký tự. // Chuỗi kết quả có hình thức tương tự chuỗi tham số của Http get. public static string Serialize(Student obj) { return $"Id = {obj.Id} " + $"& FirstName = {obj.FirstName} " + $"& LastName = {obj.LastName} " + $"& DateOfBirth = {obj.DateOfBirth.Ticks}"; } // Chuyển đổi một chuỗi trở lại thành object kiểu Student public static Student Deserialize(string data) { var dict = new Dictionary Viết code cho Client và Serverusing Common; using System; using System.IO; using System.Net; using System.Net.Sockets; namespace Client { class Program { static void Main(string[] args) { Console.Title = "Client"; while (true) { Console.Write("Press enter to send ... "); Console.ReadLine(); var student = new Student { Id = 1, FirstName = "Nguyen Van", LastName = "A", DateOfBirth = new DateTime(1990, 12, 30) }; var client = new TcpClient(); client.Connect(IPAddress.Loopback, 1308); var stream = client.GetStream(); var writer = new StreamWriter(stream) { AutoFlush = true }; writer.WriteLine(TextSerializer.Serialize(student)); } } } } using Common; using System; using System.IO; using System.Net; using System.Net.Sockets; namespace Server { internal class Program { private static void Main() { Console.Title = "Server"; var listener = new TcpListener(IPAddress.Any, 1308); listener.Start(10); while (true) { var client = listener.AcceptTcpClient(); var stream = client.GetStream(); var reader = new StreamReader(stream); var data = reader.ReadLine(); var student = TextSerializer.Deserialize(data); Console.WriteLine($"Raw data:\r\n{data}\r\n"); Console.WriteLine("Deserialized object:"); Console.WriteLine($"Id: {student.Id}\r\nFirst Name: {student.FirstName}\r\nLast Name: {student.LastName}\r\nDate of birth: {student.DateOfBirth.ToShortDateString()}"); client.Close(); } } } } Phân tích ví dụTrong ví dụ trên chúng ta xây dựng một lớp thực thể Student, lớp using System; using System.Collections.Generic; namespace Common { public class TextSerializer { // Chuyển đổi một object của Student sang chuỗi ký tự. // Chuỗi kết quả có hình thức tương tự chuỗi tham số của Http get. public static string Serialize(Student obj) { return $"Id = {obj.Id} " + $"& FirstName = {obj.FirstName} " + $"& LastName = {obj.LastName} " + $"& DateOfBirth = {obj.DateOfBirth.Ticks}"; } // Chuyển đổi một chuỗi trở lại thành object kiểu Student public static Student Deserialize(string data) { var dict = new Dictionary5có nhiệm vụ chuyển đổi object của using System; using System.Collections.Generic; namespace Common { public class TextSerializer { // Chuyển đổi một object của Student sang chuỗi ký tự. // Chuỗi kết quả có hình thức tương tự chuỗi tham số của Http get. public static string Serialize(Student obj) { return $"Id = {obj.Id} " + $"& FirstName = {obj.FirstName} " + $"& LastName = {obj.LastName} " + $"& DateOfBirth = {obj.DateOfBirth.Ticks}"; } // Chuyển đổi một chuỗi trở lại thành object kiểu Student public static Student Deserialize(string data) { var dict = new Dictionary6thành chuỗi ký tự và ngược lại. ToString và ParseNhư chúng ta thấy, quá trình chuyển đổi object của Student thành chuỗi ký tự, và chuyển đổi chuỗi ký tự thành object của Student là khá đơn giản. Hầu hết các kiểu dữ liệu cơ bản của C# đều hỗ trợ sẵn phương thức ToString (kế thừa từ lớp Object) để chuyển thành chuỗi ký tự, và phương thức Parse để chuyển đổi từ chuỗi ký tự. Điểm khác biệt ở đây là kiểu dữ liệu DateTime dùng để lưu trữ thông tin thời gian, bao gồm đầy đủ ngày, tháng, năm, giờ, phút, giây, mili giây. Biến của DateTime là một object bình thường (tương tự như biến của Student). Chúng ta lợi dụng thuộc tính Ticks của lớp này để biến đổi dữ liệu. Một tick biểu diễn khoảng thời gian bằng 100 nano giây (10 phần triệu của 1 giây), tức là 10 triệu ticks bằng 1 giây. Giá trị của thuộc tính Ticks là số tick tính từ 12h đêm ngày 1 tháng 1 năm 0001. Toàn bộ các tính toán trên thời gian của kiểu DateTime dựa trên con số này. Vì vậy, giá trị Ticks có thể sử dụng để tạo ra object của DateTime. Kết quả của text serialization trong ví dụ trên là chuỗi ký tự: using System; using System.Collections.Generic; namespace Common { public class TextSerializer { // Chuyển đổi một object của Student sang chuỗi ký tự. // Chuỗi kết quả có hình thức tương tự chuỗi tham số của Http get. public static string Serialize(Student obj) { return $"Id = {obj.Id} " + $"& FirstName = {obj.FirstName} " + $"& LastName = {obj.LastName} " + $"& DateOfBirth = {obj.DateOfBirth.Ticks}"; } // Chuyển đổi một chuỗi trở lại thành object kiểu Student public static Student Deserialize(string data) { var dict = new Dictionary7 Chuỗi ký tự này được viết theo định dạng tương tự như cách viết chuỗi tham số get của http. Một số lưu ýChúng ta thấy, để thực hiện text serialization có những điểm đáng lưu ý:
Giải pháp ở trên chúng ta đưa ra chỉ là một trong số rất nhiều giải pháp khác nhau có thể áp dụng để chuyển đổi object thành chuỗi ký tự. Binary serializationVí dụHãy cùng thực hiện ví dụ sau: Giả sử có một class Student với các trường Id (int), FirstName (string), LastName (string), DateOfBirth (DateTime). Hãy truyền một object của Student từ client đến server. Viết code cho thư viện Commonusing System; namespace Common { public class Student { public int Id { get; set; } = 1; public string FirstName { get; set; } = ""; public string LastName { get; set; } = ""; public DateTime DateOfBirth { get; set; } = DateTime.Now; } } using System; using System.Collections.Generic; using System.Text; namespace Common { public class BinarySerializer { // hai phương thức sau đây sử dụng lớp BitConverter. // BitConverter giúp chuyển đổi một biến (thuộc các kiểu cơ sở, trừ kiểu string) // về mảng byte. public static byte[] Serialize(Student obj) { // danh sách này sẽ chứa các byte thu từ từng trường dữ liệu var data = new List Viết code cho Client và Serverusing Common; using System; using System.IO; using System.Net; using System.Net.Sockets; namespace Client { class Program { static void Main(string[] args) { Console.Title = "Client"; while (true) { Console.Write("Press enter to send ... "); Console.ReadLine(); var student = new Student { Id = 1, FirstName = "Nguyen Van", LastName = "A", DateOfBirth = new DateTime(1990, 12, 30) }; var client = new TcpClient(); client.Connect(IPAddress.Loopback, 1308); var stream = client.GetStream(); var writer = new BinaryWriter(stream); var data = BinarySerializer.Serialize(student); writer.Write(data.Length); stream.Write(data, 0, data.Length); stream.Flush(); client.Close(); } } } } using Common; using System; using System.IO; using System.Net; using System.Net.Sockets; namespace Server { class Program { static void Main(string[] args) { Console.Title = "Server"; var listener = new TcpListener(IPAddress.Any, 1308); listener.Start(10); while (true) { var client = listener.AcceptTcpClient(); var stream = client.GetStream(); var reader = new BinaryReader(stream); var length = reader.ReadInt32(); var data = reader.ReadBytes(length); var student = BinarySerializer.Deserialize(data); client.Close(); Console.WriteLine("Raw byte array:"); foreach (var b in data) Console.Write($"{b} "); Console.WriteLine("\r\nDeserialized object:"); Console.WriteLine($"Id: {student.Id}\r\nFirst Name: {student.FirstName}\r\nLast Name: {student.LastName}\r\nDate of birth: {student.DateOfBirth.ToShortDateString()}"); } } } }Binary serialization example Phân tích ví dụTrong ví dụ trên chúng ta xây dựng lớp BinarySerializer chuyển đổi object của Student thành mảng byte và ngược lại. Trong binary serialization mỗi trường dữ liệu được biến đổi thành một mảng byte. Chúng ta kết hợp hai lớp BitConverter và Encoding để thực hiện binary serialization. Nếu trong class chứa các biến thuộc kiểu object khác, chúng ta lại cần tiếp tục áp dụng hai class này cho từng trường trong object đó. Các lớp hỗ trợ nói trên hoạt động rất tốt với các trường dữ liệu có kích thước cố định (như kiểu số, logic, char). Đối với các trường kiểu string có kích thước biến đổi thường phải bổ sung thêm thông tin về độ dài của chuỗi để quá trình deserialization có thể lấy ra đúng số byte của chuỗi đó. Cấu trúc chuỗi byteĐối với binary serialization, trật tự mã hóa các trường rất quan trọng. Mã hóa các trường theo thứ tự nào thì phải giải mã theo đúng vị trí của trường đó. Trong ví dụ trên chúng ta mã hóa theo trật tự: Id (4 byte, cố định), length 1 (4 byte, cố định, chứa thông tin độ dài của trường FirstName), FirstName (không cố định), length2 (4 byte, cố định, chứa thông tin độ dài của trường LastName), LastName (không cố định), DateOfBirth (8 byte, cố định). Trật tự byte của binary serializationDo đó, khi giải mã ta dùng một biến offset để định vị byte bắt đầu của mỗi thành phần:
Kết quả chuyển đổiTrong ví dụ trên, kết quả của binary serialization là một chuỗi byte (31 byte): 1 0 0 0 6 0 0 0 78 103 117 121 101 110 5 0 0 0 86 97 110 32 65 0 128 64 95 128 9 183 8. Trong đó:
Một số lưu ýBinary serialization thường cho ra kết quả gọn nhẹ hơn nhưng không quá phù hợp nếu trong class có nhiều trường kích thước biến đổi (chủ yếu là string). Text serialization cho ra kết quả dài hơn nhưng dễ đọc (với người) và phù hợp nếu class chứa nhiều trường có độ dài biến đổi. Serialization và Stream thường song hành với nhau. Ở trên chúng ta minh họa de/serialization bằng cách chuyển đổi trực tiếp sang chuỗi hoặc mảng byte. Trên thực tế, quá trình trên thường làm việc với một luồng dữ liệu nào đó để tránh phải lưu trữ những mảng dữ liệu quá lớn trong chương trình. Quá trình serialization ghi dữ liệu thẳng vào stream, quá trình deserialization đọc thẳng dữ liệu từ stream Hỗ trợ serialization trong .NETTrong phần ví dụ về serialization chúng ta đã thấy việc chuyển một object về chuỗi ký tự hoặc mảng byte là một công việc tương đối phức tạp, tốn công sức và dễ sai sót, đặc biệt đối với các class lớn có nhiều trường dữ liệu, cũng như khi phải làm việc với nhiều class khác nhau. Để hỗ trợ cho người lập trình, .NET framework cung cấp các class hỗ trợ cho 3 loại serialization: binary, xml và json. BinaryFormatterLớp BinaryFormatter: biến đổi một object về mảng byte và ghi trực tiếp vào một stream; đọc các byte dữ liệu từ một stream và biến đổi về object. Lớp BinaryFormatter nằm trong không gian tên System.Runtime.Serialization.Formatters.Binary. Hãy cùng thực hiện một ví dụ để xem cách sử dụng của BinaryFormatter Xây dựng cấu trúc solutionLặp lại các bước như ở ví dụ đầu tiên với text serialization để tạo ra solution TcpBinaryFormatter có cấu trúc như sau: Lưu ý: thiết lập để Client và Server cùng tham chiếu tới thư viện Common Viết codeusing System; namespace Common { [Serializable] public class Student { public int Id { get; set; } = 1; public string FirstName { get; set; } = ""; public string LastName { get; set; } = ""; public DateTime DateOfBirth { get; set; } = DateTime.Now; } } using Common; using System; using System.Net; using System.Net.Sockets; using System.Runtime.Serialization.Formatters.Binary; namespace Client { class Program { static void Main(string[] args) { Console.Title = "Client"; while (true) { Console.Write("Press enter to send ... "); Console.ReadLine(); var student = new Student { Id = 1, FirstName = "Nguyen Van", LastName = "A", DateOfBirth = new DateTime(1990, 12, 30) }; var client = new TcpClient(); client.Connect(IPAddress.Loopback, 1308); var stream = client.GetStream(); var formatter = new BinaryFormatter(); formatter.Serialize(stream, student); client.Close(); } } } } using System; using System.Collections.Generic; namespace Common { public class TextSerializer { // Chuyển đổi một object của Student sang chuỗi ký tự. // Chuỗi kết quả có hình thức tương tự chuỗi tham số của Http get. public static string Serialize(Student obj) { return $"Id = {obj.Id} " + $"& FirstName = {obj.FirstName} " + $"& LastName = {obj.LastName} " + $"& DateOfBirth = {obj.DateOfBirth.Ticks}"; } // Chuyển đổi một chuỗi trở lại thành object kiểu Student public static Student Deserialize(string data) { var dict = new Dictionary0 XmlSerializerLớp XmlSerializer: tương tự như BinaryFormatter, XmlSerializer biến đổi một object về dạng xml và ghi vào một stream, cũng như đọc một file xml và biến đổi về object. XmlSerializer nằm trong không gian tên using Common; using System; using System.IO; using System.Net; using System.Net.Sockets; namespace Client { class Program { static void Main(string[] args) { Console.Title = "Client"; while (true) { Console.Write("Press enter to send ... "); Console.ReadLine(); var student = new Student { Id = 1, FirstName = "Nguyen Van", LastName = "A", DateOfBirth = new DateTime(1990, 12, 30) }; var client = new TcpClient(); client.Connect(IPAddress.Loopback, 1308); var stream = client.GetStream(); var writer = new StreamWriter(stream) { AutoFlush = true }; writer.WriteLine(TextSerializer.Serialize(student)); } } } }1. Do làm việc với xml là một dạng dữ liệu cấp cao, XmlSerializer cũng có thể dùng đến (nhưng không bắt buộc) hai lớp adapter XmlReader và XmlWriter để để hỗ trợ đọc ghi xml. Hãy cùng thực hiện ví dụ sau using System; using System.Collections.Generic; namespace Common { public class TextSerializer { // Chuyển đổi một object của Student sang chuỗi ký tự. // Chuỗi kết quả có hình thức tương tự chuỗi tham số của Http get. public static string Serialize(Student obj) { return $"Id = {obj.Id} " + $"& FirstName = {obj.FirstName} " + $"& LastName = {obj.LastName} " + $"& DateOfBirth = {obj.DateOfBirth.Ticks}"; } // Chuyển đổi một chuỗi trở lại thành object kiểu Student public static Student Deserialize(string data) { var dict = new Dictionary1 using System; using System.Collections.Generic; namespace Common { public class TextSerializer { // Chuyển đổi một object của Student sang chuỗi ký tự. // Chuỗi kết quả có hình thức tương tự chuỗi tham số của Http get. public static string Serialize(Student obj) { return $"Id = {obj.Id} " + $"& FirstName = {obj.FirstName} " + $"& LastName = {obj.LastName} " + $"& DateOfBirth = {obj.DateOfBirth.Ticks}"; } // Chuyển đổi một chuỗi trở lại thành object kiểu Student public static Student Deserialize(string data) { var dict = new Dictionary2 NewtonSoft.JsonĐối với định dạng json, mặc dù .NET framework có class hỗ trợ nhưng không thực sự tốt. Bộ thư viện NewtonSoft.Json thường được sử dụng rất nhiều hiện nay. Có thể download thư viện này từ NuGet. Xem bài viết này để biết cách cài đặt thư viện NewtonSoft.Json từ NuGet. Tạo solution TcpJsonSerializer tương tự như các ví dụ trên và cài đặt thư viện NewtonSoft.Json vào Client và Server. Viết code cho Client và Server như sau: using System; using System.Collections.Generic; namespace Common { public class TextSerializer { // Chuyển đổi một object của Student sang chuỗi ký tự. // Chuỗi kết quả có hình thức tương tự chuỗi tham số của Http get. public static string Serialize(Student obj) { return $"Id = {obj.Id} " + $"& FirstName = {obj.FirstName} " + $"& LastName = {obj.LastName} " + $"& DateOfBirth = {obj.DateOfBirth.Ticks}"; } // Chuyển đổi một chuỗi trở lại thành object kiểu Student public static Student Deserialize(string data) { var dict = new Dictionary3 using System; using System.Collections.Generic; namespace Common { public class TextSerializer { // Chuyển đổi một object của Student sang chuỗi ký tự. // Chuỗi kết quả có hình thức tương tự chuỗi tham số của Http get. public static string Serialize(Student obj) { return $"Id = {obj.Id} " + $"& FirstName = {obj.FirstName} " + $"& LastName = {obj.LastName} " + $"& DateOfBirth = {obj.DateOfBirth.Ticks}"; } // Chuyển đổi một chuỗi trở lại thành object kiểu Student public static Student Deserialize(string data) { var dict = new Dictionary4 |