1. 程式人生 > 其它 >Java 網路程式設計技術總結

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();
                //準備給本機的 888 埠,傳送 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 告訴服務端,否則服務端是不知道客戶端已經發送完畢了。

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