Java 網絡編程技術總結


Java 有關 UDP 和 TCP 兩種協議的網絡編程技術,在大部分情況下,很少會使用到,但是偶爾也會使用。對於大部分開發人員來說,最常遇到的使用場景有兩種:一種場景是公司的產品或項目需要跟相關的硬件進行對接,另一種場景就是需要跟其它公司進行接口對接(比如某些銀行提供的接口就要求使用 socket 對接),所以我們還是得要簡單學習和了解一下 Java 的網絡編程技術。

對於以上兩種常見的使用場景來說,我們使用 Java 網絡編程技術去解決,還是很容易的,比較容易上手。
本篇博客的主要目的在於對 Java 網絡編程進行一個簡單的總結,以便后續需要用到的時候,能夠快速找到。


一、UDP 通信

UDP 協議是一種只管傳輸,不需要回應的網絡協議,它在通信的兩端各建立一個 Socket 對象,這倆 Socket 對象都可以發送數據和接收數據,但是不需要對方的回復,因此傳輸效率很高,對於基於 UDP 協議的通信雙方而言,其實沒有所謂的客戶端和服務器的概念。Java 提供以下方法來實現 UDP 協議的通信:

方法名 說明
DatagramSocket() 創建 UDP Socket 對象
DatagramPacket(byte[] buf,int len,InetAddress add,int port) 創建數據包,指定目標主機端口
void send(DatagramPacket p) 發送數據報包
void close() 關閉連接
void receive(DatagramPacket p) 接收數據報包

發送端代碼演示:
import java.io.*;
import java.net.*;

//使用控制台,循環發送字符串數據,如果發送的是字符串 exit ,雙方服務都停止
public class ClientDemo {
    public static void main(String[] args) {

        //使用 BufferedReader 來作為控制台的字符串輸入交互
        //使用 DatagramSocket 創建 UDP 的 Socket 對象
        try (BufferedReader bw = new BufferedReader(new InputStreamReader(System.in));
             DatagramSocket ds = new DatagramSocket()) {

            while (true) {
                String str = bw.readLine();
                byte[] bytes = str.getBytes();
                //准備給本機的 8888 端口,發送 UDP 數據,
                InetAddress address = InetAddress.getLocalHost();
                int port = 8888;
                //對於 UDP 協議來說,使用 DatagramPacket 來封裝要發送的數據包
                DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
                ds.send(dp);

                //如果發送的是 exit 字符串,就停止發送服務
                if ("exit".equals(str)) {
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

接收端代碼演示:

import java.net.DatagramPacket;
import java.net.DatagramSocket;

//循環接收發送來的數據,當接收到 exit 字符串時,停止服務不再接收數據
public class ServerDemo {
    public static void main(String[] args) {

        //使用 DatagramSocket 創建 UDP 的 Socket 對象
        try (DatagramSocket ds = new DatagramSocket(8888);) {

            while (true) {
                //使用 DatagramPacket 封裝接收的 UDP 數據包
                DatagramPacket dp = new DatagramPacket(new byte[1024], 1024);
                ds.receive(dp);
                String result = new String(dp.getData(), 0, dp.getLength());
                System.out.println(result);

                //如果接收到 exit 字符串,則停止服務,不再接收
                if ("exit".equals(result)) {
                    break;
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

以上代碼就是 Java 采用 UDP 協議進行通信的基本編碼,大家可以根據工作中的實際需要進行改進,總體來說還算比較簡單。


二、TCP 通信

TCP 協議是面向連接的通信協議,即傳輸數據之前,在發送端和接收端建立邏輯連接,然后再傳輸數據,它提供了兩台計算機之間可靠無差錯的數據傳輸。在TCP連接中必須要明確客戶端與服務器端,由客戶端向服務端發出連接請求,每次連接的創建都需要經過三次握手。

TCP 協議是我們最常使用的通信協議,對於客戶端需要創建 Socket 對象,對於服務端需要創建 ServerSocket 對象。

無論對於客戶端還是服務端,可以通過其 Scoket 對象獲取到 InputStream 流和 OutputStream 流。通過 OutputStream 流寫數據就是發送數據,通過 InputStream 流讀數據就是接收數據。對於客戶端 Socket 對象發送數據來說,需要特別注意:如果客戶端 Socket 對象后續不再發送數據的話,需要調用其 shutdownOutput 方法明確告訴服務后續不再發送數據,這樣服務端才會停止接收數據,否則服務端接收數據的代碼將會阻塞,無法繼續向下運行。下面我們還是通過代碼來演示一下吧。

客戶端代碼演示:

import java.io.*;
import java.net.Socket;

//本代碼演示客戶端通過 TCP 協議,向服務端上傳圖片文件
public class ClientDemo {
    public static void main(String[] args)  {

        //創建客戶端 Socket 對象,創建字節緩沖流對象讀取要上傳的圖片文件
        try (Socket socket = new Socket("127.0.0.1", 8888);
             BufferedInputStream bis = new BufferedInputStream(
                     new FileInputStream("d:\\數據產品組大合照.jpg"));) {

            //獲取 Socket 輸出流對象,用於向服務端發送數據,
            //由於該輸出流是從 Socket 中獲取,因此不需要主動關閉,
            //因為 Socket 關閉后,該輸出流會自動關閉
            BufferedOutputStream bos = 
                new BufferedOutputStream(socket.getOutputStream());

            byte[] bArr = new byte[1024];
            int len;
            while ((len = bis.read(bArr)) != -1) {
                bos.write(bArr, 0, len);
            }
            bos.flush();

            //注意:此行代碼非常重要,告訴服務端:這邊的客戶端數據已經全部發送完畢了
            socket.shutdownOutput();

            //接收服務端返回來的消息,打印到控制台上,
            //由於該輸入流是從 Socket 中獲取,因此不需要主動關閉,
            //因為 Socket 關閉后,該輸入流會自動關閉
            BufferedReader br = new BufferedReader(
                new InputStreamReader(socket.getInputStream()));
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服務端的代碼演示:

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

//接收客戶端發送來的圖片數據,並保存到硬盤中
public class ServerDemo {
    public static void main(String[] args) {

        //創建服務端 ServerSocket 對象,創建字節流緩沖對象把接收的圖片保存到硬盤
        try (ServerSocket ss = new ServerSocket(8888);
             BufferedOutputStream bos = new BufferedOutputStream(
                     new FileOutputStream("d:\\copy.jpg"));
             //獲取客戶端的 socket 對象
             //注意這里獲取的客戶端 Socket 對象由於在 try 小括號內初始化,
             //因此出了 try 代碼塊后,會自動關閉客戶端 Socket 對象
             //這樣客戶端那邊就可以結束了
             Socket accept = ss.accept()) {

            //接收客戶端發來的數據,保存到硬盤中
            //由於該輸入流是從 Socket 中獲取,因此不需要主動關閉,
            //因為 Socket 關閉后,該輸入流會自動關閉
            BufferedInputStream bis = 
                new BufferedInputStream(accept.getInputStream());

            byte[] bArr = new byte[1024];
            int len;
            while ((len = bis.read(bArr)) != -1) {
                bos.write(bArr, 0, len);
            }

            //獲取 Socket 輸出流對象,用於向客戶端發送數據,
            //由於該輸出流是從 Socket 中獲取,因此不需要主動關閉,
            //因為 Socket 關閉后,該輸出流會自動關閉
            BufferedWriter bw = new BufferedWriter(
                new OutputStreamWriter(accept.getOutputStream()));
            bw.write("上傳成功");
            bw.newLine();
            bw.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

上面的服務端代碼,只能接收到一個客戶端的文件上傳,為了能夠支持多客戶端,我們可以采用線程池進行改進,首先我們先寫一個接收客戶端上傳的圖片數據,並保存到硬盤的線程類,代碼如下所示:

import java.io.*;
import java.net.Socket;
import java.util.UUID;

public class ThreadSocket implements Runnable {

    private Socket acceptSocket;

    public ThreadSocket(Socket accept) { this.acceptSocket = accept; }

    @Override
    public void run() {
        //創建字節緩沖流對象,用於向硬盤保存圖片數據
        try (BufferedOutputStream bos = new BufferedOutputStream(
                     new FileOutputStream("d:\\" + UUID.randomUUID() + ".jpg"))) {

            //接收客戶端發送過來的數據
            BufferedInputStream bis = 
                new BufferedInputStream(acceptSocket.getInputStream());

            byte[] bArr = new byte[1024];
            int len;
            while ((len = bis.read(bArr)) != -1) {
                bos.write(bArr, 0, len);
            }

            //向客戶端發送消息
            BufferedWriter bw = new BufferedWriter(
                    new OutputStreamWriter(acceptSocket.getOutputStream()));
            bw.write("圖片已經上傳成功");
            bw.newLine();
            bw.flush();

            //關閉服務器接收到的客戶端 socket 對象,這個很重要,不然的話,客戶端無法結束
            acceptSocket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

然后我們使用線程池,改進服務端代碼,這樣就可以同時接納多個客戶端上傳圖片了,代碼如下所示:

import java.net.*;
import java.util.concurrent.*;

public class ServerDemo {
    public static void main(String[] args) {

        try (ServerSocket ss = new ServerSocket(8888)) {
            //創建線程池
            ThreadPoolExecutor pool = new ThreadPoolExecutor(
                    5,//核心線程數量
                    10,//線程池的總數量
                    20,//臨時線程空閑時間
                    TimeUnit.SECONDS,//臨時線程空閑時間的單位
                    new ArrayBlockingQueue<>(5),//阻塞隊列
                    Executors.defaultThreadFactory(),//創建線程的方式
                    new ThreadPoolExecutor.AbortPolicy()//任務拒絕策略
            );

            while (true) {
                Socket acceptSocket = ss.accept();
                ThreadSocket ts = new ThreadSocket(acceptSocket);
                //向線程池提交任務
                pool.submit(ts);
            }

            //pool.shutdown(); //關閉線程池
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

以上就是簡單的介紹了 Java 有關 UDP 和 TCP 網絡編程的相關技術,其中 UDP 示例是發送文字信息,TCP 示例是上傳圖片文件,並采用線程池進行了改進。

有關 TCP 發送和接收文字信息,由於非常簡單,所以這里就不介紹了。之所以介紹 TCP 上傳圖片文件,是因為讓大家注意客戶端 Scoket 對象發送完數據后,要調用 shutdownOutput 告訴服務端,否則服務端是不知道客戶端已經發送完畢了。

希望以上代碼示例,大家能夠看懂,對大家有所幫助。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM