Java 網路程式設計(TCP程式設計 和 UDP程式設計)

Rainbow-Sea發表於2024-07-18

1. Java 網路程式設計(TCP程式設計 和 UDP程式設計)

@

目錄
  • 1. Java 網路程式設計(TCP程式設計 和 UDP程式設計)
  • 2. 網路程式設計的概念
  • 3. IP 地址
    • 3.1 IP地址相關的:域名與DNS
  • 4. 埠號(port)
  • 5. 通訊協議
    • 5.1 通訊協議相關的:OSI 參考模型
    • 5.2 通訊協議相關的:TCP / IP 參考模型
    • 5.3 補充:OSI 參考模型 與 TCP / IP 參考模型 區別
  • 6. 網路程式設計基礎類
    • 6.1 InetAddress類
    • 6.2 URL 類
  • 7. TCP 與 UDP協議
    • 7.1 Socket 套接字概述
    • 7.2 TCP 與 UDP協議的區別
    • 7.3 補充:TCP協議的三次握手(通道建立)
    • 7.4 補充:TCP協議的四次揮手(通道關閉)
  • 8. TCP協議程式設計
    • 8.1 Socket類概述
    • 8.2 ServerSocket 類概述
  • 9. 基於TCP協議的程式設計
    • 9.1基於 TCP協議的單向通訊的實現
    • 9.2 基於 TCP協議的雙向通訊的實現
  • 10. 基於 UDP 協議的程式設計
    • 10.1 UDP 協議程式設計概述
    • 10.2 DatagramSocket 類的概述
    • 10.3 DatagramPacket 類的概述
  • 11. 基於UDP協議的程式設計通訊實現
  • 12. 總結:
  • 13. 最後:


2. 網路程式設計的概念

什麼是網路程式設計 ?

網路程式設計是指利用計算機網路實現程式之間通訊的一種程式設計方式。在網路程式設計中,程式需要透過網路協議(如 TCP/IP)來進行通訊,以實現不同計算機之間的資料傳輸和共享。

在網路程式設計中,通常有三個基本要素:

  1. IP 地址:定位網路中某臺計算機
  2. 埠號 port:定位計算機上的某個程序(某個應用)
  3. 通訊協議:透過IP地址和埠號定位後,如何保證資料可靠高效的傳輸,這就需要依靠通訊協議了。

3. IP 地址

IP 地址用於唯一標識網路中的每一臺計算機。在 Internet 上,使用 IPv4 或 IPv6 地址來表示 IP 地址。通常 IPv4 地址格式為 xxx.xxx.xxx.xxx,其中每個 xxx 都表示一個 8 位的二進位制數(每一個xxx的取值範圍是0-255),組合起來可以表示 2^32 個不同的 IP 地址。

IPv4 地址的總數量是4294967296 個,但並不是所有的 IPv4 地址都可以使用。IPv4 地址被分為網路地址和主機地址兩部分,前3個位元組用於表示網路(省市區),最後1個位元組用於表示主機(家門牌)。而一些 IP 地址被保留或者被私有機構使用,不能用於公網的地址分配。另外,一些 IP 地址被用作多播地址,僅用於特定的應用場景。因此實際上可供使用的 IPv4 地址數量要少於總數量,而且隨著 IPv4 地址的逐漸枯竭,IPv6 地址已經開始逐漸普及,IPv6 地址數量更是相當巨大。

IPv6使用16個位元組表示IP地址(128位),這樣就解決了網路地址資源數量不夠的問題。IPv6 地址由 8 組 16 位十六進位制數表示,每組之間用冒號分隔,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984

本機地址:127.0.0.1,主機名:localhost。

192.168.0.0-192.168.255.255為私有地址,屬於非註冊地址,專門為組織機構內部使用。

3.1 IP地址相關的:域名與DNS

域名:

IP地址畢竟是數字標識,使用時不好記憶和書寫,因此在IP地址的基礎上又發展出一種符號化的地址方案,來代替數字型的IP地址。每一個符號化的地址都與特定的IP地址對應。這個與網路上的數字型IP地址相對應的字元型地址,就被稱為域名。

目前域名已經成為網際網路品牌、網上商標保護必備的要素之一,除了識別功能外,還有引導、宣傳等作用。如:www.baidu.com

DNS:

在Internet上域名與IP地址之間是一對一(或者多對一)的,域名雖然便於人們記憶,但機器之間只能互相認識IP地址,它們之間的轉換工作稱為域名解析,域名解析需要由專門的域名解析伺服器來完成,DNS(Domain Name System域名系統)就是進行域名解析的伺服器,域名的最終指向是IP。

4. 埠號(port)

在這裡插入圖片描述

在計算機中,不同的應用程式是透過埠號區分的。

埠號是用兩個位元組(無符號)表示的,它的取值範圍是0~65535,而這些計算機埠可分為3大類:

  1. 公認埠:0~1023。被預先定義的服務通訊佔用(如:HTTP佔用埠80,FTP佔用埠21,Telnet佔用埠23等)
  2. 註冊埠:1024~49151。分配給使用者程序或應用程式。(如:Tomcat佔用埠8080,MySQL佔用埠3306,Oracle佔用埠1521等)。
  3. 動態/私有埠:49152~65535。

通常情況下,伺服器程式使用固定的埠號來監聽客戶端的請求,而客戶端則使用隨機埠連線伺服器。

IP地址好比每個人的地址(門牌號),埠好比是房間號。必須同時指定IP地址和埠號才能夠正確的傳送資料。接下來透過一個圖例來描述IP地址和埠號的作用。

5. 通訊協議

透過計算機網路可以使多臺計算機實現連線,位於同一個網路中的計算機在進行連線和通訊時需要遵守一定的規則。就像兩個人想要順利溝通就必須使用同一種語言一樣,如果一個人只懂英語而另外一個人只懂中文,這樣就會造成沒有共同語言而無法溝通。

在計算機網路中,這些連線和通訊的規則被稱為網路通訊協議,它對資料的傳輸格式、傳輸速率、傳輸步驟等做了統一規定,通訊雙方必須同時遵守才能完成資料交換。

在計算機網路中,常用的協議有 TCP、UDP、HTTP、FTP 等。這些協議規定了資料傳輸的格式、傳輸方式和傳輸順序等細節。其中,TCP(傳輸控制協議)是一種可靠的面向連線的協議,它提供資料傳輸的完整性保證;而 UDP(使用者資料包協議)則是一種無連線的協議,傳輸效率高。在網路程式設計中,需要選取合適的協議型別來實現資料傳輸。

5.1 通訊協議相關的:OSI 參考模型

在這裡插入圖片描述

世界上第一個網路體系結構由IBM公司提出(1974年,SNA),以後其他公司也相繼提出自己的網路體系結構如:Digital公司的DNA,美國國防部的TCP/IP等,多種網路體系結構並存,其結果是若採用IBM的結構,只能選用IBM的產品,只能與同種結構的網路互聯。

為了促進計算機網路的發展,國際標準化組織ISO(International Organization for Standardization)於1977年成立了一個委員會,在現有網路的基礎上,提出了不基於具體機型、作業系統或公司的網路體系結構,稱為開放系統互連參考模型,即OSI/RM (Open System Interconnection Reference Model)。OSI模型把網路通訊的工作分為7層,分別是物理層、資料鏈路層、網路層、傳輸層、會話層、表示層和應用層。

5.2 通訊協議相關的:TCP / IP 參考模型

OSI 參考模型的初衷是提供全世界範圍的計算機網路都要遵循的統一標準,但是由於存在模型和協議自身的缺陷,遲遲沒有成熟的產品推出。TCP/IP協議在實踐中不斷完善和發展取得成功,作為網路的基礎,Internet的語言,可以說沒有TCP/IP參考模型就沒有網際網路的今天。

TCP/IP,即Transmission Control Protocol/Internet Protocol的簡寫,中譯名為傳輸控制協議/因特網互聯協議,是Internet最基本的協議、Internet國際網際網路絡的基礎。

TCP/IP協議是一個開放的網路協議簇,它的名字主要取自最重要的網路層IP協議和傳輸層TCP協議。TCP/IP協議定義了電子裝置如何連入因特網,以及資料如何在它們之間傳輸的標準。TCP/IP參考模型採用4層的層級結構,每一層都呼叫它的下一層所提供的協議來完成自己的需求,這4個層次分別是:網路介面層、網際網路層(IP層)、傳輸層(TCP層)、應用層。

OSI模型與TCP/IP模型的對應關係如圖所示:

在這裡插入圖片描述

5.3 補充:OSI 參考模型 與 TCP / IP 參考模型 區別

  1. OSI 參考模型是理論上的,而 TCP/IP 參考模型是實踐上的。TCP/IP 參考模型被許多實際的協議(如 IP、TCP、HTTP 等)所支援和實現,而 OSI 參考模型則主要是作為理論框架和標準進行研究和討論。
  2. OSI 參考模型是由國際標準化組織提出的網路通訊協議框架,其中分為 7 層,各層之間明確了功能的劃分,從物理層到應用層,逐層向上升,每層只對自己下一層提供服務,並依次封裝和解封資料。OSI 參考模型是一種理論上的協議框架,用於描述計算機系統間的通訊原理和規範。
  3. TCP/IP 參考模型(也稱網際網路參考模型)是實際應用中最廣泛的協議框架。它將網路協議劃分為 4 層:網路介面層、網路層、傳輸層和應用層。TCP/IP 參考模型與 OSI 參考模型之間有著相對應的層次結構,但是其中的每一層都是實際存在的協議,而不是純粹的框架。TCP/IP 參考模型被廣泛應用於網際網路上,是計算機系統間進行通訊的重要基礎。

6. 網路程式設計基礎類

6.1 InetAddress類

在這裡插入圖片描述

在這裡插入圖片描述

java.net.IntAddress 類用來封裝計算機的IP地址和DNS(沒有埠資訊),它包括一個主機名和一個IP地址,是j ava對IP地址的高層表示。大多數其它網路類都要用到這個類,包括Socket、ServerSocket、URL、DatagramSocket、DatagramPacket等

常用靜態方法:

  • static InetAddress getLocalHost() 得到本機的InetAddress物件,其中封裝了IP地址和主機名
  • static InetAddress getByName(String host) 傳入目標主機的名字或IP地址得到對應的InetAddress物件,其中封裝了IP地址和主機名(底層會自動連線DNS伺服器進行域名解析)

常用例項方法:

  • public String getHostAddress() 獲取IP地址
  • public String getHostName() 獲取主機名/域名

編寫執行測試:

在這裡插入圖片描述

package day34.com.rainbowsea.javase.net;


import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * java.net.IntAddress類用來封裝設計計算機IP地址和DNS(沒有埠資訊)
 * 它包括一個主機名和一個地址,是Java對IP地址的高層表示,大多數其它
 * 網路類都要用到這個類,包括 Socket,ServerSocket,URL.DatagramSocket,DatagramPacket等
 */
public class InetAddressTest {
    public static void main(String[] args) throws UnknownHostException {
        // 獲取本機的IP地址和主機名的封裝物件: InetAddress
        InetAddress inetAddress = InetAddress.getLocalHost();

        // 獲取本機的IP地址
        String hostAddress = inetAddress.getHostAddress();
        System.out.println("本機IP地址: " + hostAddress);

        // 獲取本機的主機名
        String hostName = inetAddress.getHostName();
        System.out.println("本機的主機名: " + hostName);

        // 透過域名來獲取InetAddress 物件
        InetAddress inetAddress2 = InetAddress.getByName("www.baidu.com");
        System.out.println(inetAddress2.getHostName());  // www.baidu.com
        System.out.println(inetAddress2.getHostAddress());  // 36.155.132.3


    }
}

在這裡插入圖片描述

6.2 URL 類

URL 是統一資源定位符 ,對可以從網際網路上得到的資源的位置和訪問方法的一種簡潔的表示,是網際網路上標準資源的地址。網際網路上的每個檔案都有一個唯一的URL,它包含的資訊指出檔案的位置以及瀏覽器應該怎麼處理它。

URL由4部分組成:協議、存放資源的主機域名、埠號、資原始檔名。如果未指定該埠號,則使用協議預設的埠。例如HTTP協議的預設埠為80。在瀏覽器中訪問網頁時,位址列顯示的地址就是URL。

URL標準格式為:<協議>://<域名或IP>:<埠>/<路徑> 。其中,<協議>://<域名或IP>是必需的,<埠>/<路徑>有時可省略。如:https://www.baidu.com

為了方便程式設計師程式設計,JDK中提供了URL類,該類的全名是java.net.URL,該類封裝了大量複雜的涉及從遠端站點獲取資訊的細節,可以使用它的各種方法來對URL物件進行分割、合併等處理。

URL類的構造方法:URL url = new URL(“http://127.0.0.1:8080/oa/index.html?name=zhangsan#tip”);

URL類的常用方法:

  • 獲取協議:url.getProtocol()
  • 獲取域名:url.getHost()
  • 獲取預設埠:url.getDefaultPort()
  • 獲取埠:url.getPort()
  • 獲取路徑:url.getPath()
  • 獲取資源:url.getFile()
  • 獲取資料:url.getQuery()
  • 獲取錨點:url.getRef()

編寫執行測試:

在這裡插入圖片描述

package day34.com.rainbowsea.javase.net;


import java.net.MalformedURLException;
import java.net.URL;

/**
 * URL包括四部分:協議:IP地址:埠:資源名稱
 * URL是網路中某個資源的地址,某個資源的唯一地址
 * 透過URL是可以真實的定位到資源的
 * 在Java中,Java類庫提供了一個URL類,來提供對URL的支援
 * URL的類的構造方法:
 *  URL url = new URL("url");
 *  URL類的常用方法
 */
public class URLTest {

    public static void main(String[] args) throws MalformedURLException {
        URL url = new URL("http://www.baidu.com/oa/index.html?name=lihua&passwrod=123#tip");


        // 獲取URL中的資訊
        String protocol = url.getProtocol();
        System.out.println("協議: " + protocol);

        // 獲取資源路徑
        String path = url.getPath();
        System.out.println("資源路徑: " + path);

        // 獲取預設埠(HTTP協議的預設埠是80,HTTPS的協議埠是:443)
        int defaultPort = url.getDefaultPort();
        System.out.println("預設埠: " + defaultPort);


        // 獲取當前的埠
        int port = url.getPort();
        System.out.println("當前埠號: " + port);

        // 獲取URL中的IP地址
        String host = url.getHost();
        System.out.println("主機地址: " + host);

        // 獲取URL準備傳送的資料
        String query = url.getQuery();
        System.out.println("需要提交給伺服器的資料: " + query);

        String ref = url.getRef();
        System.out.println("獲取錨點: " + ref);

        // 獲取 資源路徑 + 資料
        String file = url.getFile();
        System.out.println("獲取資料資原始檔路徑: " + file);
    }
}

使用URL類的 openStream()方法可以開啟到此URL的連線並返回一個用於從該連線讀入的InputStream,實現最簡單的網路爬蟲。

編寫執行測試:

在這裡插入圖片描述

package day34.com.rainbowsea.javase.net;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;

public class URLTest2 {

    public static void main(String[] args) throws IOException {
        // 使用URL類的openStream()方法可以開啟到此URL的連線並返回一個用於從該連線讀入的InputStream,實現最簡單的網路爬蟲
        URL url = new URL("https://tianqi.qq.com/");
        InputStream inputStream = url.openStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

        String s = null;

        while ((s = bufferedReader.readLine()) != null) {
            System.out.println(s);
        }

        bufferedReader.close();
    }
}

7. TCP 與 UDP協議

7.1 Socket 套接字概述

在這裡插入圖片描述

我們開發的網路應用程式位於應用層,TCP和UDP屬於傳輸層協議,在應用層如何使用傳輸層的服務呢?在應用層和傳輸層之間,則是使用套接Socket來進行分離。

套接字就像是傳輸層為應用層開的一個小口,應用程式透過這個小口向遠端傳送資料,或者接收遠端發來的資料。而這個小口以內,也就是資料進入這個口之後,或者資料從這個口出來之前,是不知道也不需要知道的,也不會關心它如何傳輸,這屬於網路其它層次工作。

Socket實際是傳輸層供給應用層的程式設計介面。Socket就是應用層與傳輸層之間的橋樑。使用Socket程式設計可以開發客戶機和伺服器應用程式,可以在本地網路上進行通訊,也可透過Internet在全球範圍內通訊。

TCP協議和UDP協議是傳輸層的兩種協議。Socket是傳輸層供給應用層的程式設計介面,所以Socket程式設計就分為TCP程式設計和UDP程式設計兩類。

7.2 TCP 與 UDP協議的區別

TCP協議:

  1. 使用TCP協議,須先建立TCP連線,形成傳輸資料通道,似於撥打電話
  2. 傳輸前,採用“三次握手”方式,屬於點對點通訊,是面向連線的,效率低。
  3. 僅支援單播傳輸,每條TCP傳輸連線只能有兩個端點(客戶端、服務端)。
  4. 兩個端點的資料傳輸,採用的是“位元組流”來傳輸,屬於可靠的資料傳輸。
  5. 傳輸完畢,需釋放已建立的連線,開銷大,速度慢,適用於檔案傳輸、郵件等。

UDP協議:

  1. 採用資料包(資料、源、目的)的方式來傳輸,無需建立連線,類似於發簡訊。
  2. 每個資料包的大小限制在64K內,超出64k可以分為多個資料包來傳送。
  3. 傳送不管對方是否準備好,接收方即使收到也不確認,因此屬於不可靠的。
  4. 可以廣播傳送,也就是屬於一對一、一對多和多對一連線的通訊協議。
  5. 傳送資料結束時無需釋放資源,開銷小,速度快,適用於視訊會議、直播等。

在這裡插入圖片描述

7.3 補充:TCP協議的三次握手(通道建立)

TCP(傳輸控制協議)是一種面向連線的、可靠的傳輸層協議。它使用三次握手來建立連線,以確保資料在兩個裝置之間可靠地傳輸。

三次握手的過程如下:

  1. 客戶端傳送 SYN(同步)資料包。這個資料包包含客戶端的初始序列號(ISN)。
  2. 伺服器收到 SYN 資料包後,傳送 SYN-ACK(同步確認)資料包。這個資料包包含伺服器的初始序列號(ISN)和對客戶端 ISN 的確認號(ACK)。
  3. 客戶端收到 SYN-ACK 資料包後,傳送 ACK(確認)資料包。這個資料包包含對伺服器 ISN 的確認號(ACK)。三次握手完成後,客戶端和伺服器就可以開始交換資料了。

可以四次,五次握手都可以,握手的目的就是為了,確保連線的建立。至於為什麼是三次,因為三次握手就足夠可以確保連線的成功建立了。多握幾次,也是可以,但是會增加時間,效率上的開銷。

三次握手的意義:

三次握手可以確保資料在兩個裝置之間可靠地傳輸。它可以防止以下情況的發生:

  1. 不會丟失:如果沒有三次握手,客戶端和伺服器可能會同時傳送資料,導致資料丟失。
  2. 不會重複:如果沒有三次握手,客戶端和伺服器可能會重複傳送資料,導致資料重複。
  3. 不會亂序:如果沒有三次握手,客戶端和伺服器可能會亂序傳送資料,導致資料亂序。

在這裡插入圖片描述

7.4 補充:TCP協議的四次揮手(通道關閉)

使用四次揮手來關閉連線,以確保資料在兩個裝置之間可靠地傳輸。

四次揮手的過程如下:

  1. 客戶端傳送 FIN(結束)資料包。這個資料包表示客戶端已經完成資料傳輸,並希望關閉連線。
  2. 伺服器收到 FIN 資料包後,傳送 ACK(確認)資料包。這個資料包表示伺服器已經收到客戶端的 FIN 資料包,並同意關閉連線。
  3. 伺服器傳送 FIN 資料包。這個資料包表示伺服器已經完成資料傳輸,並希望關閉連線。
  4. 客戶端收到 FIN 資料包後,傳送 ACK(確認)資料包。這個資料包表示客戶端已經收到伺服器的 FIN 資料包,並同意關閉連線。四次揮手完成後,客戶端和伺服器之間的連線就關閉了。

同理,五次,六次...等等揮手都可以,揮手的:目的就是為了,確保連線的關閉,不丟失資料。至於為什麼是四次,因為四次揮手就足夠可以確保所有的連線關閉了,資料不丟失。多揮幾次,也是可以,但是會增加時間,效率上的開銷。

四次揮手的意義:

四次揮手可以確保資料在兩個裝置之間可靠地傳輸。它可以防止以下情況的發生:

  1. 如果沒有四次揮手,客戶端和伺服器可能會同時關閉連線,導致資料丟失。
  2. 如果沒有四次揮手,客戶端和伺服器可能會重複傳送資料,導致資料重複。
  3. 如果沒有四次揮手,客戶端和伺服器可能會亂序傳送資料,導致資料亂序。

8. TCP協議程式設計

在這裡插入圖片描述

套接字是一種程序間的資料交換機制,利用套接字(Socket)開發網路應用程式早已被廣泛的採用,以至於成為事實上的標準。

在網路通訊中,第一次主動發起通訊的程式被稱作客戶端(Client),而在第一次通訊中等待連線的程式被稱作服務端(Server)。一旦通訊建立,則客戶端和伺服器端完全一樣,沒有本質的區別。

套接字與主機地址和埠號相關聯,主機地址就是客戶端或伺服器程式所在的主機的IP地址,埠地址是指客戶端或伺服器程式使用的主機的通訊埠。在客戶端和伺服器中,分別建立獨立的Socket,並透過Socket的屬性,將兩個Socket進行連線,這樣客戶端和伺服器透過套接字所建立連線並使用IO流進行通訊。

8.1 Socket類概述

在這裡插入圖片描述

在這裡插入圖片描述

Socket類實現客戶端套接字(Client),套接字是兩臺機器間通訊的端點

Socket類構造方法:

public Socket(InetAddress a, int p)  // 建立套接字並連線到指定IP地址的指定埠號

Socket類例項方法:

public InetAddress getInetAddress()		// 返回此套接字連線到的遠端 IP 地址。
public InputStream getInputStream()		// 返回此套接字的輸入流(接收網路訊息)。
public OutputStream getOutputStream()		// 返回此套接字的輸出流(傳送網路訊息)。
public void shutdownInput()				// 禁用此套接字的輸入流
public void shutdownOutput()				// 禁用此套接字的輸出流。
public synchronized void close()			// 關閉此套接字(預設會關閉IO流)。

8.2 ServerSocket 類概述

在這裡插入圖片描述

在這裡插入圖片描述

ServerSocket 類用於實現伺服器套接字(Server服務端)。伺服器套接字等待請求透過網路傳入。它基於該請求執行某些操作,然後可能向請求者返回結果。

ServerSocket構造方法:

public ServerSocket(int port)

ServerSocket例項方法:

public Socket accept()				// 偵聽要連線到此套接字並接受它。
public InetAddress getInetAddress()	// 返回此伺服器套接字的本地地址。
public void close()				// 關閉此套接字。

9. 基於TCP協議的程式設計

9.1基於 TCP協議的單向通訊的實現

Java語言的基於套接字程式設計分為服務端程式設計和客戶端程式設計,其通訊模型如圖所示

在這裡插入圖片描述

伺服器端實現步驟:

  1. 建立ServerSocket物件,繫結並監聽埠;
  2. 透過accept監聽客戶端的請求;
  3. 建立連線後,透過輸出輸入流進行讀寫操作;
  4. 呼叫close()方法關閉資源。

伺服器端的程式碼編寫如下:

在這裡插入圖片描述

package day34.com.rainbowsea.javase.net.onewaycommunication;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket clientSocket = null;
        BufferedReader bufferedReader = null;

        try {
            // 先啟動服務端,啟動伺服器端後,這個應用肯定要對應一個埠
            // 建立伺服器端套接字物件
            int port = 8888;  // 指明埠
            serverSocket = new ServerSocket(port);
            System.out.println("伺服器端正在啟動,請稍後...");
            System.out.println("伺服器端啟動成功,埠號: " + port + ",等待客戶端的請求");


            // 開始接收客戶端的請求
            clientSocket = serverSocket.accept();

            // 後續程式碼怎麼寫一會再說?
            // 服務端接收訊息,所以服務端應該獲取輸入流
            bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

            // 開始讀
            String s = null;
            while ((s = bufferedReader.readLine()) != null) {
                System.out.println(s);
            }


        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            // 關閉服務端套接字
            try {
                serverSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            try {
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            try {
                bufferedReader.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }
    }
}

客戶端實現步驟:

  1. 建立Socket物件,指定服務端的地址和埠號;
  2. 建立連線後,透過輸入輸出流進行讀寫操作;
  3. 透過輸出輸入流獲取伺服器返回資訊;
  4. 呼叫close()方法關閉資源。

客戶端的程式碼編寫如下:

在這裡插入圖片描述

package day34.com.rainbowsea.javase.net.onewaycommunication;


import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

/**
 * 現在使用Java中的 Socket實現單向通訊,基於 TCP協議,屬於TCP程式設計
 */
public class Client {
    public static void main(String[] args) {
        Socket clientSocket = null;
        BufferedWriter bufferedWriter = null;
        Scanner scanner = new Scanner(System.in);

        // 建立客戶端套接字物件
        // 需要指定伺服器的IP地址,和埠號
        try {
            InetAddress localHost = InetAddress.getLocalHost();
            int port = 8888;
            clientSocket = new Socket(localHost, port);

            // 客戶端給伺服器端傳送資訊
            // 客戶端你是輸出流
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));

            // 傳送資訊
        /*    bufferedWriter.write("你好,最近怎麼樣");
            bufferedWriter.write("\n");
            bufferedWriter.write("你收到訊息了嗎");*/

            // 迴圈傳送資訊
        /*    while (true) {
                bufferedWriter.write("你好,最近怎麼樣");
                bufferedWriter.write("\n");
                bufferedWriter.write("你收到訊息了嗎");
                // 因為使用了快取機制,需要記得重新整理
                bufferedWriter.flush();

                // 延遲效果
                Thread.sleep(1000);
            }*/


            // 鍵盤中輸入資訊,傳送給伺服器端
            while (true) {
                System.out.println("請輸入您要傳送的資訊: ");
                // 從鍵盤上接收的訊息
                String msg = scanner.next();

                // 把訊息傳送給伺服器端
                bufferedWriter.write(msg);

                bufferedWriter.write("\n"); // 換行

                // 重新整理
                bufferedWriter.flush();

            }

            // 因為使用了快取機制,需要記得重新整理
            //bufferedWriter.flush();

        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            try {
                bufferedWriter.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            scanner.close();
        }


    }
}

執行測試:

注意:一定是先啟動伺服器程式,然後再啟動客戶端程式,先後順序千萬別弄混了!

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

9.2 基於 TCP協議的雙向通訊的實現

在雙向通訊的案例中,客戶端需要向服務端傳送一張圖片,服務端收到客戶端傳送的圖片後,則需要向客戶端回覆收到圖片的反饋。在客戶端給服務端傳送圖片的時候,圖片傳送完畢必須呼叫shutdownOutput()方法來關閉socket輸出流,否則服務端讀取資料就會一直阻塞。

伺服器端實現步驟:

  1. 建立ServerSocket物件,繫結監聽埠;
  2. 透過accept()方法監聽客戶端請求;
  3. 使用輸入流接收客戶端傳送的圖片,然後透過輸出流儲存圖片
  4. 透過輸出流返回客戶端圖片收到。
  5. 呼叫close()方法關閉資源

服務端的程式碼編寫:

在這裡插入圖片描述

package day34.com.rainbowsea.javase.net.twowaycommunication;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 雙向通訊
 */
public class TwoWayServer {

    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket clientSocket = null;
        BufferedInputStream bufferedInputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        BufferedWriter bufferedWriter = null;

        try {
            // 建立伺服器套接字物件
            int port = 8888;  // 埠號
            serverSocket = new ServerSocket(port);

            System.out.println("伺服器啟動成功,正在接收客戶端的請求");

            // 開始接收客戶端的請求
            clientSocket = serverSocket.accept();

            // 獲取輸入流
            bufferedInputStream = new BufferedInputStream(clientSocket.getInputStream());

            // 新建輸出流,輸出讀取到的資訊,到硬碟當中
            //new BufferedOutputStream(new FileOutputStream("本地伺服器硬碟地址"))
            bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("./test.jpg"));

            // 開始讀
            byte[] bytes = new byte[1024];
            int readCount = 0;
            while ((readCount = bufferedInputStream.read(bytes)) != -1) {
                // 把客戶端傳送過來的圖片,儲存到本地伺服器中
                bufferedOutputStream.write(bytes, 0, readCount);
            }

            // 重新整理
            bufferedOutputStream.flush();

            // 伺服器接收完圖片之後給客戶端回個話
             bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
            bufferedWriter.write("你發的圖片我已經收到了");

            // 重新整理
            bufferedWriter.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                serverSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            try {
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            try {
                bufferedInputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            try {
                bufferedOutputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            try {
                bufferedWriter.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

客戶端實現步驟:

  1. 建立socket物件,指明需要連線的伺服器地址和埠號;
  2. 建立連線後,透過輸出流向伺服器端傳送圖片;
  3. 透過輸入流獲取伺服器的響應資訊;
  4. 呼叫close()方法關閉資源

客戶端的程式碼編寫:

在這裡插入圖片描述

所在圖片的路徑如下:

在這裡插入圖片描述

執行測試:

同樣注意:注意:一定是先啟動伺服器程式,然後再啟動客戶端程式,先後順序千萬別弄混了!

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

10. 基於 UDP 協議的程式設計

10.1 UDP 協議程式設計概述

在UDP通訊協議下,兩臺計算機之間進行資料互動,並不需要先建立連線,傳送端直接往指定的IP和埠號上傳送資料即可,但是它並不能保證資料一定能讓對方收到,也不能確定什麼時候可以送達。

java.net.DatagramSocket類java.net.DatagramPacket類 是使用UDP程式設計中需要使用的兩個類,並且傳送端接收端 都需要使用這個倆類,並且 傳送端與接收端是兩個獨立的執行程式。

  1. DatagramSocket:負責接收和傳送資料,建立接收端時需要指定埠號。
  2. DatagramPacket:負責把資料打包,建立傳送端時需指定接收端的IP地址和埠。

10.2 DatagramSocket 類的概述

在這裡插入圖片描述

在這裡插入圖片描述

DatagramSocket類作為基於UDP協議的Socket,使用DatagramSocket類可以用於接收和傳送資料,同時建立接收端時還需指定埠號。

DatagramSocket的構造方法:

public DatagramSocket()			// 建立傳送端的資料包套接字
public DatagramSocket(int port)		// 建立接收端的資料包套接字,並指定埠號

DatagramSocket的例項方法:

public void send(DatagramPacket p)	// 傳送資料包。
public void receive(DatagramPacket p)	// 接收資料包。
public void close()				// 關閉資料包套接字。

10.3 DatagramPacket 類的概述

在這裡插入圖片描述

在這裡插入圖片描述

DatagramPacket類負責把傳送的資料打包(打包的資料為byte型別的陣列),並且建立傳送端時需指定接收端的IP地址和埠。

DatagramPacket的構造方法:

public DatagramPacket(byte buf[], int offset, int length) // 建立接收端的資料包。
public DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port) // 建立傳送端的資料包,並指定接收端的IP地址和埠號。

DatagramPacket的例項方法:

public synchronized byte[] getData() // 返回資料包中儲存的資料
public synchronized int getLength()  // 獲得傳送或接收資料包中的長度

11. 基於UDP協議的程式設計通訊實現

接收端實現步驟:

  1. 建立DatagramSocket物件(接收端),並指定埠號;
  2. 建立DatagramPacket物件(資料包);
  3. 呼叫receive()方法,用於接收資料包;
  4. 呼叫close()方法關閉資源

接收端的程式碼編寫:
在這裡插入圖片描述

package day34.com.rainbowsea.javase.net.udpcommunication;




import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * UDP程式設計,接收端
 */
public class Receive {
    public static void main(String[] args) throws Exception {
        // 建立 UDP的 Socket 套接字
        DatagramSocket datagramSocket = new DatagramSocket(8888);

        byte[] bytes = new byte[1024 * 64];
        // 準備一個包,這個包接收傳送方的資訊
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);

        // 程式執行到這裡,停下來,等待傳送方的傳送
        datagramSocket.receive(datagramPacket);

        // 程式執行到這裡說明,已經完全將傳送方傳送的資料接收到了
        // 從包中取出來資料

        String msg = new String(bytes, 0, datagramPacket.getLength());

        System.out.println("接收到傳送方發過來的訊息: " + msg);

        datagramSocket.close();

    }
}

傳送端的程式碼編寫:

在這裡插入圖片描述

package day34.com.rainbowsea.javase.net.udpcommunication;


import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * UDP程式設計, 傳送端
 */
public class Send {
    public static void main(String[] args) throws Exception {
        // 建立一個 UDP的Socket 套接字
        DatagramSocket datagramSocket = new DatagramSocket();

        //  建立包
        byte[] bytes = "RainbowSea".getBytes();
        DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length, InetAddress.getLocalHost(), 8888);

        // 傳送訊息,將封裝到包(datagramPacket) 中的資訊傳送過去
        datagramSocket.send(datagramPacket);

        datagramSocket.close();
    }
}

執行測試:注意:先啟動接收端,再啟動傳送端

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

12. 總結:

Java SE 中的網路程式設計主要還是理解:網路協議:TCP協議和UDP協議,特別是 TCP協議的三次握手,和四次揮手。

而Java SE 的網路程式設計,這些我們後續的 Tomcat Web 框架當中都是封裝好了的,並不需要我們真的自己重寫這些底層的方法。我們只需要呼叫就好了。所以關於這部分的內容大家瞭解即可。

13. 最後:

限於自身水平,其中存在的錯誤,希望大家給予指教,韓信點兵——多多益善,謝謝大家,江湖再見,後會有期 !!!

在這裡插入圖片描述

相關文章