1. 網絡基本概念
1.1 計算機網絡
定義:是指将地理位置不同的具有獨立功能(沒有網絡可以獨立存在的)的多台計算機及其外部裝置,通過通信線路連接配接起來,在網絡作業系統,網絡管理軟體及網絡通信協定的管理和協調下,實作資源共享和資訊傳遞的計算機系統
- 主幹:計算機網絡是計算機系統
- 網絡功能:資源共享、資訊傳遞
- 網絡組成
- 網絡硬體:計算機、外部裝置、通信連接配接
- 網絡軟體:網絡作業系統、網絡管理軟體、網絡通信協定
分類–按照規模:
- 區域網路 LAN
- 城域網 MAN
- 廣域網 WAN
分類–按照傳輸媒體:
- 同軸電纜網絡(類似于有線電視網的電纜)
- 雙絞線網絡
- 光纖網絡(傳輸的為光信号)
- 衛星網絡
分類–拓撲結構:
- 星形網絡(最常使用)
- 總線網絡:信号在傳遞過程中都能收到,辨識是自己的則接收
- 環狀網絡:同樣的傳遞方式
1.2 網絡通信協定
在網絡中實作通信,必須要有一些約定(通信協定),對速率、傳輸代碼、傳輸控制步驟等指定标準(好比交通規則)
問題:網絡通信涉及内容很多:源位址、目标位址、加密解密、流量控制、路由控制等,如何實作如此複雜的網絡協定?
===》
分層
:将複雜的成份分解成一些簡單的成份,再将它們複合起來(同層間可以通信、上一層可以調用下一層,而不與再下一層發生關系)
網絡通信協定分層:
- 名義上的标準:ISO --> OSI 參考模型
- 事實上的标準:TCP/IP協定棧
資料的封裝與拆封:在傳輸過程中,經過每一層,都需要添加各種資料,最終發送到另一端,另一端再拆解這些資料
1.3 TCP/IP協定棧
網絡通信最常采用的協定
- 網絡層主要協定:IP協定
- 傳輸層主要協定:TCP 和 UDP 協定
1.4 TCP協定
面向連接配接的、可靠的、基于位元組流的傳輸層通信協定(打電話的案例)
- 面向連接配接(一段資訊分段後發送,發送順序和接收順序一緻)
- 點到點的通信
- 高可靠性:三次握手
- 占用系統資源多、效率低
應用案列:HTTP、FTP、Telnet、SMTP
1.5 UDP協定
無連接配接的傳輸層協定,提供面向事務的簡單不可靠資訊傳送服務(發電報、發送群發短信)
- 非面向連接配接,傳輸不可靠,可能丢失(一段資訊分段後發送,不一定哪一段先到達)
- 發送不管對方是否準備好,接收方收到後也不回複
- 可以廣播發送
- 非常簡單的協定,開銷少
應用案例:DNS、SNMP
1.6 IP位址和端口
IP位址,用來标志網絡中的一個通信實體(計算機、路由器)的位址
分類:
- IPV4:32位位址,點分十進制表示,如192.168.0.1
- IPV6:128位寫個8個16位的無符号整數,每個整數用4個十六進制位辨別,數之間用 : 分割
特殊的 IP 位址:
- 127.0.0.1:本機位址
- 192.168.0.0 – 192.168.255.255 私有位址,專門為組織機構内部使用
端口(port):
- IP位址用來标志一台計算機,但一個計算機可以提供多種應用程式,使用端口來區分應用程式
- 範圍:0 – 65535(16位整數)
端口分類:
- 0 – 1023 :公認端口(比如 80給了 WWW,21給了 FTP等)
- 1024 – 49151:注冊端口(配置設定給使用者或應用程式)
- 49152 – 65535:動态/私有端口
IP和端口API:
- InetAddress 類:封裝計算機的 ip位址,沒有端口
- InetSocketAddress:包含端口,用于 socket 通信
1.7 URL 統一資源定位符
Uniform Resource Locator:由 4部分組成:協定、存放資源的主機域名、端口号、資源檔案名
1.8 Socket 套接字
Socket實際是傳輸層供給應用層的程式設計接口
類似于寄信:使用者(應用層)将信(資料)投入郵筒即可(郵筒的口,就是socket),進入Socket之後,怎麼送信就是郵局、公路交管(傳輸層、網絡層)等的事。
2. 網絡程式設計常用類
2.1 封裝IP位址 – InetAddress
// 1.擷取 IP位址
InetAddress ia = InetAddress.getLocalHost(); // 本機的 ip
// 2.操作 IP位址
System.out.println(ia); // DESKTOP-F31QQ1H/192.168.0.102
System.out.println(ia.getHostAddress()); // 192.168.0.102
System.out.println(ia.getHostName()); // DESKTOP-F31QQ1H
InetAddress ia2 = InetAddress.getByName("www.lwclick.com"); // 通過域名擷取ip
System.out.println(ia2);
2.2 封裝 IP 和 Port – InetSocketAddress
// 建立一個 InetSocketAddress 對象
InetSocketAddress isa = new InetSocketAddress("www.lwclick.com", 8888);
// 擷取對象内容
System.out.println(isa); // www.lwclick.com/104.21.41.202:8888
System.out.println(isa.getAddress()); // www.lwclick.com/104.21.41.202
System.out.println(isa.getPort()); // 8888
2.3 URL類
// 建立一個 URL 協定:https 域名/IP位址:lwclick.com 端口:80 路徑:/categories/MySQL/
URL url = new URL("https://lwclick.com:80/categories/MySQL/");
// 擷取 URL 各個組成部分
System.out.println(url.getProtocol()); // https
System.out.println(url.getHost()); // lwclick.com
System.out.println(url.getPort()); // 80
System.out.println(url.getDefaultPort()); // 443 預設的 https 端口
System.out.println(url.getPath()); // /categories/MySQL/
3. TCP程式設計
3.1 一次單向通信
- 伺服器端:
- 建立 ServerSocket,在指定端口監聽(
accept()
方法)
并處理請求(如果用戶端請求到來,傳回對應的Socket,否則的話一直等待,線程也被阻塞)
- 建立 ServerSocket,在指定端口監聽(
- 用戶端:
- 建立 Socket,需要指定伺服器的 ip 和端口号,向伺服器發送和接收響應
- 發送資料:
- 需要使用輸出流(OutputStream),可以通過 DataOutputStream 和 ObjectOutputStream 進行包裝,提高效率
- 接收資料:
- 使用輸入流(InputStream),使用 DataInputStream 和 ObjectInputStream 進行包裝
伺服器端:
public class LoginServer {
public static void main(String[] args) throws IOException {
// 1.建立一個 ServerSocket,配置監聽端口
ServerSocket serverSocket = new ServerSocket(8080);
// 2.使用 ServerSocket 在指定端口監聽
Socket socket = serverSocket.accept(); // 請求不到,在此阻塞; 請求到了,傳回一個socket,繼續執行
// 3.接收用戶端的請求資料,輸出結果
InputStream is = socket.getInputStream(); // 擷取流
DataInputStream dis = new DataInputStream(is); // 同樣使用資料流進行包裝
String info = dis.readUTF(); // 讀取對應類型的寫入的資料
System.out.println("用戶端的請求:" + info);
// 4.關閉資源
dis.close();
serverSocket.close();
}
}
用戶端:
public class LoginClient {
public static void main(String[] args) throws IOException {
// 1.建立一個 Socket,指明伺服器端ip和端口
Socket socket = new Socket(InetAddress.getLocalHost(), 8080); // InetAddress.getByName()擷取ip
// 2.發送資料給伺服器端
OutputStream os = socket.getOutputStream(); // 資訊通過流發送,輸出流
DataOutputStream dos = new DataOutputStream(os); // 資料流進行包裝
dos.writeUTF("userName=lwclick&pwd=123");
// 3.關閉資源
dos.close(); // 關閉高層流,低層自動關閉
}
}
注意:測試時,伺服器端需要先啟動,然後再啟動用戶端
3.2 一次雙向通信
伺服器端:
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
String info = dis.readUTF();
System.out.println("用戶端的請求:" + info);
// ============================== 向用戶端發送資料 =====================================
// 4. 給用戶端一個響應
OutputStream os = socket.getOutputStream(); // 此處的socket為接收的用戶端的響應
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("登入成功,歡迎!");
// 5.關閉資源
dos.close();
dis.close();
serverSocket.close();
}
用戶端:
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getL ocalHost(), 8080);
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("userName=lwclick&pwd=123");
// ============================== 接收伺服器端回報 =====================================
// 3.接收伺服器端響應,并輸出
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
String info = dis.readUTF();
System.out.println("伺服器端的響應:" + info);
// 4.關閉資源
dis.close();
dos.close();
}
3.3 傳輸對象
User 類:
在網絡上傳輸,類一定要實作序列化接口
public class User implements Serializable {
private String userId;
private String password;
// getter / setter / toString / constructor
}
伺服器端:
public static void main(String[] args) throws IOException, ClassNotFoundException {
ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
// =============== 此處使用【對象流】接收資料 ===================
ObjectInputStream ois = new ObjectInputStream(is);
User user = (User)ois.readObject();
System.out.println("用戶端的請求:" + user);
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
if (user.getUserId().indexOf("lwclick") >= 0 && user.getPassword().length() > 6) {
dos.writeUTF("登入成功,歡迎!");
} else {
dos.writeUTF("登入失敗,請重試!");
}
dos.close();
ois.close();
serverSocket.close();
}
用戶端:
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(), 8080);
// 擷取使用者輸入
Scanner sc = new Scanner(System.in);
System.out.print("userId: ");
String userId = sc.next();
System.out.print("password: ");
String password = sc.next();
User user = new User(userId, password);
OutputStream os = socket.getOutputStream();
// 【對象流】進行包裝
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(user);
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
String info = dis.readUTF();
System.out.println("伺服器端的響應:" + info);
dis.close();
oos.close();
}
3.4 引入多線程
将伺服器端接到請求後的處理步驟,放到線程的 run()方法 中,
每過來一個請求,就建立一個線程去執行
線程類:
public class LoginThread extends Thread {
private Socket socket;
public LoginThread() {
}
public LoginThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
DataOutputStream dos = null;
ObjectInputStream ois = null;
try {
InputStream is = socket.getInputStream();
ois = new ObjectInputStream(is);
User user = (User)ois.readObject();
System.out.println("用戶端的請求:" + user);
OutputStream os = socket.getOutputStream();
dos = new DataOutputStream(os);
if (user.getUserId().indexOf("lwclick") >= 0 && user.getPassword().length() > 6) {
dos.writeUTF("登入成功,歡迎!");
} else {
dos.writeUTF("登入失敗,請重試!");
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (dos != null) {
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (ois != null) {
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
伺服器端:
為每一個登入請求,建立一個線程來處理
public static void main(String[] args) throws IOException, ClassNotFoundException {
ServerSocket serverSocket = new ServerSocket(8080);
int i = 1;
while (true) {
Socket socket = serverSocket.accept();
// 為每一個登入請求,建立一個線程來處理
new LoginThread(socket).start();
// 統計用戶端的IP位址和總的請求次數
InetAddress ia = socket.getInetAddress();
System.out.println("這是第" + (i++) + "個請求,對方的IP位址是:" + ia.getHostAddress());
}
}
用戶端:(無需改變)
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(), 8080);
// 擷取使用者輸入
Scanner sc = new Scanner(System.in);
System.out.print("userId: ");
String userId = sc.next();
System.out.print("password: ");
String password = sc.next();
User user = new User(userId, password);
OutputStream os = socket.getOutputStream();
// 【對象流】進行包裝
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(user);
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
String info = dis.readUTF();
System.out.println("伺服器端的響應:" + info);
dis.close();
oos.close();
}
4. UDP程式設計
無連接配接的,客戶與咨詢師的線上交流
- 使用基于 UDP 協定的 Socket 網絡程式設計實作
- 不需要使用 IO 流實作資料的傳輸
- 每個資料發送單元被統一封裝成資料包(ip,接口,資料等)的方式,發送方将資料發到網絡上,資料包在網絡上尋找它要去的目的地
需要使用的類:
- DatagramSocket:用于發送或接收資料包
- DatagramPacket:資料包
4.1 一次單向通信
用戶端:
public static void main(String[] args) throws IOException {
// 1.建立一個 Socket,用來發送和接收資料包
DatagramSocket socket = new DatagramSocket(9999); // 用戶端監聽的接口
// 2.使用 socket 發送一個資料包
String str = "親,在嗎";
byte[] buf = str.getBytes();
InetAddress ia = InetAddress.getLocalHost();
int port = 8888; // 伺服器端接收資料的端口号
DatagramPacket packet = new DatagramPacket(buf, buf.length, ia, port);
// 發送資料包
socket.send(packet);
// 3.關閉 socket
socket.close();
}
伺服器端:
public static void main(String[] args) throws IOException {
// 1.建立一個 Socket,用來發送和接收資料包
DatagramSocket socket = new DatagramSocket(8888); // 伺服器端監聽的接口
// 2.使用 socket 接收一個資料包
byte[] buf = new byte[128];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet); // ip,port等資訊
System.out.println(new String(packet.getData(), 0, packet.getLength()));
System.out.println(packet.getAddress());
System.out.println(packet.getPort());
// 3.關閉 socket
socket.close();
}
4.2 多次雙向通信
用戶端:
public static void main(String[] args) throws IOException {
// 1.建立一個 Socket,用來發送和接收資料包
DatagramSocket socket = new DatagramSocket(9999); // 用戶端監聽的接口
Scanner sc = new Scanner(System.in);
while (true) {
String line = sc.nextLine();
// 2.使用 socket 發送一個資料包
byte[] buf = line.getBytes();
InetAddress ia = InetAddress.getLocalHost();
int port = 8888; // 伺服器端接收資料的端口号
DatagramPacket packet = new DatagramPacket(buf, buf.length, ia, port);
// 發送資料包
socket.send(packet);
// 如果用戶端輸入 bye,結束對話
if ("bye".equals(line)) {
break;
}
// 接收伺服器端傳回的消息
byte[] bytes = new byte[128];
DatagramPacket packetReceive = new DatagramPacket(bytes, bytes.length);
socket.receive(packetReceive);
System.out.println(new String(packetReceive.getData(), 0, packetReceive.getLength()));
}
// 3.關閉 socket
socket.close();
}
伺服器端:
public static void main(String[] args) throws IOException {
// 1.建立一個 Socket,用來發送和接收資料包
DatagramSocket socket = new DatagramSocket(8888); // 伺服器端監聽的接口
Scanner sc = new Scanner(System.in);
while (true) {
// 2.使用 socket 接收一個資料包
byte[] buf = new byte[128];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet); // ip,port等資訊
String info = new String(packet.getData(), 0, packet.getLength());
System.out.println(info);
if ("bye".equals(info)) {
break;
}
// 使用 socket 給用戶端發送一個資料包
String str = sc.nextLine();
byte[] bytes = str.getBytes();
InetAddress address = packet.getAddress(); // 發送資料的用戶端位址
int port = packet.getPort(); // 端口号
DatagramPacket sendPacket = new DatagramPacket(bytes, bytes.length, address, port);
socket.send(sendPacket);
}
// 3.關閉 socket
socket.close();
}
5. 檔案上傳
使用 TCP程式設計 實作檔案上傳功能
- 思路:進行兩次檔案的複制
- 【用戶端】将檔案從【本地】複制到【網絡】
- 【服務端】将檔案從【網絡】複制到【本地】
用戶端:
public class UploadClient {
public static void main(String[] args) throws IOException {
// 建立一個 socket,指明伺服器端ip 和監聽端口
Socket socket = new Socket(InetAddress.getLocalHost(), 8800);
// ======================== 上傳檔案到服務端的目的端口 ===========================
// 本機的源檔案
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("e:/readme.txt"));
// 将檔案寫到目的伺服器端口的位置
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
byte[] buf = new byte[1024];
int len = bis.read(buf);
while (len != -1) {
bos.write(buf, 0, len);
len = bis.read(buf);
}
bos.close();
bis.close();
}
}
伺服器端:
public class UploadServer {
public static void main(String[] args) throws IOException {
// 建立一個 ServerSocket
ServerSocket serverSocket = new ServerSocket(8800);
// 使用 ServerSocket 在指定端口監聽
Socket socket = serverSocket.accept();
// ===================== 從目的端口取檔案 ========================
// 從目的端口取内容
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
// 儲存到伺服器的本地
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("e:/readme2.txt"));
byte[] buf = new byte[1024];
int len = bis.read(buf);
while (len != -1) {
bos.write(buf, 0, len);
len = bis.read(buf);
}
bos.close();
bis.close();
}
}