🔥作者主頁:小林同學的學習筆錄
🔥mysql專欄:小林同學的專欄
目錄
1. 網絡編程
1.1? 概述
1.2? 網絡編程的三要素
1.2.1? IP地址
1.2.2? InetAddress
1.2.3? 端口和協議
1.3? UDP協議
1.3.1? UDP發送數據
1.3.2? UDP接收數據
1.4? TCP協議
1.4.1? TCP協議實例
1.4.2? 三次握手,四次揮手
1. 網絡編程
1.1? 概述
概述:在網絡通信協議下,不同計算機上運行的程序,進行數據的傳輸
java.net包中可以看見常見的網絡應用程序API
常見的軟件架構:
C/S:Client / Server? ?客戶端 / 服務器
- 需要用戶下載并安裝客戶端程序,在遠程有一個服務器程序
- 優缺點:
- 畫面可以做的比較精美,用戶體驗好(不需要網絡傳輸,數據來源于安裝包)
- 需要開發客戶端,也需要開發服務端
- 用戶需要下載和更新的時候太麻煩?
B/S:Browser / Server? 瀏覽器 /? 服務器
- 只需要一個瀏覽器,通過訪問不同的網址實現操作程序,客戶訪問不同的服務器
- 優缺點:
- 不需要開發客戶端,只需要頁面 + 服務器
- 用戶不需要下載,打開瀏覽器就能使用
- 如果應用過大,用戶體驗將受到影響(因為數據進行網絡傳輸效率比較低)
1.2? 網絡編程的三要素
IP:設備在網絡中的地址,是唯一標識
端口號:應用程序在設備中的唯一標識
協議:數據在網絡中的傳輸規則,常見的協議有UDP,TCP,HTTP,HTTPS,FTP
1.2.1? IP地址
IP地址分為兩大類:
-
IPv4:是給每個連接在網絡上的主機分配一個32bit地址。按照TCP/IP規定,IP地址用二進制來表示,每個IP地址長32bit,也就是4個字節。例如一個采用二進制形式的IP地址是“11000000 10101000 00000001 01000010”,這么長的地址,處理起來也太費勁了。為了方便使用,IP地址經常被寫成十進制的形式,中間使用符號“.”分隔不同的字節。于是,上面的IP地址可以表示為“192.168.1.66”。IP地址的這種表示法叫做“點分十進制表示法”,這顯然比1和0容易記憶得多,最多有2^32次方個IP
-
IPv6:由于互聯網的蓬勃發展,IP地址的需求量愈來愈大,但是網絡地址資源有限,使得IP的分配越發緊張。為了擴大地址空間,通過IPv6重新定義地址空間,采用128位地址長度,每16個字節一組,分成8組十六進制數,每一組用分號隔開,這樣就解決了網絡地址資源數量不夠的問題,最多有2^128次方個IP、
DOS常用命令:
-
ipconfig:查看本機IP地址
-
ping IP地址:檢查網絡與目標主機是否能連通
特殊IP地址:
-
127.0.0.1:是回送地址,可以代表本機地址LocalHost,一般用來測試使用
疑問:假設192.168.1.100是我的電腦IP,那么這個IP跟127.0.0.1是一樣嗎?
不一樣,192.168.1.100和127.0.0.1不是一樣的IP地址。192.168.1.100是局域網內的私有IP地址,用于在局域網中標識設備。而127.0.0.1是本地回環地址,用于在同一臺設備內部進行通信。當你的計算機嘗試連接127.0.0.1時,它實際上是在嘗試與自己通信,而不是與網絡上的其他設備通信。
疑問:公網地址(萬維網使用)和私有地址(局域網使用)的區別?
公網地址和私有地址之間的主要區別在于它們的可訪問性和范圍。公網地址是全球唯一的IP地址,用于在互聯網上唯一標識設備和進行通信。私有地址則是在局域網內使用的地址,不會在互聯網上進行路由,因此不能直接從互聯網上訪問。私有地址用于在局域網內部進行通信,而通過路由器進行網絡地址轉換(NAT),可以允許多個設備共享單個公網IP地址來訪問互聯網。
1.2.2? InetAddress
InetAddress
?是 Java 編程語言中用于表示 IP 地址的類。它提供了一種將 IP 地址和主機名相互轉換的方式。通過?InetAddress
?類,可以實現網絡通信中的主機名解析、IP 地址解析等功能。
成員方法:
1.2.3? 端口和協議
-
端口
-
設備上應用程序的唯一標識
-
-
端口號
-
用兩個字節表示的整數,它的取值范圍是0~65535。其中,0~1023之間的端口號用于一些知名的網絡服務和應用,普通的應用程序需要使用1024以上的端口號。如果端口號被另外一個服務或應用所占用,會導致當前程序啟動失敗
-
-
協議
-
計算機網絡中,連接和通信的規則被稱為網絡通信協議
-
-
UDP協議
-
用戶數據報協議(User Datagram Protocol)
-
UDP是無連接通信協議,即在數據傳輸時,數據的發送端和接收端不建立邏輯連接。簡單來說,當一臺計算機向另外一臺計算機發送數據時,發送端不會確認接收端是否存在,就會發出數據,同樣接收端在收到數據時,也不會向發送端反饋是否收到數據。
-
由于使用UDP協議消耗系統資源小,通信效率高,所以通常都會用于音頻、視頻和普通數據的傳輸
-
速度快,有大小限制一次最多發送64K,數據不安全,易丟失數據
-
例如視頻會議通常采用UDP協議,因為這種情況即使偶爾丟失一兩個數據包,也不會對接收結果產生太大影響。但是在使用UDP協議傳送數據時,由于UDP的面向無連接性,不能保證數據的完整性,因此在傳輸重要數據時不建議使用UDP協議
-
-
TCP協議
-
傳輸控制協議 (Transmission Control Protocol)
-
TCP協議是面向連接的通信協議,即傳輸數據之前,在發送端和接收端建立邏輯連接,然后再傳輸數據,它提供了兩臺計算機之間可靠無差錯的數據傳輸。在TCP連接中必須要明確客戶端與服務器端,由客戶端向服務端發出連接請求,每次連接的創建都需要經過“三次握手”
-
速度慢,沒有大小限制,數據安全
-
三次握手:TCP協議中,在發送數據的準備階段,客戶端與服務器之間的三次交互,以保證連接的可靠
第一次握手,客戶端向服務器端發出連接請求,等待服務器確認
第二次握手,服務器端向客戶端回送一個響應,通知客戶端收到了連接請求
第三次握手,客戶端再次向服務器端發送確認信息,確認連接
-
完成三次握手,連接建立后,客戶端和服務器就可以開始進行數據傳輸了。由于這種面向連接的特性,TCP協議可以保證傳輸數據的安全,所以應用十分廣泛。例如上傳文件、下載文件、瀏覽網頁等
-
1.3? UDP協議
1.3.1? UDP發送數據
構造方法:
成員方法:
發送數據的步驟
-
創建發送端的Socket對象(DatagramSocket)
-
創建數據,并把數據打包(DatagramPacket)
-
調用DatagramSocket對象的方法發送數據(send)
-
關閉發送端
代碼演示:
public class Send {public static void main(String[] args) throws IOException {/*** 創建發送端的Socket對象(DatagramSocket)* 創建數據,并把數據打包(DatagramPacket)* 調用DatagramSocket對象的方法發送數據(send)* 關閉發送端*///這里參數如果沒有端口號,系統會自動分配一個端口號DatagramSocket socket = new DatagramSocket(10000);String str = "龍顏大怒666";byte[] bytes = new byte[1024];bytes = str.getBytes();DatagramPacket packet = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("127.0.0.1"),10086);socket.send(packet);socket.close();}
}
1.3.2? UDP接收數據
構造方法:
成員方法:
接收數據的步驟
-
創建接收端的Socket對象(DatagramSocket)
-
創建一個數據包,用于接收數據(DatagramPacket)
-
調用DatagramSocket對象的方法接收數據(receive)
-
解析數據包,并把數據在控制臺顯示
-
關閉接收端
代碼演示:
public class Receive {public static void main(String[] args) throws IOException {/*** 創建接收端的Socket對象(DatagramSocket)* 創建一個數據包,用于接收數據(DatagramPacket)* 調用DatagramSocket對象的方法接收數據(receive)* 解析數據包,并把數據在控制臺顯示* 關閉接收端*///注意:這里的端口號一定要與發送端中的數據包端口一致,不然會收不到數據DatagramSocket socket = new DatagramSocket(10086);byte[] bytes = new byte[1024];DatagramPacket packet = new DatagramPacket(bytes,bytes.length);//這個方法會一直等待發送端發送信息過來,直到拿到數據才會取消阻塞socket.receive(packet);byte[] data = packet.getData();InetAddress address = packet.getAddress();int port = packet.getPort();System.out.println("數據:" + new String(data,0,packet.getLength()) + " 主機IP:" + address + " 端口號:" + port);socket.close();}
}輸出結果:數據:龍顏大怒666 主機IP:/127.0.0.1 端口號:10000
1.4? TCP協議
1.4.1? TCP協議實例
代碼演示:
public class Client {public static void main(String[] args) throws IOException {//創建socket對象//細節:在創建對象同時會連接服務端//如果連接不上代碼會報錯Socket socket = new Socket("127.0.0.1",10000);//創建socket的輸出流通道OutputStream outputStream = socket.getOutputStream();//寫入數據outputStream.write("你好呀".getBytes());//關閉資源socket.close();outputStream.close();}
}
public class Server {public static void main(String[] args) throws IOException {//這里的端口號要跟客戶端的Socket保持一致ServerSocket serverSocket = new ServerSocket(10000);//這里會阻塞等待客戶端發送信息Socket socKet = serverSocket.accept();//通過socket獲取輸入流通道InputStream inputStream = socKet.getInputStream();//解決中文亂碼問題InputStreamReader inputStreamReader = new InputStreamReader(inputStream);BufferedReader bufferedReader = new BufferedReader(inputStreamReader);int len;while ((len = bufferedReader.read()) != -1) {System.out.print((char) len);}//關閉資源serverSocket.close();socKet.close();}
}
1.4.2? 三次握手,四次揮手
三次握手:為了確保連接的建立
四次揮手:確保連接斷開,且數據處理完畢
1.5? 綜合練習
1.5.1? 多發多送
public class Test01 {public static void main(String[] args) throws IOException {/*** 客戶端:多次發送數據* 服務端:接收多次數據,并打印*/Socket socket = new Socket("127.0.0.1",10002);OutputStream outputStream = socket.getOutputStream();Scanner scanner = new Scanner(System.in);while (true) {System.out.println("請輸入你要發送的信息:");String str = scanner.nextLine();//用戶輸入886表示退出if("886".equals(str)){break;}outputStream.write(str.getBytes());}//關閉資源outputStream.close();socket.close();}
}public class TestServer01 {public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(10002);Socket socket = serverSocket.accept();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));int len;while((len = bufferedReader.read()) != -1){System.out.print((char)len);}//關閉資源socket.close();serverSocket.close();}
}
1.5.2? 接收并反饋
public class Test02 {public static void main(String[] args) throws IOException {/*** 客戶端:發送一條數據,接收服務端反饋的消息并打印* 服務端:接收數據并打印,再給客戶端反饋信息*/Socket socket = new Socket(InetAddress.getLocalHost(),10003);Scanner scanner = new Scanner(System.in);OutputStream os = socket.getOutputStream();System.out.println("請輸入要發給服務端的信息:");String str = scanner.nextLine();os.write(str.getBytes());//細節:這里需要一個結束標記,服務端那邊讀取才知道結束socket.shutdownOutput();InputStreamReader isr = new InputStreamReader(socket.getInputStream());int len;while ((len = isr.read()) != -1){System.out.print((char)len);}socket.close();}
}public class TestServer02 {public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(10003);Socket socket = serverSocket.accept();InputStreamReader isr = new InputStreamReader(socket.getInputStream());int len;//細節://read方法從連接通道讀取數據//但是需要一個結束標記循環才會停止//否則,程序就會一直停在read方法這里,等待讀取下面的數據while ((len = isr.read()) != -1){System.out.print((char)len);}OutputStream os = socket.getOutputStream();os.write("收到客戶端的信息".getBytes());socket.close();serverSocket.close();}
}
1.5.3? 上傳練習
public class Test03 {public static void main(String[] args) throws IOException {/*** 案例需求:* 客戶端:數據來自于本地文件,接收服務器反饋* 服務器:接收到的數據寫入本地文件,給出反饋*/Socket socket = new Socket("127.0.0.1", 10000);byte[] bytes = new byte[1024];int len;//這種方式效率比較低//FileInputStream fis = new FileInputStream("D:\\img\\0a3b3288-3446-4420-bbff-f263d0c02d8e.jpg");//OutputStream os = socket.getOutputStream();//緩存流可以降低磁盤IO次數BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source/0a3b3288-3446-4420-bbff-f263d0c02d8e.jpg"));BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());//發送數據while ((len = bis.read(bytes)) != -1){bos.write(bytes,0,len);}//設置結束標記socket.shutdownOutput();//接收數據InputStreamReader isr = new InputStreamReader(socket.getInputStream());while ((len = isr.read()) != -1){System.out.print((char)len);}socket.close();}
}public class TestServer03 {public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(10000);Socket socket = serverSocket.accept();InputStream is = socket.getInputStream();byte[] bytes = new byte[1024];int len;//效率低//FileOutputStream fos = new FileOutputStream(new File("D:\\test\\a.jpg"));//這里需要解決文件名重復問題,導致原先的文件會被后面的覆蓋,用UUID來解決//System.out.println(UUID.randomUUID());//72e165ae-98ad-4cd4-80e9-c9f86b910461,我們一般看到的效果是沒有"-"的,需要處理一下String name = UUID.randomUUID().toString().replace("-", "");//BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("img\\a.jpg"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("img\\" + name + ".jpg"));//邊讀邊寫while((len = is.read(bytes)) != -1){bos.write(bytes,0,len);}//發送數據OutputStream os = socket.getOutputStream();os.write("收到數據啦".getBytes());socket.close();serverSocket.close();}
}
1.5.4? 服務器改寫成多線程,以及線程優化
服務器只能處理一個客戶端請求,接收完一個圖片之后,服務器就關閉了。
優化方案一:使用循環
弊端:第一個用戶正在上傳數據,第二個用戶就來訪問了,此時第二個用戶是無法成功上傳的。
所以,使用多線程改進
優化方案二:使用循環 + 多線程
每來一個用戶,就開啟多線程處理
public class Test04 {public static void main(String[] args) throws IOException {/*** 案例需求:* 客戶端:數據來自于本地文件,接收服務器反饋* 服務器:接收到的數據寫入本地文件,給出反饋*/Socket socket = new Socket("127.0.0.1", 10000);byte[] bytes = new byte[1024];int len;//這種方式效率比較低//FileInputStream fis = new FileInputStream("D:\\img\\0a3b3288-3446-4420-bbff-f263d0c02d8e.jpg");//OutputStream os = socket.getOutputStream();//緩存流可以降低磁盤IO次數BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source/0a3b3288-3446-4420-bbff-f263d0c02d8e.jpg"));BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());//發送數據while ((len = bis.read(bytes)) != -1){bos.write(bytes,0,len);}//設置結束標記socket.shutdownOutput();//接收數據InputStreamReader isr = new InputStreamReader(socket.getInputStream());while ((len = isr.read()) != -1){System.out.print((char)len);}socket.close();}
}public class TestServer04 {public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(10000);ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3,//核心線程數量16,//線程池總大小60,//空閑時間TimeUnit.SECONDS,//空閑時間(單位)new ArrayBlockingQueue<>(2),//隊列Executors.defaultThreadFactory(),//線程工廠,讓線程池如何創建線程對象new ThreadPoolExecutor.AbortPolicy()//阻塞隊列);while (true) {//等待客戶端連接Socket socket = serverSocket.accept();//一個用戶對應一條線程//new Thread(new MyRunnable(socket)).start();//線程池進行優化poolExecutor.submit(new MyRunnable(socket));}}
}public class MyRunnable implements Runnable {private Socket socket;public MyRunnable(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {InputStream is = socket.getInputStream();byte[] bytes = new byte[1024];int len;//效率低//FileOutputStream fos = new FileOutputStream(new File("D:\\test\\a.jpg"));//這里需要解決文件名重復問題,導致原先的文件會被后面的覆蓋,用UUID來解決//System.out.println(UUID.randomUUID());//72e165ae-98ad-4cd4-80e9-c9f86b910461,我們一般看到的效果是沒有"-"的,需要處理一下String name = UUID.randomUUID().toString().replace("-", "");//BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("img\\a.jpg"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("img\\" + name + ".jpg"));//邊讀邊寫while ((len = is.read(bytes)) != -1) {bos.write(bytes, 0, len);}//發送數據OutputStream os = socket.getOutputStream();os.write("上傳成功".getBytes());} catch (IOException e) {throw new RuntimeException(e);} finally {if (socket != null) {try {socket.close();} catch (IOException e) {throw new RuntimeException(e);}}}}