Giao thức Udp, cấu trúc header, truyền dữ liệu

    0

    UDP (User Datagram Protocol) là một trong hai giao thức của tầng giao vận trong bộ giao thức TCP/IP. Giao thức UDP được thiết kế bởi David P. Reed và hiện nay được định nghĩa trong RFC 768. UDP được thực thi ở dạng phần mềm và cài đặt ở dạng thư viện hệ thống của hầu hết các hệ điều hành hiện nay. Vì vậy hoàn toàn có thể gọi UDP là “hàm” hay “chương trình” khi nhìn từ khía cạnh lập trình mạng. UDP cung cấp dịch vụ vận chuyển dữ liệu đầu cuối – đầu cuối cho chương trình ứng dụng thông qua socket Udp.

    Quay trở lại Hướng dẫn tự học lập trình socket Tcp/Ip.

    Ở bài trước chúng ta đã phân tích chi tiết kỹ thuật lập trình socket Udp qua code của bài thực hành. Trong bài học này chúng ta sẽ tiếp cận từ phía giao thức UDP và xem xét chi tiết về giao thức này để hiểu rõ những gì diễn ra khi lập trình với Udp socket, qua đó sẽ đưa ra và giải thích một số vấn đề gặp phải khi lập trình với Udp socket.

    Cấu trúc gói tin của giao thức Udp

    Khi chúng ta gọi hàm Udp để vận chuyển một chuỗi byte tới tiến trình đích, chương trình Udp sẽ bổ sung thêm một chuỗi byte nhỏ (8 byte) vào đầu của chuỗi byte dữ liệu. Chuỗi byte bổ sung của Udp được gọi là header. Phần dữ liệu Udp nhận từ ứng dụng được gọi là phần payload.

    Udp header cùng với payload tạo ra một đơn vị thông tin riêng gọi là datagram. Như vậy có thể dễ dàng hình dung, datagram thực chất cũng chỉ là một chuỗi byte lớn. Cấu trúc của UDP Header được trình bày ở hình dưới đây.

    Cấu trúc header của gói tin UDP
    Cấu trúc header của gói tin UDP

    Cách mô tả chuỗi byte

    Chúng ta trước hết giải thích cách mô tả chuỗi byte thường dùng để mô tả cấu trúc header của các giao thức.

    Do một chuỗi bit thường rất dài, chúng ta không thể viết nó trên cùng một dòng. Thay vào đó, người ta sử dụng cách viết gần giống như biểu diễn một ma trận. Cụ thể, người ta thường chỉ viết 32 bit (4 byte) trên mỗi dòng, và nhóm mỗi 8 bit vào một octet/byte.

    Để tiện lợi cho việc tính toán ra vị trí của bit hoặc byte trong mảng, người ta đưa vào hai dòng phụ: dòng đầu đánh số byte từ 0 đến 3 (dòng Octet), dòng 2 đánh số bit từ 0 đến 31 (dòng Bit). Đầu mỗi dòng người ta thêm giá trị offset của byte và offset của bit.

    Vị trí của byte/octet ở mỗi dòng tính bằng offset của byte/octet ở dòng đó cộng với số thứ tự cột tương ứng ở dòng Octet. Tương tự, vị trí của bit ở mỗi dòng bằng offset của bit của dòng đó cộng số thứ tự của cột tương ứng ở dòng Bit.

    Trong mô tả Udp header bên trên, chúng ta sử dụng hai dòng để mô tả một mảng 8 byte (64 bit), mỗi dòng chứa 4 byte (32 bit).

    Mảng này được chia làm 4 phần:

    1. Source port chiếm từ bit 0 đến bit 15 (tức là byte 0 và byte 1),
    2. Destination port chiếm từ bit 16 đến 31 (tức là byte 2 và byte 3),
    3. Length chiếm từ bit 32 + 0 = 32 (32 là bit offset của dòng 2) đến 32 + 15 = 47 (tức là byte 4 + 0 = 4 và byte 4 + 1 = 5, với 4 là byte offset của dòng 2),
    4. Checksum chiếm từ bit 32 + 16 = 48 tới bit 32 + 31 = 63 (tức là byte 4 + 2 = 6 và byte 4 + 3 = 7).

    Udp header

    Chúng ta sẽ giải thích ý nghĩa của các trường trong Udp header.

    Source port number (2 byte) là số cổng của tiến trình nguồn. Lần đầu tiên Client phát lệnh SendTo, Udp sẽ tự hỏi hệ thống để mượn một giá trị port (thường nằm cuối dải giá trị) và điền vào trường này. Khi Server phát lệnh SendTo, Udp sử dụng luôn giá trị cổng mà tiến trình này đã chiếm dụng (1308, như trong bài thực hành đã sử dụng)

    Mỗi khi khởi tạo lại object của Socket (như trong code của Client), giá trị cổng nguồn lại thay đổi. Nếu chúng ta duy trì một object duy nhất của Socket thì giá trị cổng nguồn của gói tin phát từ Client sẽ không đổi.

    Destination port number (2 byte) là số cổng tiến trình đích. Khi Client phát lệnh SendTo, thông tin về port của Server từ tham số thứ hai (kiểu IPEndPoint) được sử dụng cho trường này.

    Length là độ dài của Header + data. Về lý thuyết thì 8 <= Length <= 65 535 (tức là 8 byte header + 65527 byte data), nhưng trên thực tế, 8 <= Length <= 65,507 bytes (bằng giá trị tối đa mô tả được bằng 2 byte là 65535 trừ đi 8 byte UDP header, trừ tiếp 20 byte IP header).

    Checksum dùng để kiểm tra lỗi header và dữ liệu nhằm đảm bảo tính toàn vẹn của gói tin. Checksum không bắt buộc với IPv4 (nếu không dùng thì chứa toàn các bit 0) nhưng bắt buộc với IPv6. Trường này được Udp tính toán tự động theo thuật toán mô tả trong RFC.

    Đặc điểm của giao thức Udp

    Từ cấu trúc gói tin Udp chúng ta có một số nhận xét sau:

    Do trường Source port và Destination port chỉ sử dụng 2 byte (16 bit) để chứa giá trị, giá trị của nó phải nằm trong dải [0, 65535], tức là từ 0 đến 2^16-1. Cấu trúc gói tin của Tcp cũng có quy định tương tự về Source port và Destination port. Điều này giúp chúng ta giải thích vùng giá trị của số cổng tiến trình đã được học.

    Nhờ trường Length, Udp xác định được độ dài của cả gói tin, và qua đó giúp duy trì ranh giới của dữ liệu. Mỗi phần dữ liệu gửi qua Udp (một lần phát lệnh SendTo) sẽ tạo ra một gói tin độc lập (và do đó cũng cần chừng ấy lệnh ReceiveFrom để nhận hết các gói tin này). Duy trì ranh giới của dữ liệu là một yêu cầu đặc biệt quan trọng khi xây dựng giao thức tầng ứng dụng mà chúng ta sẽ xem xét ở các chương tiếp theo. Khác với Udp, Tcp (sẽ xem xét ở phần sau) không duy trì được ranh giới dữ liệu (và người lập trình ứng dụng phải tự làm).

    Do cách tính giá trị trường Length, độ dài của chuỗi byte dữ liệu (thu được sau khi thực hiện chuyển đổi từ dữ liệu sang mảng byte) không được vượt quá 65507 byte để có thể đóng được vào một gói tin Udp. Nếu chuỗi dữ liệu vượt quá con số này thì chương trình phải tự mình cắt thành những phần có kích thước nhỏ hơn. Tương tự, trong tình huống đó, tiến trình đích phải tự mình ghép nối các phần dữ liệu lại trước khi sử dụng.

    UDP chỉ cung cấp duy nhất khả năng kiểm tra tính toàn vẹn của gói tin. UDP không cung cấp các chức năng đảm bảo vận tải, không đảm bảo thứ tự đến của gói tin, không đảm bảo việc chống trùng lặp của gói tin. Nếu ứng dụng cần những tính năng trên trong khi vẫn muốn sử dụng dịch vụ truyền Udp thì phải tự mình thực hiện trong giao thức của ứng dụng.

    Truyền dữ liệu với giao thức Udp

    Truyền tải dữ liệu qua UDP không tạo liên kết ảo giữa các tiến trình tham gia truyền thông. Do không tạo ra liên kết ảo trước khi truyền dữ liệu, giao thức UDP có thể truyền dữ liệu đi ngay lập tức mà không cần thực hiện quá trình xây dựng liên kết phức tạp. Vì lý do này mà chỉ khi nào nhận được dữ liệu (ReceiveFrom), tiến trình mới biết được nó đang trao đổi dữ liệu với tiến trình nào (thông qua tham số thứ hai kiểu EndPoint). Cũng vì lý do này, UDP được gọi là giao thức phi liên kết, socket UDP còn được gọi là socket phi liên kết (connectionless socket).

    Giao thức UDP đơn giản và cho phép truyền dữ liệu theo thời gian thực. Tuy nhiên, do thiếu các cơ chế kiểm soát dữ liệu trong quá trình truyền, giao thức UDP chỉ thích hợp khi truyền các loại dữ liệu không có yêu cầu cao về kiểm tra và sửa lỗi. Vì vậy UDP được sử dụng phổ biến nhất trong việc truyền tải dữ liệu đa phương tiện (video, audio, image), trong đó, việc mất một số datagram không ảnh hưởng đến việc sử dụng dữ liệu, đồng thời cần tốc độ truyền tải cao.

    Truyền tải dữ liệu qua Udp không sử dụng các bộ nhớ đệm, do đó chương trình đích bắt buộc phải nhận gói tin Udp kịp thời. Nếu không phát lệnh nhận (ReceiveFrom) kịp thời, gói tin Udp sẽ mất đi. Như trong phần thực hành trên chúng ta để cho Server luôn luôn chờ nhận gói tin trong một vòng lặp vô tận.

    Giao thức và dịch vụ tầng ứng dụng phổ biến sử dụng UDP bao gồm Domain Name System (DNS), Network Time Protocol (NTP), IP tunneling, Remote Procedure Call (RPC), Network File System (NFS), DHCP, TFTP (Trivial FTP), IPTV (Streaming media).

    Bình luận

    avatar
      Đăng ký theo dõi  
    Thông báo về