Thực hành: Lập trình socket Udp cơ bản

    0

    Như phân tích ở chương trước, .NET framework hỗ trợ lập trình socket bằng cách tạo ra một wrapper xung quanh bộ Socket API của hệ thống, đồng thời cung cấp nhiều công cụ mạnh dùng trong phát triển ứng dụng mạng.

    Bắt đầu từ bài này chúng ta sẽ xem xét các kỹ thuật lập trình socket cơ bản trong .NET, bao gồm lập trình với socket Tcp, socket Udp và socket thô. Các kỹ thuật này sử dụng trực tiếp lớp Socket để gửi và nhận dữ liệu. Chúng ta sẽ vận dụng các kỹ thuật lập trình socket cơ bản này để viết các ứng dụng mạng đơn giản theo mô hình client / server.

    Trong bài học này chúng ta bắt đầu làm quen với lập trình socket Udp và các lớp hỗ trợ lập trình socket của .NET framework thông qua thực hiện một bài thực hành đơn giản.

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

    Hỗ trợ lập trình socket trong .NET framework

    .NET cung cấp không gian tên System.NetSystem.Net.Sockets chứa các class và các kiểu dữ liệu hỗ trợ quan trọng phục vụ cho lập trình với socket. Các class này nằm trong assembly System (file thư viện System.dll), được sử dụng mặc định trong các project của C#.

    Lớp cơ bản nhất, trung tâm và quan trọng nhất trong lập trình socket với C# và .NET là lớp Socket.

    Lớp Socket đảm nhiệm vai trò của một wrapper xung quanh Windows Socket API. Lớp này giúp người lập trình gọi tới các API này từ chương trình .NET sử dụng các kiểu dữ liệu và cách thức lập trình của .NET mà không cần biết đến các cấu trúc của Windows socket API. Lớp Socket cung cấp tất cả chức năng cơ bản dành cho lập trình truyền thông với socket cho C# (và các ngôn ngữ .NET khác).

    Namespace System.Net.Sockets cũng chứa một số lớp hỗ trợ lập trình socket cao cấp hơn như TcpClient, TcpListener, UdpClient. Các lớp này đều được xây dựng xung quanh lớp Socket giúp đơn giản hóa việc lập trình mạng. Các lớp này sẽ được xem xét chi tiết trong các bài sau.

    Ngoài ra, để lập trình những ứng dụng mạng phức tạp hơn, .NET framework cung cấp thêm nhiều thư viện hỗ trợ lập trình với luồng mạng (NetworkStream), tuần tự hóa dữ liệu (Serialization), lập trình bất đồng bộ (Asynchronous programming), lập trình đa luồng (Threading), bảo mật socket, mã hóa dữ liệu, v.v.. Các nội dung này sẽ lần lượt được trình bày chi tiết ở các bài sau.

    Để thực hiện tất cả bài tập và ví dụ chúng ta sẽ sử dụng Visual Studio 2017 (phiên bản Community, Professional hoặc Ultimate). Có thể tải ở đây.

    Thực hành: xây dựng ứng dụng client/server đơn giản sử dụng socket Udp

    Chúng ta bắt đầu học lập trình với Udp socket thông qua một ví dụ.

    Bài toán

    Viết một ứng dụng dòng lệnh đơn giản (console application) theo mô hình client/server đáp ứng các yêu cầu:

    1. Client cho phép người dùng nhập một chuỗi ký tự từ bàn phím và gửi chuỗi ký tự cho server;
    2. Server nhận chuỗi ký tự, chuyển đổi tất cả ký tự thành dạng in hoa và gửi chuỗi kết quả lại cho client;
    3. Client nhận kết quả và hiển thị lại cho người dùng.

    Logic của cả hai ứng dụng được trình bày trong sơ đồ dưới đây.

    Sơ đồ khối hoạt động của chương trình cơ bản sử dụng socket Udp
    Sơ đồ khối hoạt động của chương trình cơ bản sử dụng socket Udp

    Chuẩn bị solution và project

    Bước 1. Tạo một solution trống trong Visual Studio đặt tên là UdpSocket

    Bước 2. Tạo hai project kiểu Console Application, một project đặt tên là Client, project kia đặt tên là Server. Sau khi thực hiện, cửa sổ Solution Explorer hiển thị như sau:

    Cửa sổ Solution Explorer của dự án
    Cửa sổ Solution Explorer với hai dự án

    Bước 3. Thiết lập để đồng thời chạy debug cả hai chương trình.

    Do là một ứng dụng mạng với hai thành phần hoạt động song song, mỗi lần debug chúng ta thường phải chạy cả client và server. Thiết lập dưới đây giúp chúng ta nhanh chóng chạy cả hệ thống để debug bằng cách tự động chạy server trước, ngay sau đó sẽ tự chạy client.

    Click phải vào tên solution và chọn Properties. Trong cửa sổ mở ra lựa chọn “Startup Project” (1) => chọn “Multiple startup projects” (2) => chọn giá trị “Start” trong cột “Action” cho từng project (3) => chọn và đẩy project Server lên trên (4). Ấn OK để hoàn tất.

    Thiết lập để debug đồng thời nhiều project
    Thiết lập để debug đồng thời nhiều project

    Nếu ấn F5 (chạy debug), chương trình Server sẽ chạy trước, ngay sau đó chương trình Client sẽ chạy theo.

    Viết code cho Client và Server

    Bước 4. Mở file Program.cs trong project Client và viết code như sau:

    Đọc kỹ các comment để nắm được ý nghĩa của từng lệnh

    using System;
    using System.Net; // để sử dụng lớp IPAddress, IPEndPoint
    using System.Net.Sockets; // để sử dụng lớp Socket
    using System.Text; // để sử dụng lớp Encoding
    
    namespace Client
    {
        internal class Program
        {
            private static void Main(string[] args)
            {
                Console.Title = "Udp Client";
    
                // yêu cầu người dùng nhập ip của server
                Console.Write("Server IP address: ");
                var serverIpStr = Console.ReadLine();
                // chuyển đổi chuỗi ký tự thành object thuộc kiểu IPAddress
                var serverIp = IPAddress.Parse(serverIpStr);
    
                // yêu cầu người dùng nhập cổng của server
                Console.Write("Server port: ");
                var serverPortStr = Console.ReadLine();
                // chuyển chuỗi ký tự thành biến kiểu int
                var serverPort = int.Parse(serverPortStr);
    
                // đây là "địa chỉ" của tiến trình server trên mạng
                // mỗi endpoint chứa ip của host và port của tiến trình
                var serverEndpoint = new IPEndPoint(serverIp, serverPort);            
    
                var size = 1024; // kích thước của bộ đệm
                var receiveBuffer = new byte[size]; // mảng byte làm bộ đệm            
    
                while (true)
                {
                    // yêu cầu người dùng nhập một chuỗi
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.Write("# Text >>> ");
                    Console.ResetColor();
                    var text = Console.ReadLine();
    
                    // khởi tạo object của lớp socket để sử dụng dịch vụ Udp
                    // lưu ý SocketType của Udp là Dgram (datagram) 
                    var socket = new Socket(SocketType.Dgram, ProtocolType.Udp);
    
                    // biến đổi chuỗi thành mảng byte
                    var sendBuffer = Encoding.ASCII.GetBytes(text);
                    // gửi mảng byte trên đến tiến trình server
                    socket.SendTo(sendBuffer, serverEndpoint);
    
                    // endpoint này chỉ dùng khi nhận dữ liệu
                    EndPoint dummyEndpoint = new IPEndPoint(IPAddress.Any, 0);
                    // nhận mảng byte từ dịch vụ Udp và lưu vào bộ đệm
                    // biến dummyEndpoint có nhiệm vụ lưu lại địa chỉ của tiến trình nguồn
                    // tuy nhiên, ở đây chúng ta đã biết tiến trình nguồn là Server
                    // do đó dummyEndpoint không có giá trị sử dụng 
                    var length = socket.ReceiveFrom(receiveBuffer, ref dummyEndpoint);
                    // chuyển đổi mảng byte về chuỗi
                    var result = Encoding.ASCII.GetString(receiveBuffer, 0, length);
                    // xóa bộ đệm (để lần sau sử dụng cho yên tâm)
                    Array.Clear(receiveBuffer, 0, size);
    
                    // đóng socket và giải phóng tài nguyên
                    socket.Close();
    
                    // in kết quả ra màn hình
                    Console.WriteLine($">>> {result}");
                }
            }
        }
    }

    Bước 5. Mở file Program.cs trong project Server và viết code như sau:

    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    
    namespace Server
    {
        internal class Program
        {
            private static void Main(string[] args)
            {
                Console.Title = "Udp Server";
    
                // giá trị Any của IPAddress tương ứng với Ip của tất cả các giao diện mạng trên máy
                var localIp = IPAddress.Any;
                // tiến trình server sẽ sử dụng cổng 1308
                var localPort = 1308;
                // biến này sẽ chứa "địa chỉ" của tiến trình server trên mạng
                var localEndPoint = new IPEndPoint(localIp, localPort);
                // yêu cầu hệ điều hành cho phép chiếm dụng cổng 1308
                // server sẽ nghe trên tất cả các mạng mà máy tính này kết nối tới
                // chỉ cần gói tin udp đến cổng 1308, tiến trình server sẽ nhận được
    
                // một overload khác của hàm tạo Socket
                // InterNetwork là họ địa chỉ dành cho IPv4
                var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                socket.Bind(localEndPoint);
                Console.WriteLine($"Local socket bind to {localEndPoint}. Waiting for request ...");
                
                var size = 1024;
                var receiveBuffer = new byte[size];
    
                while (true)
                {
                    // biến này về sau sẽ chứa địa chỉ của tiến trình client nào gửi gói tin tới
                    EndPoint remoteEndpoint = new IPEndPoint(IPAddress.Any, 0);
                    // khi nhận được gói tin nào sẽ lưu lại địa chỉ của tiến trình client
                    var length = socket.ReceiveFrom(receiveBuffer, ref remoteEndpoint);
                    var text = Encoding.ASCII.GetString(receiveBuffer, 0, length);
                    Console.WriteLine($"Received from {remoteEndpoint}: {text}");
    
                    // chuyển chuỗi thành dạng in hoa
                    var result = text.ToUpper();
    
                    var sendBuffer = Encoding.ASCII.GetBytes(result);
                    // gửi kết quả lại cho client
                    socket.SendTo(sendBuffer, remoteEndpoint);
    
                    Array.Clear(receiveBuffer, 0, size);
                }
            }
        }
    }
    

    Dịch và chạy thử

    Bước 6. Chạy thử ứng dụng (F5) Chương trình server sẽ chạy trước, chương trình client sẽ chạy ngay sau server.

    Server sẽ viết ra thông báo về IP và giá trị cổng mà nó đang chờ request. Client sẽ yêu cầu người dùng nhập địa chỉ IP và số cổng của server. Vì server đang nghe tất cả các giao diện mạng và client đang chạy trên cùng máy vật lý với server, client có thể sử dụng địa chỉ loopback (127.0.0.1) và số cổng 1308.

    Ảnh chụp màn hình khi ứng dụng hoạt động
    Ảnh chụp màn hình khi ứng dụng hoạt động

    Nhập bất kỳ chuỗi ký tự nào vào dấu nhắc lệnh của client. Ngay sau đó sẽ nhận được kết quả là chuỗi ký tự đã chuyển thành chữ hoa. Để ý rằng mỗi lần Client phát đi một chuỗi ký tự, giá trị cổng của nó lại thay đổi (ở trên là 63004, 63005, 63006).

    Lưu ý, khi Server hoạt động lần điều tiên, hệ điều hành sẽ yêu cầu người dùng cấp phép cho Server chiếm dụng cổng 1308. Nếu chạy Server từ một tài khoản bị hạn chế quyền sẽ không thực hiện được yêu cầu này, và Server sẽ bị đóng lại ngay sau đó. Từ lần hoạt động sau sẽ không cần trả lời cấp phép nữa.

    Bình luận

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