Ngăn cản mất dữ liệu trong lập trình Socket phi kết nối (UDP)

1
757

Trong lập trình Socket phi kết nối cũng có khả năng xảy ra việc mất dữ liệu như Socket hướng kết nối. Một thuận lợi của việc truyền thông dùng giao thức TCP là giao thức TCP sử dụng bộ đệm TCP. Tất cả dữ liệu được gởi bởi TCP Socket được đặt vào bộ đệm TCP trước khi được gởi ra ngoài mạng. Cũng giống như vậy, tất cả dữ liệu nhận từ Socket được đặt vào bộ đệm TCP trước khi được đọc bởi phương thức Receive(). Khi phương thức Receive() cố gắng đọc dữ liệu từ bộ đệm, nếu nó không đọc hết dữ liệu thì phần còn lại vẫn nằm trong bộ đệm và chờ lần gọi phương thức Receive() kế tiếp.

Vì UDP không quan tâm đến việc gởi lại các gói tin nên nó không dùng bộ đệm. Tất cả dữ liệu được gởi từ Socket đều được lập tức gởi ra ngoài mạng và tất cả dữ liệu được nhận từ mạng lập tức được chuyển cho phương thức ReceiveFrom() trong lần gọi tiếp theo. Khi phương thức ReceiveFrom() được dùng trong chương trình, các lập trình viên phải đảm bảo rằng bộ đệm phải đủ lớn để chấp nhận hết dữ liệu từ UDP Socket. Nếu bộ đệm  quá nhỏ, dữ liệu sẽ bị mất. Để thấy được điều này, ta tiến hành thay đổi kích thước bộ đệm trong chương trình UDP đơn giản trên.

Sau đây chúng ta sẽ kiểm chứng việc mất dữ liệu với chương trình đơn giản sau đây:

Chương trình BadUDPClient

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class BadUdpClient
{
    public static void Main()
    {
        byte[] data = new byte[30];
        string input, stringData;
        IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5000);
        Socket server = new Socket(AddressFamily.InterNetwork,
        SocketType.Dgram, ProtocolType.Udp);
        string welcome = "Xin chao serveer";
        data = Encoding.ASCII.GetBytes(welcome);
        server.SendTo(data, data.Length, SocketFlags.None, ipep);
        IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
        EndPoint tmpRemote = (EndPoint)sender;
        data = new byte[30];
        int recv = server.ReceiveFrom(data, ref tmpRemote);
        Console.WriteLine("Thong diep duoc nhan tu {0}:",
        tmpRemote.ToString()); Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));
        while (true)
        {
            input = Console.ReadLine();
            if (input == "exit")
                break;
            server.SendTo(Encoding.ASCII.GetBytes(input), tmpRemote);
            data = new byte[30];
            recv = server.ReceiveFrom(data, ref tmpRemote);
            stringData = Encoding.ASCII.GetString(data, 0, recv); Console.WriteLine(stringData);
        }
        Console.WriteLine("Dang dong client");
        server.Close();
    }
}

Ta có thể test chương trình này với chương trình UDP Server đơn giản tại đây. Khi ta nhận dữ liệu ít hơn 10 byte thì chương trình vẫn chạy bình thường nhưng khi ta nhập dữ liệu lớn hơn 30 byte thì chương trình BadUdpClient sẽ phát sinh ra một biệt lệ.

Mặc dầu ta không thể lấy lại dữ liệu đã bị mất nhưng ta có thể hạn chế mất dữ liệu bằng cách đặt phương thức ReceiveFrom() trong khối try-catch, khi dữ liệu bị mất bởi kích thước bộ đệm nhỏ, ta có thể tăng kích thước bộ đệm vào lần kế tiếp nhận dữ liệu. Để minh họa cho ví dụ này chúng ta sẽ chạy thử chương trình sau :

Chương trình BetterUdpClient

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class BetterdUdpClient
{
    public static void Main()
    {
        byte[] data = new byte[30];
        string input, stringData;
        IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5000);
        Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        string welcome = "Xin chao server";
        data = Encoding.ASCII.GetBytes(welcome);
        server.SendTo(data, data.Length, SocketFlags.None, ipep);
        IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
        EndPoint tmpRemote = (EndPoint)sender;
        data = new byte[30];
        int recv = server.ReceiveFrom(data, ref tmpRemote);
        Console.WriteLine("Thong diep duoc nhan tu {0}:", tmpRemote.ToString());
        Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv)); int i = 30;
        while (true)
        {
            input = Console.ReadLine();
            if (input == "exit")
                break;
            server.SendTo(Encoding.ASCII.GetBytes(input), tmpRemote);
            data = new byte[i];
            try
            {
                recv = server.ReceiveFrom(data, ref tmpRemote);
                stringData = Encoding.ASCII.GetString(data, 0, recv);
                Console.WriteLine(stringData);
            }
            catch (SocketException)
            {
                Console.WriteLine("Canh bao: du lieu bi mat, hay thu lai");
                i += 10;
            }
        }
        Console.WriteLine("Dang dong client");
        server.Close();
    }

}

Thay vì sử dụng mảng buffer với chiều dài cố định, chương trình BetterUdpClient dùng một biết có thể thiết lập giá trị khác nhau mỗi lần phương thức ReceiveFrom() được dùng.

byte[] data = new byte[i];
            try
            {
                recv = server.ReceiveFrom(data, ref tmpRemote);
                stringData = Encoding.ASCII.GetString(data, 0, recv);
                Console.WriteLine(stringData);
            }
            catch (SocketException)
            {
                Console.WriteLine("Canh bao: du lieu bi mat, hay thu lai");
                i += 10;
            }

Trên đây là cách xử lí vấn đề ngăn cản mất dữ liệu trong quá trình truyền thông điệp của lập trình Socket phi kết nối. Ngoài ra còn có một khó khăn khác là: Khi lập trình với giao thức udp là khả năng bị mất gói tin bởi vì  UDP là một giao thức phi kết nối nên không có cách nào mà thiết bị gởi biết được gói tin gởi có thực sự đến được đích hay không.

Cách đơn giản nhất để ngăn chặn việc mất các gói tin là phải có cơ chế hồi báo giống như giao thức TCP. Các gói tin được gởi thành công đến thiết bị nhận thì thiết bị nhận phải sinh ra gói tin hồi báo cho thiết bị gởi biết đã nhận thành công. Nếu gói tin hồi báo không được nhận trong một khoảng thời gian nào đó thì thiết bị nhận sẽ cho là gói tin đó đã bị mất và gởi lại gói tin đó. Có hai kỹ thuật dùng để truyền lại các gói tin UDP:

  • Sử dụng Socket bất đồng bộ và một đối tượng Timer. Kỹ thuật này yêu cầu sử dụng một Socket bất đồng bộ mà nó có thể lắng nghe các gói tin đến không bị block. Sau khi Socket được thiết lập đọc bất đồng bộ, một đối tượng Timer có thể được thiết lập, nếu đối tượng Timer tắt trước khi hành động đọc bất đồng bộ kết thúc thì việc gởi lại dữ liệu diễn ra.
  • Sử dụng Socket đồng bộ và thiết lập giá trị Socket time-out. Để làm được việc này, ta dùng phương thức SetSocketOption().

Ở bài viết này, chúng ta chủ yếu xoay quanh việc mất ngăn cản mất dữ liệu trong quá trình truyền tải thông điệp của UDP, nên vấn đề ngăn cản mất gói tin sẽ không trình bài cụ thể ở đây. Hẹn gặp lại các bạn ở các bài viết khác. Chúc các bạn thành công!

1 COMMENT

This site uses Akismet to reduce spam. Learn how your comment data is processed.