Java - 網路通信

By sunwc 2023-04-02 Java

通信雙方地址

  • IP:網路定位的唯一一台主機,Java中表示 IP 地址,為 InetAddress 類;

    分為IPv4(例如 192.168.0.1)、IPv6(例如 3ffe:3201:1401:1280:c8ff:fe4d:db39:1984);私有IP地址(局部網域)範圍即為192.168.0.0 ~ 192.168.255.255

  • Port 號:定位主機上哪一個應用程序(Process)在執行;Port 號用來區分一台主機上的不同應用程序的 Process

  • domain(域名):在 browser 輸入域名,透過DNS(域名解析服務器)取得實際IP地址後、連接實際的Web server

遵循一定的規則(網路通信協議)

網路通信協議分層思想:

TCP, UDP的差別

  • TCP協議:
    • 使用TCP協議前,須先建立TCP連接,形成傳輸資料通道
    • 傳輸前,採用“三次握手”方式,點對點通信,是可靠的
      • 握手機制:保證對方都在
    • TCP協議進行通信的兩個應用Process:客戶端、服務端
    • 在連接中可進行大資料量的傳輸
    • 傳輸完畢,需釋放已建立的連接,效率低

建立連接需三次握手

關閉連接需四次揮手

  • UDP協議:適合播放影片
    • 將資料、源、目的 封裝成數據包,不需要建立連接
    • 每個資料報的大小限制在64K內
    • 發送不管對方是否準備好,接收方收到也不確認,故是不可靠的
    • 可以廣播發送
    • 發送資料結束時無需釋放資源,開銷小,速度快
@Test
public void inetAddressTest() {

    try {
        // 取得域名IP
        InetAddress google = InetAddress.getByName("www.google.com");
        System.out.println(google);

        // 取得域名
        System.out.println(google.getHostName());

        // 取得IP地址
        System.out.println(google.getHostAddress());

        // 取得本機IP
        InetAddress host1 = InetAddress.getLocalHost();
        System.out.println(host1);
    } catch (UnknownHostException e) {
        e.printStackTrace();
    }
}

模擬客戶端連接、發送訊息至服務器端

範例一

**
 * 實現TCP網路編程
 * @author sunwc
 * @create 2023-04-02 下午 02:37
 */
public class SocketTest {

    /**
     * 模擬客戶端
     */
    @Test
    public void client() {


        Socket socket = null;
        OutputStream os = null;
        try {
            InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
            // 創建Socket物件,指明服務器端的IP與port號
            socket = new Socket(inetAddress, 8899);
            // 取得輸出流,用於輸出資料
            os = socket.getOutputStream();
            // 寫出資料
            os.write("你好,這邊是客戶端".getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            // 關閉資源
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 模擬服務器端
     */
    @Test
    public void server() {

        ByteArrayOutputStream baos = null;
        ServerSocket serverSocket = null;
        Socket socket = null;
        InputStream is = null;
        try {
            // 創建服務器端的ServerSocket,指名自己的port號
            serverSocket = new ServerSocket(8899);

            // 接收客戶端的Socket
            socket = serverSocket.accept();
            // 取得輸入流
            is = socket.getInputStream();

            // 暫時用baos儲存所有bytes
            baos = new ByteArrayOutputStream();

            byte[] buffer = new byte[20];
            int len;
            while ((len = is.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }

            System.out.println(baos.toString());
            System.out.println("收到來自 客戶端:" + socket.getInetAddress().getHostAddress() + "的資料");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (baos != null) {
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

範例二

**
 * 從客戶端發送文件給服務器端,服務器端保存文件至本地,並回傳“發送成功”給客戶端
 * @author sunwc
 * @create 2023-04-02 下午 04:30
 */
public class TCPTest {

    @Test
    public void client() throws IOException {

        // 1. 建立客戶端Socket
        Socket socket = new Socket("127.0.0.1", 9090);
        // 2. 取得輸入流讀取本地文件
        FileInputStream fis = new FileInputStream("girl.png");
        // 3. 取得輸出流
        OutputStream os = socket.getOutputStream();

        // 4. 進行讀寫操作
        byte[] buffer = new byte[1024];
        int len;
        while ((len = fis.read(buffer)) != -1) {
            os.write(buffer, 0, len);
        }

        // 5. socket關閉資料的輸出
        socket.shutdownOutput();

        // 6. 接收來自於服務器端的反饋,將資料顯示在console上
        // 考量接收的訊息輸出的console可能有亂碼問題,故使用ByteArrayOutputStream
        InputStream is = socket.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        buffer = new byte[20];
        while ((len = is.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }
        System.out.println(baos.toString());

        // 7. 關閉資源
        baos.close();
        is.close();
        os.close();
        fis.close();
        socket.close();
    }



    @Test
    public void server() throws IOException {

        // 1. 建服務器Socket
        ServerSocket ss = new ServerSocket(9090);
        // 2. 接收客戶端Socket
        Socket socket = ss.accept();
        // 3. 取得輸入流
        InputStream is = socket.getInputStream();

        // 4. 準備輸出流:將從客戶端取得的圖片寫至本地
        FileOutputStream fos = new FileOutputStream("girl_final.png");

        // 5. 讀寫操作
        byte[] buffer = new byte[1024];
        int len;
        while ((len = is.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }

        System.out.println("服務器端圖片寫出完成");

         // 6. 服務器端給予客戶端反饋
        OutputStream os = socket.getOutputStream();
        os.write("您好,服務器端有收到您傳過來的圖片了".getBytes(StandardCharsets.UTF_8));

        // 7.關閉資源
        os.close();
        fos.close();
        is.close();
        socket.close();
        ss.close();

    }
}

範例三

/**
 * 客戶端給服務端發送txt文件,服務端將文件轉成大寫再回傳給客戶端
 * @author sunwc
 * @create 2023-04-02 下午 05:39
 */
public class TCPTest2 {

    @Test
    public void client() throws IOException {

        // 1. 建立與服務器端連接的socket
        Socket socket = new Socket("127.0.0.1",9999);

        // 2. 讀取本地文件並暫時保存在 ByteArrayOutputStream
        FileInputStream fis = new FileInputStream("english.txt");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
         // 3. 取得輸出流
        OutputStream os = socket.getOutputStream();

        byte[] buffer = new byte[20];
        int len;
        while ((len = fis.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }

        // 4. 一次性寫出資料到服務器端
        os.write(baos.toByteArray());

        // 5. socket關閉輸出資料
        socket.shutdownOutput();

        // 6. 取得服務器端大寫英文字的資料並將資料寫到本地文件
        InputStream is = socket.getInputStream();
        FileOutputStream fos = new FileOutputStream("english_final.txt");

        buffer = new byte[20];
        while ((len = is.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }

        // 7. 關閉資源
        fos.close();
        is.close();
        os.close();
        baos.close();
        fis.close();
        socket.close();
    }

    @Test
    public void server() throws IOException {

        // 1. 建立服務器端socket
        ServerSocket ss = new ServerSocket(9999);
        // 2. 接收客戶端socket
        Socket socket = ss.accept();
        // 3. 接收輸入流並將資料暫時寫入 ByteArrayOutputStream
        InputStream is = socket.getInputStream();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[20];
        int len;
        while ((len = is.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }

        System.out.println("服務器端文件接收完成");
        // 4. 將 ByteArrayOutputStream 資料一次性將英文字轉大寫
        String upperCase = baos.toString().toUpperCase(Locale.ROOT);

        // 5. 一次性寫出大寫英文字資料到客戶端
        OutputStream os = socket.getOutputStream();
        os.write(upperCase.getBytes(StandardCharsets.UTF_8));
        System.out.println("服務器端文件轉大寫並傳輸完成");

        // 6. 關閉資源
        os.close();
        baos.close();
        is.close();
        socket.close();
        ss.close();
    }
}