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 告訴服務端,否則服務端是不知道客戶端已經發送完畢了。
希望以上程式碼示例,大家能夠看懂,對大家有所幫助。