Socket(插座)是網路通訊的端點,提供一種機制,使得兩個程式可以在網路上進行資料交換。
在 Java 中,Socket 是一個類別,封裝底層的網路通訊細節,讓開發者能夠專注於應用邏輯的實現。
在網路通訊模型中,Socket 扮演著關鍵的角色:
抽象化網路通訊:Socket 將複雜的網路通訊過程抽象化,提供一個簡單的介面供應用程式使用。
建立點對點連接:Socket 允許兩個應用程式之間建立直接的連接,實現資料的雙向傳輸。
支援多種協定:Java 的 Socket API 支援多種網路協定,最常用的是 TCP(傳輸控制協定)和 UDP(使用者資料包協定)。
跨平台兼容:Java 的 Socket 程式設計提供跨平台的一致性,使得開發者可以編寫一次程式碼,在不同的作業系統上運行。
IP 位址:
InetAddress
類別來處理 IP 位址。連接埠(Port):
Java 提供 java.net.Socket
類別來實現 TCP 通訊,以及 java.net.DatagramSocket
類別來實現 UDP 通訊。
connect(SocketAddress endpoint)
:連接到指定的伺服器。getInputStream()
:獲取輸入串流。getOutputStream()
:獲取輸出串流。close()
:關閉 Socket 連接。accept()
:等待並接受客戶端連接。close()
:關閉伺服器 Socket。send(DatagramPacket p)
:發送資料包。receive(DatagramPacket p)
:接收資料包。TCP(傳輸控制協定)是一種可靠的、面向連接的協定。在 Java 中,我們使用 java.net.Socket
和 java.net.ServerSocket
類別來實現 TCP 通訊。
建立一個 TCP 伺服器的基本步驟如下:
ServerSocket
物件,指定監聽的連接埠。accept()
方法等待客戶端連接。以下是簡單的 TCP 伺服器範例:
import java.io.*;
import java.net.*;
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(6789);
System.out.println("Server is listening on port 6789");
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("Client connected: " + clientSocket.getInetAddress());
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("Received: " + inputLine);
out.println("Server received: " + inputLine);
}
clientSocket.close();
}
} finally {
if (serverSocket != null) serverSocket.close();
}
}
}
建立一個 TCP 客戶端的基本步驟如下:
Socket
物件,指定伺服器的 IP 位址和連接埠。以下是簡單的 TCP 客戶端範例:
import java.io.*;
import java.net.*;
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket socket = null;
try {
socket = new Socket("localhost", 6789);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
String userInput;
while ((userInput = stdIn.readLine()) != null) {
out.println(userInput);
System.out.println("Server response: " + in.readLine());
}
} finally {
if (socket != null) socket.close();
}
}
}
讓我們來看一個完整的 TCP 伺服器和客戶端通訊的範例,實現簡單的回音(Echo)服務,客戶端發送訊息,伺服器將接收到的訊息回傳給客戶端。
伺服器端程式碼:
import java.io.*;
import java.net.*;
public class EchoServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(6789);
System.out.println("Echo Server is listening on port 6789");
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("Client connected: " + clientSocket.getInetAddress());
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("Echoing: " + inputLine);
out.println(inputLine);
}
clientSocket.close();
}
} finally {
if (serverSocket != null) serverSocket.close();
}
}
}
客戶端程式碼:
import java.io.*;
import java.net.*;
public class EchoClient {
public static void main(String[] args) throws IOException {
Socket socket = null;
try {
socket = new Socket("localhost", 6789);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
String userInput;
System.out.println("Enter messages to echo (type 'exit' to quit):");
while ((userInput = stdIn.readLine()) != null) {
if ("exit".equalsIgnoreCase(userInput)) break;
out.println(userInput);
System.out.println("Echo: " + in.readLine());
}
} finally {
if (socket != null) socket.close();
}
}
}
UDP(使用者資料包協定)是一種無連接的傳輸協定,相較於 TCP,更輕量但不保證資料的可靠傳輸。
在 Java 中,我們使用 java.net.DatagramSocket
和 java.net.DatagramPacket
類別來實現 UDP 通訊。
建立一個 UDP 伺服器的基本步驟如下:
DatagramSocket
物件,指定監聽的連接埠。DatagramPacket
物件來接收資料。receive()
方法接收資料包。DatagramPacket
並使用 send()
方法發送。以下是簡單的 UDP 伺服器範例:
import java.net.*;
public class UDPServer {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(9876);
byte[] receiveData = new byte[1024];
while(true) {
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
socket.receive(receivePacket);
String sentence = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("RECEIVED: " + sentence);
InetAddress IPAddress = receivePacket.getAddress();
int port = receivePacket.getPort();
String capitalizedSentence = sentence.toUpperCase();
byte[] sendData = capitalizedSentence.getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, port);
socket.send(sendPacket);
}
}
}
建立一個 UDP 客戶端的基本步驟如下:
DatagramSocket
物件。DatagramPacket
物件,包含要發送的資料、目標 IP 和連接埠。send()
方法發送資料包。receive()
方法。以下是簡單的 UDP 客戶端範例:
import java.io.*;
import java.net.*;
public class UDPClient {
public static void main(String args[]) throws Exception {
BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in));
DatagramSocket clientSocket = new DatagramSocket();
InetAddress IPAddress = InetAddress.getByName("localhost");
byte[] sendData = new byte[1024];
byte[] receiveData = new byte[1024];
System.out.println("Enter a message:");
String sentence = inFromUser.readLine();
sendData = sentence.getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, 9876);
clientSocket.send(sendPacket);
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
clientSocket.receive(receivePacket);
String modifiedSentence = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("FROM SERVER:" + modifiedSentence);
clientSocket.close();
}
}
將實現一個簡單的時間查詢服務,客戶端發送請求,伺服器回傳當前時間。
伺服器端程式碼:
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TimeServer {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(9876);
byte[] receiveData = new byte[1024];
while(true) {
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
socket.receive(receivePacket);
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String currentTime = formatter.format(new Date());
InetAddress IPAddress = receivePacket.getAddress();
int port = receivePacket.getPort();
byte[] sendData = currentTime.getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, port);
socket.send(sendPacket);
}
}
}
客戶端程式碼:
import java.net.*;
public class TimeClient {
public static void main(String args[]) throws Exception {
DatagramSocket clientSocket = new DatagramSocket();
InetAddress IPAddress = InetAddress.getByName("localhost");
byte[] sendData = new byte[1024];
byte[] receiveData = new byte[1024];
String sentence = "GET_TIME";
sendData = sentence.getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, 9876);
clientSocket.send(sendPacket);
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
clientSocket.receive(receivePacket);
String time = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("Current time from server: " + time);
clientSocket.close();
}
}
以下是結合上述的實踐建議所撰寫的簡單 TCP 伺服器範例:
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
import javax.net.ssl.*;
import java.security.*;
public class BestPracticeTCPServer {
private static final int PORT = 8888;
private static final int TIMEOUT = 30000; // 30 seconds
private static final int MAX_CONNECTIONS = 100;
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(MAX_CONNECTIONS);
try (ServerSocket serverSocket = createSSLServerSocket()) {
serverSocket.setSoTimeout(TIMEOUT);
System.out.println("Server is listening on port " + PORT);
while (true) {
try {
Socket clientSocket = serverSocket.accept();
executor.submit(new ClientHandler(clientSocket));
} catch (SocketTimeoutException e) {
System.out.println("Timeout waiting for connection, continuing...");
}
}
} catch (IOException e) {
System.err.println("Could not listen on port " + PORT);
e.printStackTrace();
} finally {
executor.shutdown();
}
}
private static SSLServerSocket createSSLServerSocket() throws IOException {
try {
// 加載金鑰庫
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream("keystore.jks"), "keystorepassword".toCharArray());
// 創建並初始化金鑰管理器
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "keystorepassword".toCharArray());
// 創建並初始化 SSL 上下文
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, null);
// 創建 SSL 伺服器 socket
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
return (SSLServerSocket) sslServerSocketFactory.createServerSocket(PORT);
} catch (Exception e) {
throw new IOException("Failed to create SSL server socket", e);
}
}
private static class ClientHandler implements Runnable {
private final Socket clientSocket;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try (
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)
) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("Received: " + inputLine);
out.println("Server received: " + inputLine);
if ("bye".equalsIgnoreCase(inputLine)) {
break;
}
}
} catch (IOException e) {
System.err.println("Error handling client: " + e.getMessage());
} finally {
try {
clientSocket.close();
} catch (IOException e) {
System.err.println("Error closing client socket: " + e.getMessage());
}
}
}
}
}
本篇文章同步刊載: JYI.TW
筆者個人的網站: JUNYI