aliases: [] tags : " " summary: [基於TCP/IP和UDP協議的Java Socket網路通信編程] author : [yaenli] notekey: [20230512-143738] # Socket 網路模型 Socket編程是在TCP/IP、UDP協議上的 ...
aliases: []
tags : " "
summary: [基於TCP/IP和UDP協議的Java Socket網路通信編程]
author : [yaenli]
notekey: [20230512-143738]
Socket 網路模型
Socket編程是在TCP/IP、UDP協議上的網路編程,在此之前,先瞭解下常見的網路模型:
-
OSI七層模型與TCP模型:
-
OSI七層模型詳解(OSI七層模型詳解)
Socket就在應用程式的傳輸層和應用層之間的一個抽象層:
Socket 知識
Socket概述
- 在電腦網路編程技術中,兩個進程或者說兩台電腦可以通過一個網路通信連接實現數據的交換,這種通信鏈路的端點就被稱為“套接字”(Socket)。
- Socket 是網路驅動層提供給應用程式的一個介面或者說一種機制。
- Socket 方便了應用程式訪問通訊協議TCP/IP、UDP 。
- 我們可以把套接字看成是 電話機,有了套接字,才有了通訊的工具。我們可以把IP地址看成是話號碼, 埠號看成是分機號。
Java中Socket的實現
Socket的底層機制非常複雜,Java平臺提供了一些簡單但是強大的類,可以簡單有效地使用Socket開發通信程式而無須瞭解底層機制。
java.net
包提供了若幹支持基於套接字的客戶端/伺服器通信的類:
ServerSocket 類用來創建 TCP/IP 伺服器端;
Socket 類用來創建 TCP/IP 客戶端;
DatagramSocket 類用來實現 UDP 協議的客戶端和伺服器套接字;
DatagramPacket 類用來封裝、處理 UDP 協議的數據包;
InetAddress 類用於封裝IP和DNS等地址信息,在創建數據報報文和 Socket 對象時,可以使用。
Socket 編程
基於TCP/IP協議的Socket編程
(1)分別使用java.net.Socket和ServerSocket來創建客戶端和伺服器端套接字,它們是基於TCP協議進行工作的,工作過程如同打電話的過程,只有雙方都接通了,才能開始通話。
(2)基於TCP創建的套接字叫做 流套接字。Socket通過數據流來完成數據的傳遞工作。
(3)Socket編程中,遵循client-server模型。伺服器端相當於一個監聽器,用來監聽埠。
相關類
- Socket 類:用構造方法創建套接字,並將此套接字連接至指定的主機和埠。
// 常用構造
public Socket(@Nullable String host, int port ) throws UnknownHostException, IOException ;
public Socket(InetAddress address, int port ) throws IOException ;
// 常用方法
public void connect(SocketAddress host, int timeout) throws IOException;// 將此套接字連接到伺服器,並指定一個超時值。
public InetAddress getInetAddress(); // 返回遠程IP信息
public int getPort(); // 返回遠程埠
public int getLocalPort(); // 返回本地埠
public InputStream getInputStream(); // 獲取輸入流
public OutputStream getOutputStream(); // 獲取輸出流
public void close() throws IOException // 關閉此套接字
- ServerSocket 類:等待客戶端建立連接,連接建立以後進行通信。
// 常用構造方法
public ServerSocket(int port) throws IOException; // 創建指定埠的伺服器套接字
public ServerSocket(int port, int backlog) throws IOException; // 指定最大連接隊列
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException; // 指定綁定的本地ip地址
// 常用方法:Socket中的方法都能適用,除此之外,還有以下方法
public Socket accept() throws IOException // (阻塞方法)偵聽連接請求,並返回一個新的通信套接字,該 Socket 連接到客戶端的 Socket
public void bind(SocketAddress host, int backlog) // 將ServerSocket綁定到特定地址
開發流程
詳細交互過程:
服務端編程步驟:
- 實例化
ServerSocket
對象,綁定指定埠; - 調用
accept()
,監聽連接請求(阻塞等待),並返回通信Socket
; - 從
Socket
獲取輸出流輸入流,從輸入流中讀取請求信息,向輸出流中寫入響應信息; - 關閉數據流和通信套接字。
客戶端編程步驟:
- 實例化
Socket
對象,連接到指定伺服器端; - 從
Socket
獲取輸出流輸入流,向輸出流中寫入請求信息,從輸入流中讀取響應信息; - 關閉數據流和通信套接字。
客戶端和伺服器端的交互,採用一問一答的模式,先啟動伺服器進入監聽狀態,等待客戶端的連接請求,連接成功以後,客戶端先 “發言”,伺服器給予 “回應”。
示例代碼
採用多線程的方式,實現一個服務端響應多個客戶端請求。
服務端代碼: 使伺服器端Socket一直處於監聽狀態。伺服器端每監聽到一個請求,創建一個線程對象並啟動。
import java.net.*;
import java.io.*;
public class SocketServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
// 建立一個伺服器Socket(ServerSocket)指定埠並開始監聽
serverSocket = new ServerSocket(8800);
// 監聽一直進行中
while (true) {
// 使用accept()方法等待客戶發起通信
Socket socket = serverSocket.accept();
new SocketThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.net.*;
import java.io.*;
public class SocketThread extends Thread {
/*
* (1)創建伺服器端線程類,run()方法中實現對一個請求的響應處理。
* (2)讓伺服器端Socket一直處於監聽狀態。
* (3)伺服器端每監聽到一個請求,創建一個線程對象並啟動
*/
Socket socket = null;
//每啟動一個線程,連接對應的Socket
public LoginThread(Socket socket) {
this.socket = socket;
}
//啟動線程,即響應客戶請求
public void run() {
InputStream is = null;
ObjectInputStream ois = null;
OutputStream os = null;
try {
//打開輸入流
is = socket.getInputStream();
//反序列化
ois = new ObjectInputStream(is);
//獲取客戶端信息,即從輸入流讀取信息,DataObject為自定義數據類
DataObject data = (DataObject)ois.readObject();
if(data!=null){
System.out.println("我是服務端,客戶端傳送信息為:" + data.getMessage());
}
//給客戶端一個響應,即向輸出流中寫入信息
os = socket.getOutputStream();
String reply = "服務端接收成功!";
os.write(reply.getBytes());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally{
try {
os.close();
ois.close();
is.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客戶端代碼:
import java.net.*;
import java.io.*;
public class SocketClient {
/*
* 客戶端通過輸出流向伺服器端發送請求信息
* 伺服器偵聽客戶端的請求得到一個Socket對象,將這個Socket對象傳遞給線程類
* 線程類通過輸入流獲取客戶端的請求並通過輸出流向客戶端發送響應信息
* 客戶端通過輸入流讀取伺服器發送的響應信息
*
*/
public static void main(String[] args) {
Socket socket = null;
OutputStream os = null;
ObjectOutputStream oos = null;
InputStream is = null;
BufferedReader br = null;
try {
// 建立客戶端Socket連接,指定伺服器的位置為本機以及埠為8800
socket = new Socket("localhost", 8800);
// 打開輸出流
os = socket.getOutputStream();
// 對象序列化
oos = new ObjectOutputStream(os);
// 發送客戶端信息,即向輸出流中寫入信息,DataObject為自定義數據類
DataObject data = new DataObject("服務端你好,我是客戶端");
oos.writeObject(data);
socket.shutdownOutput();
// 接收伺服器端的響應,即從輸入流中讀取信息
is = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
String reply;
while ((reply = br.readLine()) != null) {
System.out.println("我是客戶端,伺服器的響應為:" + reply);
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
br.close();
is.close();
oos.close();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
基於UDP的socket編程
(1)基於TCP的網路通信是安全的,是雙向的,如同打電話,需要先有服務端,建立雙向連接後,才開始數據通信。
(2)基於UDP的套接字就是 數據報套接字。數據報是表示通信的一種報文類型,使用數據報進行通信時無須事先建立連接,只需要指明對方地址,然後將數據送出去。這樣的網路通信是不安全的,所以只應用在如聊天系統、咨詢系統等場合下。
(3)兩端都要先構造好相應的數據包。數據報套接字發送成功之後,就相當於建立了一個虛連接,雙方可以發送數據。
(4)Java中有兩個可使用數據報實現通信的類,即DatagramPacket和DatagramSocket。
(5)DatagramPacket類起到數據容器的作用,DatagramSocket類用於發送或接收DatagramPacket,以此實現數據報通信。
UDP與TCP通信的區別:
TCP | UDP | |
---|---|---|
是否連接 | 面向連接 | 面向非連接 |
傳輸可靠性 | 可靠 | 不可靠 |
速度 | 慢 | 快 |
相關類
- java.net. DatagramPacket :數據電報包,用於封裝發送的數據。
- java.net. DatagramSocket :數據電報套接字,不維護連接狀態,不產生輸入/輸出數據流,用於接收和發送DatagramPacket對象封裝好的數據報。
開發流程
UDP通信的兩個端點程式是平等的,沒有主次之分,甚至它們的代碼都可以完全是一樣的。
接收端編程步驟:
- 實例化
DatagramSocket
創建數據報套接字,綁定到指定埠; - 實例化
DatagramPacket
建立要接收的UDP包; - 調用
DatagramSocket.receive()
,接收UDP包; - 處理接收到的
DatagramPacket
數據包,關閉數據報套接字。
發送端編程步驟:
- 實例化
DatagramSocket
創建數據報套接字,綁定到指定埠; - 實例化
DatagramPacket
建立要發送的UDP包; - 調用
DatagramSocket.send()
,發送UDP包; - 關閉數據報套接字。
示例代碼
發送方發送咨詢問題,接收方回應咨詢。
接收端代碼:
import java.net.*;
import java.io.*;
public class UDPReceive {
public static void main(String[] args) {
/*
* 接收方實現步驟如下:
* (1)創建DatagramPacket對象,準備接收封裝的數據。
* (2)創建DatagramSocket對象,接收數據保存於DatagramPacket對象中。
* (3)利用DatagramPacket對象處理數據。
*/
DatagramSocket ds = null;
DatagramPacket dp = null;
DatagramPacket dp_reply = null;
// 創建DatagramPacket對象,用來準備接收數據
byte[] buf = new byte[1024];
dp = new DatagramPacket(buf, 1024);
try {
// 創建DatagramSocket對象,接收數據
ds = new DatagramSocket(8800);
ds.receive(dp);
// 顯示接收到的信息
String mess = new String(dp.getData(), 0, dp.getLength());
System.out.println(dp.getAddress().getHostAddress() + "說:" + mess);
// 給發送端返回數據,需要發送端去接受
String reply = "接收端:你好,我在,請咨詢!";
// 創建DatagramPacket對象,封裝數據
dp_reply = new DatagramPacket(reply.getBytes(),
reply.getBytes().length, dp.getSocketAddress());
ds.send(dp_reply);
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
ds.close();
}
}
}
發送端代碼:
import java.net.*;
import java.io.*;
public class UDPSend {
/*
* 發送方實現步驟如下:
* (1)獲取本地主機的InetAddress對象。
* (2)創建DatagramPacket對象,封裝要發送的信息。
* (3)利用DatagramSocket對象將DatagramPacket對象數據發送出去。
*/
public static void main(String[] args) {
DatagramSocket ds = null;
InetAddress ia = null;
String mess = "發送端:你好,我想咨詢一個問題。";
try {
// 獲取本地主機地址
ia = InetAddress.getByName("localhost");
// 創建DatagramPacket對象,封裝數據
DatagramPacket dp = new DatagramPacket(mess.getBytes(),
mess.getBytes().length, ia, 8800);
// 創建DatagramSocket對象,向伺服器發送數據
ds = new DatagramSocket();
ds.send(dp);
//接受返回來的數據。
byte[] buf = new byte[1024];
DatagramPacket dpre = new DatagramPacket(buf, buf.length);
ds.receive(dpre);
// 顯示接收到的信息
String reply = new String(dpre.getData(), 0, dpre.getLength());
System.out.println(dpre.getAddress().getHostAddress() + "說:"
+ reply);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
ds.close();
}
}
}
參考文章
Java網路編程——Socket 編程
Java---Socket編程UDP/TCP-CSDN博客
JAVA進階——Socket編程-CSDN博客
Java Socket實現簡單的Http伺服器