Lập trình mạng Java nâng cao - Hướng dẫn chi tiết UDP, TCP, bắt tay ba bước và bốn lần bắt tay (kèm code và ví dụ)

29. Lập trình mạng

29.1 Tổng quan

Ba yếu tố cốt lõi của lập trình mạng:

  • IP: Địa chỉ của thiết bị trên mạng, là định danh duy nhất.
  • Cổng (Port): Định danh duy nhất của ứng dụng trên thiết bị (0-65535).
  • Giao thức (Protocol): Quy tắc truyền dữ liệu trên mạng (UDP, TCP, HTTP...).

29.2 Lớp InetAddress

static InetAddress getByName(String host)
Xác định địa chỉ IP từ tên máy chủ. host có thể là tên máy hoặc địa chỉ IP.

String getHostName()              Lấy tên máy chủ của địa chỉ IP này.
String getHostAddress()           Trả về chuỗi địa chỉ IP dưới dạng văn bản.

(Khi gặp ngoại lệ, ném trực tiếp; mặc định dùng cách xử lý đầu tiên được gợi ý bởi alt+enter)

import java.net.InetAddress;
import java.net.UnknownHostException;

public class Example01 {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress address = InetAddress.getByName("mykwh");
        System.out.println(address); // mykwh/169.254.146.8

        String hostAddress = address.getHostAddress();
        System.out.println(hostAddress); // 169.254.146.8

        String hostName = address.getHostName();
        System.out.println(hostName); // mykwh
    }
}

29.3 Giao tiếp UDP

29.3.1 Gửi dữ liệu qua UDP

  • Tạo đối tượng DatagramSocket cho phía gửi.
  • Đóng gói dữ liệu (DatagramPacket).
  • Gửi dữ liệu.
  • Giải phóng tài nguyên.

(Khi gặp ngoại lệ, ném trực tiếp; mặc định dùng cách xử lý đầu tiên được gợi ý bởi alt+enter)

import java.io.IOException;
import java.net.*;

public class Example02 {
    public static void main(String[] args) throws IOException {
        // 1. Tạo đối tượng DatagramSocket cho phía gửi (giống như công ty chuyển phát)
        // Gán cổng: sau này sẽ gửi dữ liệu qua cổng này.
        // Tham số rỗng: chọn ngẫu nhiên một cổng khả dụng.
        // Có tham số: chỉ định cổng cụ thể.
        DatagramSocket ds = new DatagramSocket();

        // 2. Đóng gói dữ liệu (DatagramPacket)
        String msg = "Xin chào";
        // 2.1 Chuyển chuỗi thành mảng byte
        byte[] bytes = msg.getBytes();
        // Gửi đến thiết bị có địa chỉ 127.0.0.1
        InetAddress targetAddr = InetAddress.getByName("127.0.0.1");
        // 2.2 Cổng đích
        int targetPort = 10011;
        // 2.3 Đóng gói để gửi
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length, targetAddr, targetPort);

        // 3. Gửi dữ liệu
        ds.send(dp);

        // 4. Giải phóng tài nguyên
        ds.close();
    }
}

29.3.2 Nhận dữ liệu qua UDP

  • Tạo đối tượng DatagramSocket cho phía nhận.
  • Nhận gói dữ liệu đã đóng gói.
  • Phân tích gói dữ liệu.
  • Giải phóng tài nguyên.

Lưu ý: Phải chạy code gửi dữ liệu ở 29.3.1 trước, sau đó mới chạy code nhận dữ liệu.

(Khi gặp ngoại lệ, ném trực tiếp; mặc định dùng cách xử lý đầu tiên được gợi ý bởi alt+enter)

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class ReceiveExample {
    public static void main(String[] args) throws IOException {
        // 1. Tạo đối tượng DatagramSocket cho phía nhận,
        // tham số phải khớp với cổng của phía gửi.
        DatagramSocket ds = new DatagramSocket(10011);

        // 2. Nhận gói dữ liệu
        // 2.1 Tạo mảng mới để nhận dữ liệu (giống như dùng hộp mới để đựng)
        byte[] buffer = new byte[1024];
        // 2.2 Dùng buffer để nhận dữ liệu, sử dụng toàn bộ không gian của buffer
        DatagramPacket dp = new DatagramPacket(buffer, buffer.length);

        // 2.3 Nhận
        // Phương thức receive là blocking (chặn).
        // Chương trình sẽ dừng ở đây và chờ đợi.
        // Đợi cho đến khi phía gửi gửi tin nhắn.
        ds.receive(dp);

        // 3. Phân tích gói dữ liệu
        // 3.1 Giải nén dữ liệu từ gói (tức là mảng buffer) vào mảng data
        byte[] data = dp.getData();
        // Lấy số byte thực tế đã nhận được
        int length = dp.getLength();
        // Lấy địa chỉ IP của thiết bị gửi
        InetAddress senderAddr = dp.getAddress();
        // Lấy cổng của thiết bị gửi
        int senderPort = dp.getPort();

        System.out.println("Dữ liệu nhận được: " + new String(data, 0, length));
        System.out.println("Dữ liệu được gửi từ thiết bị " + senderAddr + " qua cổng " + senderPort);
    }
}

29.3.3 Ví dụ thực hành

UDP gửi: Kết thúc khi nhập "ending".

UDP nhận: Vì không biết khi nào phía gửi dừng, nên dùng vòng lặp vô hạn để nhận.

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class SendExample {
    public static void main(String[] args) throws IOException {
        // 1. Tạo đối tượng phía gửi
        DatagramSocket ds = new DatagramSocket();

        // 2. Đóng gói dữ liệu
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("Nhập dữ liệu cần gửi:");
            String input = sc.nextLine();
            if ("ending".equals(input)) {
                break;
            }
            byte[] bytes = input.getBytes();
            InetAddress targetAddr = InetAddress.getByName("127.0.0.1");
            int targetPort = 10011;
            DatagramPacket dp = new DatagramPacket(bytes, bytes.length, targetAddr, targetPort);

            // 3. Gửi dữ liệu
            ds.send(dp);
        }

        // 4. Giải phóng tài nguyên
        ds.close();
    }
}
=====================================================
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ReceiveExample {
    public static void main(String[] args) throws IOException {
        // 1. Tạo đối tượng DatagramSocket cho phía nhận
        DatagramSocket ds = new DatagramSocket(10011);

        // 2. Tạo gói dữ liệu để nhận
        byte[] buffer = new byte[1024];
        DatagramPacket dp = new DatagramPacket(buffer, buffer.length);

        while (true) {
            // 3. Nhận dữ liệu
            ds.receive(dp);

            // 4. Phân tích gói dữ liệu
            byte[] data = dp.getData();
            int senderPort = dp.getPort();
            String senderIp = dp.getAddress().getHostAddress();
            String senderName = dp.getAddress().getHostName();
            int receivedLen = dp.getLength();

            // 5. In dữ liệu
            System.out.println("Thiết bị IP: " + senderIp + ", tên máy: " + senderName + ", cổng: " + senderPort + " đã gửi: " + new String(data, 0, receivedLen));
        }
    }
}

29.4 Ba chế độ giao tiếp UDP

Unicast (đơn hướng), Multicast (đa hướng), Broadcast (quảng bá).

29.4.1 Unicast (Đơn hướng)

// Unicast: Tạo đối tượng DatagramSocket
DatagramSocket ds = new DatagramSocket();

29.4.2 Multicast (Đa hướng)

Địa chỉ Multicast: 224.0.0.0 ~ 239.255.255.255

Trong đó: 224.0.0.0 ~ 224.0.0.255 là địa chỉ multicast dành riêng. Bạn có thể dùng các địa chỉ multicast dự phòng.

Tạo đối tượng MulticastSocket cho phía nhận
MulticastSocket ms = new MulticastSocket(10011);
public class SendMulticast {
    public static void main(String[] args) throws IOException {
        // Phía gửi Multicast

        // 1. Tạo đối tượng MulticastSocket cho phía gửi
        MulticastSocket ms = new MulticastSocket();

        // 2. Đóng gói dữ liệu (DatagramPacket)
        String msg = "Đuổi theo gió đuổi theo trăng, đừng dừng lại; nơi tận cùng đồng cỏ hoang vu là núi xuân";
        byte[] bytes = msg.getBytes();
        InetAddress groupAddr = InetAddress.getByName("224.0.0.6");
        int groupPort = 10011;
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length, groupAddr, groupPort);

        // 3. Gửi dữ liệu bằng phương thức của MulticastSocket
        ms.send(dp);

        // 4. Giải phóng tài nguyên
        ms.close();
    }
}
======================================================
public class ReceiveMulticast01 {
    public static void main(String[] args) throws IOException {
        // Phía nhận Multicast 1

        // 1. Tạo đối tượng MulticastSocket cho phía nhận
        MulticastSocket ms = new MulticastSocket(10011);

        // 2. Thêm máy hiện tại vào nhóm 224.0.0.6
        InetAddress groupAddr = InetAddress.getByName("224.0.0.6");
        ms.joinGroup(groupAddr);

        // 3. Nhận gói dữ liệu
        byte[] buffer = new byte[1024];
        DatagramPacket dp = new DatagramPacket(buffer, buffer.length);

        // 4. Nhận dữ liệu
        ms.receive(dp);

        // 5. Phân tích dữ liệu
        byte[] data = dp.getData();
        int receivedLen = dp.getLength();
        int senderPort = dp.getPort();
        String senderIp = dp.getAddress().getHostAddress();
        String senderName = dp.getAddress().getHostName();

        System.out.println("IP: " + senderIp + ", tên máy: " + senderName + " đã gửi dữ liệu: " + new String(data, 0, receivedLen));

        // 6. Giải phóng tài nguyên
        ms.close();
    }
}
======================================================
public class ReceiveMulticast02 {
    public static void main(String[] args) throws IOException {
        // Phía nhận Multicast 2

        // 1. Tạo đối tượng MulticastSocket cho phía nhận
        MulticastSocket ms = new MulticastSocket(10011);

        // 2. Thêm máy hiện tại vào nhóm 224.0.0.6
        InetAddress groupAddr = InetAddress.getByName("224.0.0.6");
        ms.joinGroup(groupAddr);

        // 3. Nhận gói dữ liệu
        byte[] buffer = new byte[1024];
        DatagramPacket dp = new DatagramPacket(buffer, buffer.length);

        // 4. Nhận dữ liệu
        ms.receive(dp);

        // 5. Phân tích dữ liệu
        byte[] data = dp.getData();
        int receivedLen = dp.getLength();
        int senderPort = dp.getPort();
        String senderIp = dp.getAddress().getHostAddress();
        String senderName = dp.getAddress().getHostName();

        System.out.println("IP: " + senderIp + ", tên máy: " + senderName + " đã gửi dữ liệu: " + new String(data, 0, receivedLen));

        // 6. Giải phóng tài nguyên
        ms.close();
    }
}

29.4.3 Broadcast (Quảng bá)

Địa chỉ Broadcast: 255.255.255.255

InetAddress address = InetAddress.getByName("127.0.0.1");
Chỉ cần thay 127.0.0.1 trong dòng code gửi unicast thành 255.255.255.255.

29.5 Giao tiếp TCP

  • Giao thức TCP là một giao thức mạng đáng tin cậy. Nó tạo một đối tượng Socket ở mỗi đầu kết nối, nhưng tốc độ truyền chậm; hướng kết nối.
  • Phải đảm bảo kết nối đã được thiết lập trước khi giao tiếp.
  • Sử dụng luồng IO (Input/Output Stream) được tạo ra từ Socket để giao tiếp mạng.

Client (Socket):

  • Tạo đối tượng Socket của client và kết nối đến server chỉ định.
    Socket(String host, int port)
  • Lấy output stream và ghi dữ liệu.
    OutputStream getOutputStream()
  • Giải phóng tài nguyên.
    void close()

Server (ServerSocket):

  • Tạo đối tượng ServerSocket (phía server).
    ServerSocket(int port)
  • Lắng nghe kết nối từ client, trả về một đối tượng Socket.
    Socket accept()
  • Lấy input stream, đọc dữ liệu và hiển thị ra console.
    InputStream getInputStream()
  • Giải phóng tài nguyên.
    void close()

Khi chạy, phải chạy server trước.

// Client, giao thức TCP, gửi dữ liệu
public class TcpClient {
    public static void main(String[] args) throws IOException {
        // Giao thức TCP, gửi dữ liệu

        // Tạo đối tượng Socket (client)
        // Khi tạo đối tượng, đồng thời kết nối đến server với IP: 127.0.0.1
        Socket socket = new Socket("127.0.0.1", 10011);

        // Sau khi kết nối thành công, lấy output stream (byte) từ kênh kết nối
        OutputStream os = socket.getOutputStream();

        // Ghi dữ liệu, ghi dưới dạng byte
        String message = "aa666";
        os.write(message.getBytes());

        // Giải phóng tài nguyên
        os.close();
        socket.close();
        // Khi giải phóng tài nguyên, tầng dưới sẽ sử dụng giao thức bốn lần bắt tay để ngắt kết nối,
        // đảm bảo dữ liệu trong kênh kết nối đã được xử lý xong.
    }
}
=======================================================
// Server, giao thức TCP, nhận dữ liệu
public class TcpServer {
    public static void main(String[] args) throws IOException {
        // Giao thức TCP, nhận dữ liệu

        // 1. Tạo đối tượng ServerSocket, liên kết với cổng của client
        ServerSocket serverSocket = new ServerSocket(10011);

        // 2. Lắng nghe kết nối từ client
        // Nếu không có client kết nối, chương trình sẽ chờ vô thời hạn.
        // Nếu có client kết nối, trả về đối tượng Socket của client đó.
        Socket clientSocket = serverSocket.accept();

        // 3. Lấy input stream (byte) từ kênh kết nối để đọc dữ liệu
        InputStream is = clientSocket.getInputStream();
        // Sử dụng InputStreamReader để chuyển đổi thành character stream, đảm bảo đọc tiếng Trung không bị lỗi font.
        InputStreamReader isr = new InputStreamReader(is);
        // Có thể bọc thêm Buffered Stream để tăng hiệu suất đọc
        BufferedReader br = new BufferedReader(isr);

        // 3.1 Định nghĩa biến đọc dữ liệu byte
        int byteRead;
        // 3.2 Đọc trong vòng lặp
        while ((byteRead = is.read()) != -1) {
            System.out.println((char) byteRead);
        }

        // 4. Giải phóng tài nguyên
        // 4.1 Ngắt kết nối với client
        clientSocket.close();
        // 4.2 Đóng server (tương đương tắt server)
        serverSocket.close();
    }
}

29.6 Bắt tay ba bước trong TCP

(Hình ảnh tham khảo từ video Java trên Bilibili)

29.7 Bốn lần bắt tay trong TCP

(Hình ảnh tham khảo từ video Java trên Bilibili)

29.8 Ví dụ thực hành

29.8.1 Gửi nhiều lần

Client: Gửi dữ liệu nhiều lần.
Server: Nhận dữ liệu nhiều lần và in ra.
public class MultiSendClient {
    public static void main(String[] args) throws IOException {
        // Client: Gửi dữ liệu nhiều lần.
        // Server: Nhận dữ liệu nhiều lần và in ra.

        // Tạo đối tượng Socket và kết nối đến server
        Socket socket = new Socket("127.0.0.1", 10011);

        // Lấy output stream byte để ghi dữ liệu
        OutputStream os = socket.getOutputStream();
        Scanner sc = new Scanner(System.in);

        while (true) {
            System.out.println("Nhập nội dung cần gửi:");
            String input = sc.nextLine();
            if ("ending".equals(input)) {
                break;
            }
            os.write(input.getBytes());
        }

        // Giải phóng tài nguyên
        os.close();
        socket.close();
    }
}
===========================================
public class MultiReceiveServer {
    public static void main(String[] args) throws IOException {
        // Client: Gửi dữ liệu nhiều lần.
        // Server: Nhận dữ liệu nhiều lần và in ra.

        // 1. Tạo đối tượng server và liên kết với cổng 10011
        ServerSocket serverSocket = new ServerSocket(10011);
        // 2. Chờ client kết nối
        Socket clientSocket = serverSocket.accept();

        // Đọc dữ liệu
        InputStream is = clientSocket.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);

        int byteRead;
        while ((byteRead = isr.read()) != -1) {
            System.out.print((char) byteRead);
        }

        // Giải phóng tài nguyên
        serverSocket.close();
    }
}

Thẻ: Java UDP TCP socket MulticastSocket

Đăng vào ngày 2 tháng 6 lúc 00:55