1 計算機網絡的核心概念
網絡通信
- 概念:兩臺設備之間通過網絡實現數據傳輸
2.網絡通信:將數據通過網絡從一臺設備傳輸到另一臺設備
- java.net包下提供了一系列的類或接口,供程序員使用,完成網絡通信
網絡
- 概念:兩臺或多臺設備通過一定物理設備連接起來構成了網絡
- 根據網絡的覆蓋範圍不同,對網絡進行分類:
局域網: 覆蓋範圍最小,僅僅覆蓋一個教室或一個機房
城域網:覆蓋範圍較大,可以覆蓋一個城市
廣域網:覆蓋範圍最大,可以覆蓋全國,甚至全球,萬維網是廣域網的代表
IP地址
-
概念: 用於唯一標識網絡中的每臺計算機/主機
-
查看ip地址: ipconfig
-
ip地址的表示形式:點分十進制 xx.xx.xx.xx
-
每一個十進制數的範圍: 0~255
-
ip地址的組成=網絡地址+主機地址,比如: 192.168.16.69
-
lPv6是互聯網工程任務組設計的用於替代IPv4的下一代IP協議,其地址數量號稱可以爲全世界的每一粒沙子編上一個地址
-
由於IPv4最大的問題在於網絡地址資源有限,嚴重製約了互聯網的應用和發展。IPv6的使用,不僅能解決網絡地址資源數量的問題,而且也解決了多種接入設備連入互聯網的障礙
-
待完善的核心概念:
- IP / Hostname / 域名 / DNS 4者的關係
- 網絡號
- 網段
- 子網掩碼
IPv4地址的分類
域名
- 如 www.baidu.com,將ip映射到域名上,訪問域名就是訪問ip
- 好處:爲了方便記憶,解決記ip的困難
端口號
-
概念: 用於標識計算機上某個特定的網絡程序
-
表示形式: 以整數形式,端口範圍0~65535 [2個字節表示端口 0~2^16-1]
-
0~1024已經被佔用, 比如 ssh 22, ftp 21, smtp 25 http 80
-
常見的網絡程序端口號:
- tomcat : 8080
- mysql : 3306
- oracle : 1521
- sqlserver : 1433
- ssh : 22
網絡通信協議
- TCP/IP協議
TCP/IP (Transmission Control Protocol/Internet Protocol)的簡寫, 中文譯名爲傳輸控制協議/因特網互聯協議,又叫網絡通訊協議,這個協議是Internet最基本的協議、Internet國際互聯網絡的基礎。
簡單地說,就是由網絡層的IP協議和傳輸層的TCP協議組成的。
TCP協議: 傳輸控制協議
-
使用TCP協議前,須先建立TCP連接,形成傳輸數據通道
-
傳輸前,採用“三次握手"方式,是可靠的
-
TCP協議進行通信的兩個應用進程: 客戶端、服務端
-
在連接中可進行大數據量的傳輸
-
傳輸完畢,需釋放已建立的連接,效率低
UDP協議: 用戶數據協議
-
將數據、源、目的封裝成數據包,不需要建立連接
-
每個數據報的大小限制在64K內,不適合傳輸大量數據
-
因無需連接,故是不可靠的
-
發送數據結束時無需釋放資源(因爲不是面向連接的),速度快
-
舉例: 發短信
2 Java Socket 概念及API
2.1 InetAddress
- Method API
- 獲取本機InetAddress對象 getLocalHost
- 根據指定主機名/域名獲取ip地址對象 getByName
- 獲取InetAddress對象的主機名 getHostName
- 獲取InetAddress對象的地址 getHostAddress
/*
* 演示InetAddress類的使用
*/
public class APITest {
public static void main(String[] args) throws UnknownHostException {
// 1.獲取本機的InetAddress對象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost); // LAPTOP-RVFFB7FM/192.168.23.1
// 2.根據機器的名字獲取InetAddress對象
InetAddress inetAddress = InetAddress.getByName("C20432");
System.out.println(inetAddress);// LAPTOP-RVFFB7FM/192.168.23.1
// 3.根據域名返回InetAddress對象,比如www.baidu.com
InetAddress inetAddress1 = InetAddress.getByName("www.baidu.com");
System.out.println(inetAddress1);// www.baidu.com/120.232.145.185
// 4.根據InetAddress對象,獲取對應的ip地址
String address = inetAddress1.getHostAddress();
System.out.println(address);// 120.232.145.185
// 5.通過InetAddress對象,獲取對應的主機名
String hostName = inetAddress1.getHostName();
System.out.println(hostName);// www.baidu.com
}
}
2.2 Socket
-
套接字(Socket)開發網絡應用程序被廣泛採用,以至於成爲事實上的網絡編程、網絡通信標準
-
通信的兩端都要有Socket,是兩臺機器間通信的端點,網絡通信其實就是
Socket
間的通信 -
Socket
允許程序把網絡連接當成一個流,數據在兩個Socket間通過IO傳輸 -
一般主動發起通信的應用程序屬客戶端,等待通信請求的爲服務端
3 TCP網絡編程
-
基於客戶端一服務端的網絡通信, 底層使用的是TCP/IP協議
-
應用場景舉例: 客戶端發送數據,服務端接受並顯示控制檯
-
基於Socket的TCP編程
3.1 案例:使用字節流(Client發 + Server收)
- 編寫1個Server端、1個 Client端。
- Server 端: 在 9999 端口監聽
- Client 端: 連接到 Server 端,發送 "Hello, Server",然後退出
- Server 端: 接收到 Client 端發送的信息,輸出,並退出。
Server端
public class SocketTCP01Server {
public static void main(String[] args) throws IOException {
// 1.在9999端口監聽,等待鏈接
// 細節:要求該端口沒有其他人監聽
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服務端,在9999端口監聽,等待連接...");
// 2.如果沒有客戶端連接9999端口,該方法會阻塞等待連接
// 如果有連接,返回一個socket
Socket socket = serverSocket.accept();
System.out.println(socket.getClass());
// 3.通過socket.getInputStream() 讀取
// 客戶端寫入到數據通道的數據,顯示
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int readLen = 0;
while((readLen = inputStream.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, readLen));
}
// 4.關閉流
inputStream.close();
socket.close();
serverSocket.close();
System.out.println("服務端退出...");
}
}
Client端
public class SocketTCP01Client {
public static void main(String[] args) throws IOException {
// 1.連接9999端口,連接成功返回一個socket
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
// 2.連接上後,生成socket,通過socket.getOutputStream()
OutputStream outputStream = socket.getOutputStream();
// 3.輸出信息
outputStream.write("hello, socket".getBytes());
// 4.關閉流和連接,節省資源
outputStream.close();
socket.close();
System.out.println("客戶端退出...");
}
}
3.2 案例:使用字節流(Client發/收 + Server收/回)
- 編寫1個 Server 端、1個 Client 端
- Server 端 : 在9999端口監聽
- Client 端 : 連接到 Server 端,發送 "hello, server",並接收服務器端回發的 "hello, Client",再退出
- Server 端 : 接收到 Client 端發送的信息,輸出;併發送 "hello , client",再退出
3.2.1 Server 端
public class SocketTCP02Server {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));
}
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello Client".getBytes());
// 設置結束標記
socket.shutdownOutput();
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();
}
}
3.2.2 Client 端
public class SocketTCP02Client {
public static void main(String[] args) throws Exception{
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello Server".getBytes());
// 設置結束標記, socket.close()也做了同樣的事情
// 但是因爲我們下面還有獲取數據通道信息,因此不能關閉,只能shutdownOutput()
socket.shutdownOutput();
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));
}
outputStream.close();
inputStream.close();
socket.close();
}
}
3.3 案例:使用字符流(Client發/收 + Server收/回)
- 編寫1個 Server 端、1個 Client 端
- Server 端 : 在9999端口監聽
- Client 端 : 連接到 Server 端,發送 "Hello, server",並接收服務器端回發的 "Hello, Client",再退出
- Server 端 : 接收到 Client 端發送的信息,輸出;併發送 "hello , client",再退出
3.3.1 Server 端
public class SocketTCP03Server {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
// IO讀取,使用字符流輸入,使用InputStreamReader將InputStream轉化爲字符流
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String s = reader.readLine();
System.out.println(s);
// 字符流方式的輸出
OutputStream outputStream = socket.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
writer.write("hello Client 字符流");
writer.newLine(); // 插入一個換行符,代表寫入的內容結束
writer.flush(); // 字符流需要手動刷新,否則數據不會寫入數據通道
reader.close();
writer.close();
socket.close();
serverSocket.close();
}
}
3.3.2 Client 端
public class SocketTCP03Client {
public static void main(String[] args) throws Exception{
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
OutputStream outputStream = socket.getOutputStream();
// 通過輸出流,轉換爲字符流
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
writer.write("hello Server 字符流");
writer.newLine(); // 插入一個換行符,代表寫入的內容結束
writer.flush(); // 字符流需要手動刷新,否則數據不會寫入數據通道
InputStream inputStream = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String s = reader.readLine();
System.out.println(s);
reader.close();
writer.close();
socket.close();
System.out.println("客戶端退出...");
}
}
3.4 案例:使用字符流(Client發[圖片]/收[文本] + Server收&存[圖片]/回[文本])
- 編寫1個 Server 端、1個 Client 端
- Server 端 : 在8888端口監聽
- Client 端 : 連接到 Server 端,發送 1張圖片
e:\\qie.png
- Server 端 : 接收到 Client 端發送的圖片,保存到 src 下,發送 "收到圖片",再退出
- Client 端 : 接收到 Server 端發送的"收到圖片",再退出
3.4.0 StreamUtils
/**
* 此類用於演示關於流的讀寫方法
*
*/
public class StreamUtils {
/**
* 功能:將輸入流轉換成byte[], 即可以把文件的內容讀入到byte[]
* @param is
* @return
* @throws Exception
*/
public static byte[] streamToByteArray(InputStream is) throws Exception{
ByteArrayOutputStream bos = new ByteArrayOutputStream();//創建輸出流對象
byte[] b = new byte[1024];//字節數組
int len;
while((len=is.read(b))!=-1){//循環讀取
bos.write(b, 0, len);//把讀取到的數據,寫入bos
}
byte[] array = bos.toByteArray();//然後將bos 轉成字節數組
bos.close();
return array;
}
/**
* 功能:將InputStream轉換成String
* @param is
* @return
* @throws Exception
*/
public static String streamToString(InputStream is) throws Exception{
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder builder= new StringBuilder();
String line;
while((line=reader.readLine ())!=null){
builder.append(line+"\r\n");
}
return builder.toString();
}
}
3.4.1 Server 端
public class TCPFileUploadServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8888);
// 等待連接
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
BufferedInputStream bis = new BufferedInputStream(inputStream);
byte[] bytes = StreamUtils.streamToByteArray(bis);
String destFilePath = "src\\a.jpg";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
bos.write(bytes);
bos.close();
// 向客戶端回覆“收到圖片”
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.write("收到圖片");
writer.flush();
writer.close();
bis.close();
socket.close();
}
}
3.4.2 Client 端
public class TCPFileUploadClient {
public static void main(String[] args) throws Exception{
// 客戶端連接服務端8888端口,得到socket對象
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
// 讀取本地的文件轉換爲字節數組
String filePath = "D:\\image\\a.jpg";
FileInputStream fileInputStream = new FileInputStream(filePath);
BufferedInputStream bis = new BufferedInputStream(fileInputStream);
// 該字節數組就是文件
byte[] bytes = StreamUtils.streamToByteArray(bis);
OutputStream outputStream = socket.getOutputStream();
// outputStream.write(bytes);
BufferedOutputStream bos = new BufferedOutputStream(outputStream);
bos.write(bytes);
socket.shutdownOutput(); // 寫入數據的結束標記
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s = reader.readLine();
System.out.println(s);
reader.close();
bos.close();
bis.close();
socket.close();
}
}
4 UDP網絡編程
4.1 基本介紹
-
類 DatagramSocket 和 DatagramPacket[數據包/數據報]實現了基於 UDP協議網絡程序。
-
UDP數據報通過數據報套接字 DatagramSocket 發送和接收,系統不保證UDP數據報一定能夠安全送到目的地,也不能確定什麼時候可以抵達。
-
DatagramPacket 對象封裝了UDP數據報,在數據報中包含了發送端的IP地址和端口號以及接收端的IP地址和端口號。
-
UDP協議中每個數據報都給出了完整的地址信息,因此無須建立發送方和接收方的連接
4.2 基本流程
-
核心的兩個類/對象 DatagramSocket與DatagramPacket
-
建立發送端,接收端(沒有服務端和客戶端概念)
-
發送數據前,建立數據包/報 DatagramPacket對象
-
調用DatagramSocket的發送、接收方法
-
關閉DatagramSocket
4.3 案例:約喫火鍋(發送端 發/收 + 接收端 收/發)
- 編寫1個 接收端A和1個發送端B
- 接收端 A : 在 9999 端口等待接收數據(receive)
- 發送端 B : 向接收端A發送數據 "hello, 明天喫火鍋~"
- 接收端 A : 接收到發送端B發送的數據,回覆 "好的,明天見",再退出
- 發送端 B : 接收A回覆的數據,再退出
4.3.1 發送端 B
public class UDPSenderB {
public static void main(String[] args) throws Exception{
// 1.創建一個DatagramSocket,監聽9998端口
DatagramSocket socket = new DatagramSocket(9998);
// 2.創建DatagramPacket,接收數據
byte[] data = "你好,明天喫火鍋".getBytes();
DatagramPacket packet = new DatagramPacket(data, 0, data.length, InetAddress.getByName("192.168.23.1"), 9999);
// 3.發送數據
socket.send(packet);
// 接收消息
data = new byte[1024];
packet = new DatagramPacket(data, data.length);
socket.receive(packet);
int length = packet.getLength();
data = packet.getData();
String msg = new String(data, 0, length);
System.out.println(msg);
// 4.關閉資源
socket.close();
}
}
4.3.2 接收端 A
public class UDPReceiverA {
public static void main(String[] args) throws Exception{
// 1.創建一個DatagramSocket對象,監聽9999端口
DatagramSocket socket = new DatagramSocket(9999);
// 2.構建一個DatagramPacket,準備接受數據
byte[] data = new byte[1024];
DatagramPacket packet = new DatagramPacket(data, data.length);
// 3.接收數據,如果沒有數據則堵塞,接收到的數據存放在packet中
System.out.println("接收端A等待接收數據...");
socket.receive(packet);
// 4.接收完數據轉化爲String並輸出
int length = packet.getLength();
data = packet.getData();
String msg = new String(data, 0, length);
System.out.println(msg);
// 回覆消息
data = "好的,明天見".getBytes();
packet = new DatagramPacket(data, 0, data.length, InetAddress.getByName("192.168.23.1"), 9998);
socket.send(packet);
// 5.關閉資源
socket.close();
}
}
4.4 案例:問答(Client發/收 + Server收/發)
- 使用字符流的方式,編寫1個客戶端程序和服務端程序
- Client 端 : 發送 "name" ;
- Server 端 : 接收到 "name"後,返回 "我是 nova"
- Client 端 : 發送 "What is your hobby"
- Server 端 : 接收到後,返回 "編寫java程序"。
若不是這2個問題,則 Server 回覆“你說啥呢?”
4.4.1 Server
public class HomeWork01Server {
public static void main(String[] args) throws Exception{
System.out.println("服務器等待接收消息...");
DatagramSocket socket = new DatagramSocket(9999);
byte[] data = new byte[1024];
DatagramPacket packet = new DatagramPacket(data, data.length);
socket.receive(packet);
int length = packet.getLength();
data = packet.getData();
String msg = new String(data, 0, length);
System.out.println(msg);
String returnMsg = "";
if("name".equals(msg)) {
returnMsg = "我是nova";
} else if("hobby".equals(msg)){
returnMsg = "編寫java程序";
} else {
returnMsg = "你說啥呢";
}
data = returnMsg.getBytes();
packet = new DatagramPacket(data, 0, data.length, InetAddress.getByName("192.168.23.1"), 9998);
socket.send(packet);
socket.close();
}
}
4.4.2 Client
public class HomeWork01Client {
public static void main(String[] args) throws Exception{
System.out.println("請輸入您的消息:");
Scanner sc = new Scanner(System.in);
String msg = sc.next();
DatagramSocket socket = new DatagramSocket(9998);
byte[] data = msg.getBytes();
DatagramPacket packet = new DatagramPacket(data, 0, data.length, InetAddress.getByName("192.168.23.1"), 9999);
socket.send(packet);
data = new byte[1024];
packet = new DatagramPacket(data, data.length);
socket.receive(packet);
int length = packet.getLength();
data = packet.getData();
System.out.println(new String(data, 0, length));
}
}
4.5 案例:我要聽音樂(Client發[文本]/收[文件] + Server收[文本]/回[文件])
- 編寫客戶端程序和服務器端程序
- Client 端:可以輸入1個音樂文件名,如:高山流水
- Server 端:收到音樂名後,可以給客戶端返回這個音樂文件。如果服務器沒有這個文件,則返回 1個默認的音樂即可。
- Client 端:收到音樂文件後,保存到本地 e:\
4.5.1 Server
public class HomeWork03Server {
public static void main(String[] args) throws Exception{
System.out.println("服務端監聽9999端口,等待接收文件名...");
// 1.創建一個socket對象
ServerSocket serverSocket = new ServerSocket(9999);
// 2.接收傳遞過來的文件名
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
// 這裏使用了while循環,是考慮客戶端可能發送的文件名較大的情況
byte[] buf = new byte[1024];
int len = 0;
String downLoadFileName = "";
while((len = inputStream.read(buf)) != -1) {
downLoadFileName += new String(buf, 0, len);
}
System.out.println("客戶端希望下載的文件名: " + downLoadFileName);
// 3.確定文件名
// 如果客戶端要求下載高山流水,則獲取文件流返回,否則,返回無名mp3
String filePath = "";
if("高山流水".equals(downLoadFileName)) {
filePath = "src//高山流水.mp3";
} else {
filePath = "src//無名.mp3";
}
// 4.獲取文件輸入流並轉化爲字節數組
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
byte[] data = StreamUtils.streamToByteArray(bis);
// 5.獲取socket輸出流並寫出數據
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
bos.write(data);
socket.shutdownOutput();
// 6.關閉資源
bos.close();
bis.close();
socket.close();
serverSocket.close();
System.out.println("服務端傳輸文件完畢,退出");
}
}
4.5.2 Client
public class HomeWork03Client {
public static void main(String[] args) throws Exception{
System.out.println("請輸入下載的文件名:");
// 1.獲取用戶輸入的文件名
Scanner sc = new Scanner(System.in);
String fileName = sc.next();
// 2.創建連接
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
// 3.獲取socket關聯的輸出流並寫出
OutputStream outputStream = socket.getOutputStream();
outputStream.write(fileName.getBytes());
// 設置寫入結束的標記
socket.shutdownOutput();
// 4.獲取輸入流並轉化爲字節數組
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] data = StreamUtils.streamToByteArray(bis);
// 5.獲取寫出流,將文件寫入磁盤
String fileNamePath = "src//com//hspedu/homework//" + fileName + ".mp3";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileNamePath));
bos.write(data);
// 6.關閉資源
outputStream.close();
bos.close();
bis.close();
socket.close();
System.out.println("文件下載完畢,退出");
}
}