1. 什么是網(wǎng)絡(luò)編程
網(wǎng)絡(luò)編程是指網(wǎng)絡(luò)上的主機(jī),通過不同的進(jìn)程,以編程的方式實(shí)現(xiàn) 網(wǎng)絡(luò)通信(或稱為網(wǎng)絡(luò)數(shù)據(jù)傳輸)
只要滿足不同的進(jìn)程就可以進(jìn)行通信,所以即便是在同一個(gè)主機(jī),只要不同的進(jìn)程,基于網(wǎng)絡(luò)傳輸數(shù)據(jù),也屬于網(wǎng)絡(luò)編程
2. 網(wǎng)絡(luò)編程中的基本概念
1)發(fā)送端和接收端
在一次網(wǎng)絡(luò)傳輸中:
發(fā)送端: 數(shù)據(jù)的 發(fā)送方進(jìn)程,稱為發(fā)送端。發(fā)送端主機(jī)即網(wǎng)絡(luò)通信中的源主機(jī)
接收端: 數(shù)據(jù)的 接收方進(jìn)程,稱為接收端。接收端主機(jī)即網(wǎng)絡(luò)通信中的目的主機(jī)
收發(fā)端: 發(fā)送端和接收端兩端,簡稱為收發(fā)端
注意: 發(fā)送端和接收端只是相對的,只是一次網(wǎng)絡(luò)數(shù)據(jù)傳輸產(chǎn)生數(shù)據(jù)流向后的概念
2)請求和響應(yīng)
一般來說,獲取一個(gè)網(wǎng)絡(luò)資源時(shí),涉及到兩次網(wǎng)絡(luò)數(shù)據(jù)傳輸
- 第一次:請求數(shù)據(jù)的發(fā)送
- 第二次:響應(yīng)數(shù)據(jù)的發(fā)送
就好比在飯店里點(diǎn)了一碗面
顧客先發(fā)起請求:來一碗面
飯店再做出響應(yīng):提供一碗面
3)客戶端和服務(wù)端
服務(wù)端: 在常見的網(wǎng)絡(luò)傳輸場景下,把 提供服務(wù) 的一方進(jìn)程,稱為服務(wù)端,可以對外提供服務(wù)
客戶端: 獲取服務(wù) 的一方進(jìn)程,稱為客戶端
對于服務(wù)來說,一般是提供:
- 客戶端獲取服務(wù)資源
- 客戶端保存資源到服務(wù)端
4)常見的客戶端服務(wù)端模型
在常見的場景中,客戶端是指給用戶使用的程序,服務(wù)端是指提供用戶服務(wù)的程序,具體流程如下:
- 客戶端發(fā)送請求到服務(wù)端
- 服務(wù)端根據(jù)請求數(shù)據(jù),執(zhí)行相應(yīng)的業(yè)務(wù)處理
- 服務(wù)端返回響應(yīng):發(fā)送業(yè)務(wù)處理結(jié)果
- 客戶端根據(jù)響應(yīng)數(shù)據(jù),展示處理結(jié)果
3. Socket 套接字
Socket 套接字時(shí)由系統(tǒng)提供用于網(wǎng)絡(luò)通信的技術(shù),是基于 TCP/IP 協(xié)議的網(wǎng)絡(luò)通信的基本操作單元。基于 Socket 套接字的網(wǎng)絡(luò)程序開發(fā)就是網(wǎng)絡(luò)編程
1)Socket 的分類
常用的 Socket 套接字有以下兩類:
-
流套接字: 使用 TCP 協(xié)議
傳輸數(shù)據(jù)基于 IO 流,流式數(shù)據(jù)的特征就是在 IO 流沒有關(guān)閉的情況下,是無邊界的數(shù)據(jù),可以多次發(fā)送數(shù)據(jù),也可以分開多次接收
-
數(shù)據(jù)報(bào)套接字: 使用 UDP 協(xié)議
傳輸數(shù)據(jù)是一塊一塊的,每一塊數(shù)據(jù)必須一次性發(fā)送,接受也必須一次性接受,不能分開發(fā)送或接收
2)Java 數(shù)據(jù)報(bào)套接字通信模型
UDP 協(xié)議具有無連接、面向數(shù)據(jù)報(bào)的特征,即每次傳輸都是沒有建立連接,并且一次發(fā)送全部數(shù)據(jù),一次接收全部數(shù)據(jù)
Java 中使用 UDP 協(xié)議通信時(shí),主要基于 DatagramSocket 類來創(chuàng)建數(shù)據(jù)報(bào)套接字,并使用 DatagramPacket 作為發(fā)送或者接受的數(shù)據(jù)報(bào)。
具體流程如下:
3)Java 流套接字通信模型
TCP 協(xié)議具有有連接、面向字節(jié)流的特征,即傳輸數(shù)據(jù)之前要先建立連接,并且數(shù)據(jù)的傳輸是基于 IO 流的
具體流程如下:
4. UDP 數(shù)據(jù)報(bào)套接字編程
1)DatagramSocket API
DatagramSocket 是 UDP Socket,用于發(fā)送和接收 UDP 數(shù)據(jù)報(bào)
構(gòu)造方法:
方法簽名 | 方法說明 |
---|---|
DatagramSocket() | 創(chuàng)建一個(gè) UDP 數(shù)據(jù)報(bào)套接字的 Socket,綁定到本機(jī)任意一個(gè)隨機(jī)端口 |
DatagramSocket(int port) | 創(chuàng)建一個(gè) UDP 數(shù)據(jù)報(bào)套接字的 Socket,綁定到本機(jī)的指定端口上 |
實(shí)例方法:
方法簽名 | 方法說明 |
---|---|
void receive(DatagramPacket p) | 接收數(shù)據(jù)報(bào)到提前構(gòu)造的 DatagramPacket 對象中(如果沒有接收到數(shù)據(jù)報(bào),該方法會阻塞等待) |
void send(DatagramPacket p) | 發(fā)送提前構(gòu)造的 DatagramPacket 數(shù)據(jù)報(bào) |
void close() | 關(guān)閉套接字 |
2)DatagramPacket API
DatagramPacket 是 UDP Socket 發(fā)送和接收的數(shù)據(jù)報(bào)
構(gòu)造方法:
方法簽名 | 方法說明 |
---|---|
DatagramPacket(byte[] buf, int length) | 構(gòu)造一個(gè) DatagramPacket 對象用來接收數(shù)據(jù)報(bào),接收的數(shù)據(jù)保存到字節(jié)數(shù)組中 |
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) | 構(gòu)造 DatagramPacket 用來發(fā)送數(shù)據(jù)報(bào),發(fā)送的數(shù)據(jù)為字節(jié)數(shù)組中的數(shù)據(jù)。指定目的主機(jī)的 IP 和端口號 |
實(shí)例方法:
方法簽名 | 方法說明 |
---|---|
InetAddress getAddress() | 從數(shù)據(jù)報(bào)中,獲取 IP 地址 |
int getPort() | 從數(shù)據(jù)報(bào)中,獲取端口號 |
byte[] getData() | 獲取數(shù)據(jù)報(bào)中的數(shù)據(jù) |
3)示例
使用 UDP Socket 套接字完成一個(gè)簡單的翻譯小程序
客戶端向服務(wù)端發(fā)起翻譯請求,服務(wù)端接收到請求后,對請求進(jìn)行處理,再向客戶端做出響應(yīng)
服務(wù)端
服務(wù)端接收到請求之后,在我們?nèi)藶橐?guī)定的 map 中進(jìn)行查詢,再對客戶端做出響應(yīng)
public class Server {
private final static HashMap<String, String> map = new HashMap<>();
static {
map.put("蘋果", "apple");
map.put("香蕉", "banana");
map.put("梨", "pear");
map.put("電腦", "computer");
map.put("手機(jī)", "phone");
}
public static void main(String[] args) throws IOException {
// 得到 socket 對象,并對其綁定端口號
Log.println("服務(wù)器開啟并且綁定了 8080 端口");
DatagramSocket socket = new DatagramSocket(8080);
// 循環(huán)接受和處理請求
while (true) {
// 準(zhǔn)備接收數(shù)據(jù)的容器
byte[] buf = new byte[1024];
// 包裝數(shù)據(jù)包
DatagramPacket received = new DatagramPacket(buf, 0, 1024);
// 接收請求
Log.println("服務(wù)器準(zhǔn)備接受請求");
socket.receive(received);
Log.println("服務(wù)器接受到了請求");
// 處理請求
InetAddress address = received.getAddress(); // 得到發(fā)起請求方的 IP
Log.println("對方的地址:" + address);
int port = received.getPort(); // 得到發(fā)起請求方的端口號
Log.println("對方的端口:" + port);
int length = received.getLength(); // 得到請求中真實(shí)有效的數(shù)據(jù)長度
Log.println("真實(shí)的數(shù)據(jù)長度:" + length);
byte[] realData = Arrays.copyOf(buf, length); // 得到請求中真實(shí)有效的數(shù)據(jù)
// 將請求數(shù)據(jù)轉(zhuǎn)換成 String
String request = new String(realData, 0, length, StandardCharsets.UTF_8);
Log.println("請求中的數(shù)據(jù):\r\n" + request);
// 人為規(guī)定請求和相應(yīng)的格式
// 請求格式必須以 “我是請求:\r\n” 開始,以 “\r\n” 結(jié)束
if (!request.startsWith("我是請求:\r\n")) {
Log.println("請求格式出錯(cuò)");
// 請求出錯(cuò)
continue;
}
if (!request.endsWith("\r\n")) {
Log.println("請求格式出錯(cuò)");
// 請求出錯(cuò)
continue;
}
// 得到請求中的英文單詞
String EnglishWord = request.substring("我是請求:\r\n".length(), request.length() - 2);
Log.println("請求中的英文單詞:" + EnglishWord);
// 進(jìn)行翻譯
String ChineseWord = map.getOrDefault(EnglishWord, "我不知道");
Log.println("翻譯后的中文:" + ChineseWord);
// 將翻譯后的結(jié)果進(jìn)行包裝
String response = String.format(ChineseWord, "%s\r\n");
byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
// 包裝數(shù)據(jù)包
DatagramPacket send = new DatagramPacket(bytes, 0, bytes.length, address, port);
// 發(fā)起響應(yīng)
Log.println("準(zhǔn)備發(fā)送響應(yīng)");
socket.send(send);
Log.println("相應(yīng)發(fā)送成功");
}
}
}
客戶端
public class Client {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
DatagramSocket socket = new DatagramSocket(9999);
while (sc.hasNextLine()) {
String str = sc.nextLine();
String send = "我是請求:\r\n" + str + "\r\n";
byte[] bytes = send.getBytes();
DatagramPacket request = new DatagramPacket(bytes, 0, bytes.length, InetAddress.getLoopbackAddress(), 8080);
socket.send(request);
// 接收響應(yīng)
byte[] buf = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buf, 0, 1024);
socket.receive(datagramPacket);
int n = datagramPacket.getLength();
String response = new String(buf, 0, n, StandardCharsets.UTF_8);
Log.println(response);
}
}
}
5. TCP 流套接字編程
1)ServerSocket API
用于創(chuàng)建 TCP 服務(wù)端 Socket 的 API
構(gòu)造方法:
方法簽名 | 方法說明 |
---|---|
ServerSocket(int port) | 創(chuàng)建一個(gè)服務(wù)端流套接字 Socket,并綁定到指定端口 |
實(shí)例方法:
方法簽名 | 方法說明 |
---|---|
Socket accept() | 等待客戶端發(fā)起連接,連接成功后返回一個(gè) Socket 對象 |
void close() | 關(guān)閉 Socket |
2)Socket API
這里的 Socket 是客戶端 Socket,或服務(wù)端中接收到客戶端連接請求后,accept 方法返回的服務(wù)端 Socket
不管是客戶端還是服務(wù)端 Socket,都是雙方建立連接后,保存對端信息,以及用來與對方收發(fā)數(shù)據(jù)的
構(gòu)造方法:
方法簽名 | 方法說明 |
---|---|
Socket(String host, int port) | 創(chuàng)建一個(gè)客戶端流套接字,并與對應(yīng) IP 的主機(jī),對應(yīng)端口的進(jìn)程建立連接 |
實(shí)例方法:
方法簽名 | 方法說明 |
---|---|
InetAddress getInetAddress() | 返回套接字所連接的 IP 地址 |
InputStream getInputStream() | 返回套接字的輸入流 |
OutputStream getOutputStream() | 返回套接字的輸出流 |
3)示例
使用 TCP Socket 套接字完成一個(gè)簡單的翻譯小程序
完成的效果和上文 UDP 的小程序效果一樣
不過在 TCP 這里有長短連接的區(qū)分,所以有兩個(gè)版本的代碼,在長連接版本中加入了并發(fā)編程,使得同一個(gè)服務(wù)端可以被多個(gè)客戶端所連接
a. 短連接版本
客戶端:
public class 短連接版本Client {
public static void main(String[] args) throws IOException {
for (int i = 0; i < 3; i++) {
Socket socket = new Socket("127.0.0.1", 8080); // 撥號
OutputStream os = socket.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(os, "UTF-8");
PrintWriter printWriter = new PrintWriter(writer);
printWriter.printf("我是請求\r\n%s\r\n", "蘋果");
printWriter.flush();
InputStream is = socket.getInputStream();
Scanner scanner = new Scanner(is, "UTF-8");
String header = scanner.nextLine();
String word = scanner.nextLine();
System.out.println(word);
socket.close(); // 掛電話
}
}
}
服務(wù)端:
public class 短連接版本Server {
private final static HashMap<String, String> map = new HashMap<>();
static {
map.put("蘋果", "apple");
map.put("香蕉", "banana");
map.put("梨", "pear");
map.put("電腦", "computer");
map.put("手機(jī)", "phone");
}
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
while(true) {
Log.println("等待客戶端建立連接");
Socket socket = serverSocket.accept();
Log.println("客戶端連接成功");
InetAddress address = socket.getInetAddress();
Log.println("客戶端 IP 地址:" + address);
int port = socket.getPort();
Log.println("客戶端端口號:" + port);
InputStream is = socket.getInputStream();
Scanner sc = new Scanner(is, "UTF-8");
String header = sc.nextLine();
String EnglishWord = sc.nextLine();
Log.println("請求中的英文單詞為:" + EnglishWord);
String ChineseWord = map.getOrDefault(EnglishWord, "我不知道");
Log.println("翻譯后的中文為:" + ChineseWord);
Log.println("準(zhǔn)備響應(yīng)");
OutputStream os = socket.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(os);
PrintWriter printWriter = new PrintWriter(writer);
printWriter.printf("OK!\r\n%s\t\n", ChineseWord);
printWriter.flush();
Log.println("響應(yīng)成功");
socket.close();
Log.println("斷開連接");
}
}
}
b. 長連接并發(fā)版本
在長連接版本中,必須服務(wù)端和客戶端同時(shí)支持長連接才可以進(jìn)行通信
如果不清楚長連接和短連接,可以去看看我上篇文章,里面有介紹長連接和短連接,HTTP 和 HTTPS,有介紹 HTTP 的長短連接,實(shí)質(zhì)上就是 TCP 長短連接
客戶端:
public class Client {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
Socket socket = new Socket("127.0.0.1", 8080);
OutputStream os = socket.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(os, StandardCharsets.UTF_8);
PrintWriter printWriter = new PrintWriter(writer);
InputStream is = socket.getInputStream();
Scanner scanner = new Scanner(is, "UTF-8");
while (sc.hasNextLine()) {
String str = sc.nextLine();
printWriter.printf("我是請求:\r\n%s\r\n", str);
printWriter.flush();
// 接收響應(yīng)
String header = scanner.nextLine();
String response = scanner.nextLine();
Log.println(header + "\r\n" + response);
}
socket.close();
}
}
服務(wù)端:文章來源:http://www.zghlxwxcb.cn/news/detail-438597.html
public class Server {
private final static HashMap<String, String> map = new HashMap<>();
static {
map.put("蘋果", "apple");
map.put("香蕉", "banana");
map.put("梨", "pear");
map.put("電腦", "computer");
map.put("手機(jī)", "phone");
}
private final static class ServerTask implements Runnable {
private final Socket socket;
private ServerTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
InetAddress address = socket.getInetAddress();
Log.println("客戶端 IP 地址為:" + address);
int port = socket.getPort();
Log.println("客戶端端口號為:" + port);
try {
InputStream is = socket.getInputStream();
// 請求格式必須以 “我是請求:\r\n” 開始,以 “\r\n” 結(jié)束
Scanner sc = new Scanner(is, "UTF-8");
// 進(jìn)行響應(yīng)
OutputStream os = socket.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(os, StandardCharsets.UTF_8);
PrintWriter printWriter = new PrintWriter(writer);
while (sc.hasNextLine()) {
String header = sc.nextLine();
String EnglishWord = sc.nextLine();
Log.println("請求中的詞為:" + EnglishWord);
// 對請求進(jìn)行處理
String ChineseWord = map.getOrDefault(EnglishWord, "我不知道");
Log.println("翻譯后的詞:" + ChineseWord);
Log.println("準(zhǔn)備發(fā)起響應(yīng)");
printWriter.printf("OK!\r\n%s\r\n", ChineseWord);
printWriter.flush();
Log.println("響應(yīng)成功");
}
socket.close();
Log.println("連接已關(guān)閉");
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
// 使用多線程完成多用戶同時(shí)在線的功能
ExecutorService threadPool = Executors.newFixedThreadPool(3);
ServerSocket serverSocket = new ServerSocket(8080);
Log.println("綁定端口到 8080");
while (true) {
Log.println("等待客戶端連接");
Socket socket = serverSocket.accept();
Log.println("客戶端連接成功");
ServerTask serverTask = new ServerTask(socket);
threadPool.execute(serverTask);
}
}
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-438597.html
到了這里,關(guān)于網(wǎng)絡(luò)編程之 Socket 套接字(使用數(shù)據(jù)報(bào)套接字和流套接字分別實(shí)現(xiàn)一個(gè)小程序(附源碼))的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!