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