javaSE學習筆記(16)---網路編程

来源:https://www.cnblogs.com/xjtu-lyh/archive/2020/02/16/12319094.html
-Advertisement-
Play Games

javaSE學習筆記(16) 網路編程 基本概念 如今,電腦已經成為人們學習、工作、生活必不可少的工具。我們利用電腦可以和親朋好友網上聊天,也可以玩網游、發郵件等等,這些功能實現都離不開電腦網路。電腦網路實現了不同電腦之間的通信,這必須依靠編寫網路程式來實現。下麵,我們將教大家如何編寫網路 ...


javaSE學習筆記(16)---網路編程

基本概念

如今,電腦已經成為人們學習、工作、生活必不可少的工具。我們利用電腦可以和親朋好友網上聊天,也可以玩網游、發郵件等等,這些功能實現都離不開電腦網路。電腦網路實現了不同電腦之間的通信,這必須依靠編寫網路程式來實現。下麵,我們將教大家如何編寫網路程式。

在學習編程之前,我們首先要瞭解關於網路通信的一些概念。

▪ 什麼是電腦網路?

電腦網路是指將地理位置不同的具有獨立功能的多台電腦及其外部設備,通過通信線路連接起來,在網路操作系統,網路管理軟體及網路通信協議的管理和協調下,實現資源共用和信息傳遞的電腦系統。

從其中我們可以提取到以下內容:

  1. 電腦網路的作用:資源共用和信息傳遞。

    1. 電腦網路的組成:

​ a) 電腦硬體:電腦(大中小型伺服器,台式機、筆記本等)、外部設備(路由器、交換機等)、通信線路(雙絞線、光纖等)。

​ b) 電腦軟體:網路操作系統(Windows 2000 Server/Advance Server、Unix、Linux等)、網路管理軟體(WorkWin、SugarNMS等)、網路通信協議(如TCP/IP協議棧等)。

  1. 電腦網路的多台電腦是具有獨立功能的,而不是脫離網路就無法存在的。

▪ 什麼是網路通信協議?

通過電腦網路可以實現不同電腦之間的連接與通信,但是電腦網路中實現通信必須有一些約定即通信協議,對速率、傳輸代碼、代碼結構、傳輸控制步驟、出錯控制等制定標準。就像兩個人想要順利溝通就必須使用同一種語言一樣,如果一個人只懂英語而另外一個人只懂中文,這樣就會造成沒有共同語言而無法溝通。

國際標準化組織(ISO,即International Organization for Standardization)定義了網路通信協議的基本框架,被稱為OSI(Open System Interconnect,即開放系統互聯)模型。要制定通訊規則,內容會很多,比如要考慮A電腦如何找到B電腦,A電腦在發送信息給B電腦時是否需要B電腦進行反饋,A電腦傳送給B電腦的數據格式又是怎樣的?內容太多太雜,所以OSI模型將這些通訊標準進行層次劃分,每一層次解決一個類別的問題,這樣就使得標準的制定沒那麼複雜。OSI模型制定的七層標準模型,分別是:應用層,表示層,會話層,傳輸層,網路層,數據鏈路層,物理層。

OSI七層協議模型如圖所示:

圖12-1 七層協議模型.png

雖然國際標準化組織制定了這樣一個網路通信協議的模型,但是實際上互聯網通訊使用最多的網路通信協議是TCP/IP網路通信協議。

TCP/IP 是一個協議族,也是按照層次劃分,共四層:應用層,傳輸層,互連網路層,網路介面層(物理+數據鏈路層)。

那麼TCP/IP協議和OSI模型有什麼區別呢?OSI網路通信協議模型,是一個參考模型,而TCP/IP協議是事實上的標準。TCP/IP協議參考了OSI 模型,但是並沒有嚴格按照OSI規定的七層標準去劃分,而只劃分了四層,這樣會更簡單點,當劃分太多層次時,你很難區分某個協議是屬於哪個層次的。TCP/IP協議和OSI模型也並不衝突,TCP/IP協議中的應用層協議,就對應於OSI中的應用層,表示層,會話層。就像以前有工業部和信息產業部,現在實行大部制後只有工業和信息化部一個部門,但是這個部門還是要做以前兩個部門一樣多的事情,本質上沒有多大的差別。TCP/IP中有兩個重要的協議,傳輸層的TCP協議和互連網路層的IP協議,因此就拿這兩個協議做代表,來命名整個協議族了,再說TCP/IP協議時,是指整個協議族。

▪ 網路協議的分層

由於網路結點之間聯繫很複雜,在制定協議時,把複雜成份分解成一些簡單的成份,再將它們複合起來。最常用的複合方式是層次方式,即同層間可以通信、上一層可以調用下一層,而與再下一層不發生關係。

把用戶應用程式作為最高層,把物理通信線路作為最低層,將其間的協議處理分為若幹層,規定每層處理的任務,也規定每層的介面標準。

ISO模型與TCP/IP模型的對應關係如圖所示。 開放系統互連參考模型與TCP/IP參考模型對比

圖12-2 開放系統互連參考模型與TCPIP參考模型對比.png

▪ 數據封裝與解封:

由於用戶傳輸的數據一般都比較大,有的可以達到MB位元組,一次性發送出去十分困難,於是就需要把數據分成許多片段,再按照一定的次序發送出去。這個過程就需要對數據進行封裝。

數據封裝(Data Encapsulation)是指將協議數據單元(PDU)封裝在一組協議頭和協議尾中的過程。在OSI七層參考模型中,每層主要負責與其它機器上的對等層進行通信。該過程是在協議數據單元(PDU)中實現的,其中每層的PDU一般由本層的協議頭、協議尾和數據封裝構成。

1.數據發送處理過程

​ (1)應用層將數據交給傳輸層,傳輸層添加上TCP的控制信息(稱為TCP頭部),這個數據單元稱為段(Segment),加入控制信息的過程稱為封裝。然後,將段交給網路層。

​ (2)網路層接收到段,再添加上IP頭部,這個數據單元稱為包(Packet)。然後,將包交給數據鏈路層。

​ (3) 數據鏈路層接收到包,再添加上MAC頭部和尾部,這個數據單元稱為幀(Frame)。然後,將幀交給物理層。

​ (4)物理層將接收到的數據轉化為比特流,然後在網線中傳送。

2.數據接收處理過程

​ (1)物理層接收到比特流,經過處理後將數據交給數據鏈路層。

​ (2)數據鏈路層將接收到的數據轉化為數據幀,再除去MAC頭部和尾部,這個除去控制信息的過程稱為解封,然後將包交給網路層。

​ (3)網路層接收到包,再除去IP頭部,然後將段交給傳輸層。

​ (4)傳輸層接收到段,再除去TCP頭部,然後將數據交給應用層。

從以上傳輸過程中,可以總結出以下規則:

​ (1)發送方數據處理的方式是從高層到底層,逐層進行數據封裝。

​ (2)接收方數據處理的方式是從底層到高層,逐層進行數據解封裝。

接收方的每一層只把對該層有意義的數據拿走,或者說每一層只能處理髮送方同等層的數據,然後把其餘的部分傳遞給上一層,這就是對等層通信的概念。

數據封裝與解封如圖所示:

數據封裝

圖12-3 數據封裝.png

數據解封

圖12-4 數據解封.png

▪ IP地址:

用來標識網路中的一個通信實體的地址。通信實體可以是電腦、路由器等。 比如互聯網的每個伺服器都要有自己的IP地址,而每個區域網的電腦要通信也要配置IP地址。路由器是連接兩個或多個網路的網路設備。

目前主流使用的IP地址是IPV4,但是隨著網路規模的不斷擴大,IPV4面臨著枯竭的危險,所以推出了IPV6。

IPV4:32位地址,並以8位為一個單位,分成四部分,以點分十進位表示,如192.168.0.1。因為8位二進位的計數範圍是00000000---11111111,對應十進位的0-255,所以-4.278.4.1是錯誤的IPV4地址。

IPV6:128位(16個位元組)寫成8個16位的無符號整數,每個整數用四個十六進位位表示,每個數之間用冒號(:)分開,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984

註意事項

  1. 127.0.0.1 本機地址

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

▪ 埠:

IP地址用來標識一臺電腦,但是一臺電腦上可能提供多種網路應用程式,如何來區分這些不同的程式呢?這就要用到埠。

埠是虛擬的概念,並不是說在主機上真的有若幹個埠。通過埠,可以在一個主機上運行多個網路應用程式。 埠的表示是一個16位的二進位整數,對應十進位的0-65535。

Oracle、MySQL、Tomcat、QQ、msn、迅雷、電驢、360等網路程式都有自己的埠。

總結

  1. IP地址好比每個人的地址(門牌號),埠好比是房間號。必須同時指定IP地址和埠號才能夠正確的發送數據。

    \2. IP地址好比為電話號碼,而埠號就好比為分機號。

▪ URL:

在www上,每一信息資源都有統一且唯一的地址,該地址就叫URL(Uniform Resource Locator),它是www的統一資源定位符。URL由4部分組成:協議 、存放資源的主機功能變數名稱、資源文件名和埠號。如果未指定該埠號,則使用協議預設的埠。例如http 協議的預設埠為 80。 在瀏覽器中訪問網頁時,地址欄顯示的地址就是URL。

在java.net包中提供了URL類,該類封裝了大量複雜的涉及從遠程站點獲取信息的細節。

▪ Socket:

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

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

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

socket的作用

圖12-5 Socket的作用.png

TCP協議和UDP協議的聯繫和區別

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

在網路通訊中,TCP方式就類似於撥打電話,使用該種方式進行網路通訊時,需要建立專門的虛擬連接,然後進行可靠的數據傳輸,如果數據發送失敗,則客戶端會自動重發該數據。而UDP方式就類似於發送簡訊,使用這種方式進行網路通訊時,不需要建立專門的虛擬連接,傳輸也不是很可靠,如果發送失敗則客戶端無法獲得。

這兩種傳輸方式都在實際的網路編程中使用,重要的數據一般使用TCP方式進行數據傳輸,而大量的非核心數據則可以通過UDP方式進行傳遞,在一些程式中甚至結合使用這兩種方式進行數據傳遞。

由於TCP需要建立專用的虛擬連接以及確認傳輸是否正確,所以使用TCP方式的速度稍微慢一些,而且傳輸時產生的數據量要比UDP稍微大一些。

總結

  1. TCP是面向連接的,傳輸數據安全,穩定,效率相對較低。

    1. UDP是面向無連接的,傳輸數據不安全,效率較高。

TCP協議

TCP(Transfer Control Protocol)是面向連接的,所謂面向連接,就是當電腦雙方通信時必需經過先建立連接,然後傳送數據,最後拆除連接三個過程。

TCP在建立連接時又分三步走:

第一步,是請求端(客戶端)發送一個包含SYN即同步(Synchronize)標誌的TCP報文,SYN同步報文會指明客戶端使用的埠以及TCP連接的初始序號。

第二步,伺服器在收到客戶端的SYN報文後,將返回一個SYN+ACK的報文,表示客戶端的請求被接受,同時TCP序號被加一,ACK即確認(Acknowledgement)。

第三步,客戶端也返回一個確認報文ACK給伺服器端,同樣TCP序列號被加一,到此一個TCP連接完成。然後才開始通信的第二步:數據處理。

這就是所說的TCP的三次握手(Three-way Handshake)。

UDP協議

基於TCP協議可以建立穩定連接的點對點的通信。這種通信方式實時、快速、安全性高,但是很占用系統的資源。

在網路傳輸方式上,還有另一種基於UDP協議的通信方式,稱為數據報通信方式。在這種方式中,每個數據發送單元被統一封裝成數據報包的方式,發送方將數據報包發送到網路中,數據報包在網路中去尋找它的目的地。

java網路編程

Java為了可移植性,不允許直接調用操作系統,而是由java.net包來提供網路功能。Java虛擬機負責提供與操作系統的實際連接。下麵我們來介紹幾個java.net包中的常用的類。

InetAddress

作用:封裝電腦的IP地址和DNS(沒有埠信息)。

​ 註:DNS是Domain Name System,功能變數名稱系統。

特點:這個類沒有構造方法。如果要得到對象,只能通過靜態方法:getLocalHost()、getByName()、 getAllByName()、 getAddress()、getHostName()。

使用getLocalHost方法創建InetAddress對象

import java.net.InetAddress;
import java.net.UnknownHostException;
public class Test1 {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress addr = InetAddress.getLocalHost();
        //返回IP地址:192.168.1.110
        System.out.println(addr.getHostAddress()); 
        //輸出電腦名:gaoqi
        System.out.println(addr.getHostName());     
    }
}

根據功能變數名稱得到InetAddress對象

import java.net.InetAddress;
import java.net.UnknownHostException;
public class Test2 {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress addr = InetAddress.getByName("www.sxt.cn");
        // 返回 sxt伺服器的IP:59.110.14.7
        System.out.println(addr.getHostAddress());
        // 輸出:www.sxt.cn
        System.out.println(addr.getHostName());
    }
}

根據IP得到InetAddress對象

import java.net.InetAddress;
import java.net.UnknownHostException;
public class Test3 {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress addr = InetAddress.getByName("59.110.14.7");
        // 返回sxt伺服器的IP:59.110.14.7
        System.out.println(addr.getHostAddress());
        /*
         * 輸出ip而不是功能變數名稱。如果這個IP地址不存在或DNS伺服器不允許進行IP地址
         * 和功能變數名稱的映射,getHostName方法就直接返回這個IP地址。
         */
        System.out.println(addr.getHostName());
    }
}

InetSocketAddress

作用:包含IP和埠信息,常用於Socket通信。此類實現 IP 套接字地址(IP 地址 + 埠號),不依賴任何協議。

InetSocketAddress的使用

import java.net.InetSocketAddress;
public class Test4 {
    public static void main(String[] args) {
        InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8080);
        InetSocketAddress socketAddress2 = new InetSocketAddress("localhost", 9000);
        System.out.println(socketAddress.getHostName());
        System.out.println(socketAddress2.getAddress());
    }
}

URL類

IP地址唯一標識了Internet上的電腦,而URL則標識了這些電腦上的資源。類 URL 代表一個統一資源定位符,它是指向互聯網“資源”的指針。資源可以是簡單的文件或目錄,也可以是對更為複雜的對象的引用,例如對資料庫或搜索引擎的查詢。

為了方便程式員編程,JDK中提供了URL類,該類的全名是java.net.URL,有了這樣一個類,就可以使用它的各種方法來對URL對象進行分割、合併等處理。

URL類的使用

import java.net.MalformedURLException;
import java.net.URL;
public class Test5 {
    public static void main(String[] args) throws MalformedURLException {
        URL u = new URL("http://www.google.cn:80/webhp#aa?canhu=33");
        System.out.println("獲取與此url關聯的協議的預設埠:" + u.getDefaultPort());
        System.out.println("getFile:" + u.getFile()); // 埠號後面的內容
        System.out.println("主機名:" + u.getHost()); // www.google.cn
        System.out.println("路徑:" + u.getPath()); // 埠號後,參數前的內容
        // 如果www.google.cn:80則返回80.否則返回-1
        System.out.println("埠:" + u.getPort()); 
        System.out.println("協議:" + u.getProtocol());
        System.out.println("參數部分:" + u.getQuery());
        System.out.println("錨點:" + u.getRef());
 
        URL u1 = new URL("http://www.abc.com/aa/");
        URL u2 = new URL(u, "2.html"); // 相對路徑構建url對象
        System.out.println(u2.toString()); // http://www.abc.com/aa/2.html
    }
}

最簡單的網路爬蟲

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 Test6 {
    public static void main(String[] args) {
        basicSpider();
    }
    //網路爬蟲
    static void basicSpider() {
        URL url = null;
        InputStream is = null;
        BufferedReader br = null;
        StringBuilder sb = new StringBuilder();
        String temp = "";
        try {
            url = new URL("http://www.baidu.com");
            is = url.openStream();
            br = new BufferedReader(new InputStreamReader(is));
            /* 
             * 這樣就可以將網路內容下載到本地機器。
             * 然後進行數據分析,建立索引。這也是搜索引擎的第一步。
             */
            while ((temp = br.readLine()) != null) {
                sb.append(temp);
            }
            System.out.println(sb);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

基於TCP協議的Socket編程和通信

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

“請求-響應”模式:

  1. Socket類:發送TCP消息。

    1. ServerSocket類:創建伺服器。

    套接字是一種進程間的數據交換機制。這些進程既可以在同一機器上,也可以在通過網路連接的不同機器上。換句話說,套接字起到通信端點的作用。單個套接字是一個端點,而一對套接字則構成一個雙向通信通道,使非關聯進程可以在本地或通過網路進行數據交換。一旦建立套接字連接,數據即可在相同或不同的系統中雙向或單向發送,直到其中一個端點關閉連接。套接字與主機地址和埠地址相關聯。主機地址就是客戶端或伺服器程式所在的主機的IP地址。埠地址是指客戶端或伺服器程式使用的主機的通信埠。

    在客戶端和伺服器中,分別創建獨立的Socket,並通過Socket的屬性,將兩個Socket進行連接,這樣,客戶端和伺服器通過套接字所建立的連接使用輸入輸出流進行通信。

    TCP/IP套接字是最可靠的雙向流協議,使用TCP/IP可以發送任意數量的數據。

    實際上,套接字只是電腦上已編號的埠。如果發送方和接收方電腦確定好埠,他們就可以通信了。

    如圖所示為客戶端與伺服器端的通信關係圖:

圖12-6 客戶端與伺服器端的通信關係圖.png

TCP/IP通信連接的簡單過程:

位於A電腦上的TCP/IP軟體向B電腦發送包含埠號的消息,B電腦的TCP/IP軟體接收該消息,併進行檢查,查看是否有它知道的程式正在該埠上接收消息。如果有,他就將該消息交給這個程式。

要使程式有效地運行,就必須有一個客戶端和一個伺服器。

通過Socket的編程順序:

  1. 創建伺服器ServerSocket,在創建時,定義ServerSocket的監聽埠(在這個埠接收客戶端發來的消息)。

    1. ServerSocket調用accept()方法,使之處於阻塞狀態。

    2. 創建客戶端Socket,並設置伺服器的IP及埠。

    3. 客戶端發出連接請求,建立連接。

    4. 分別取得伺服器和客戶端Socket的InputStream和OutputStream。

    5. 利用Socket和ServerSocket進行數據傳輸。

  2. 關閉流及Socket。

TCP:單向通信Socket之伺服器端

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
 
/**
 * 最簡單的伺服器端代碼
 * @author Administrator
 */
public class BasicSocketServer {
    public static void main(String[] args) {
        Socket socket = null;
        BufferedWriter bw = null;
        try {
            // 建立伺服器端套接字:指定監聽的介面
            ServerSocket serverSocket = new ServerSocket(8888);
            System.out.println("服務端建立監聽");
            // 監聽,等待客戶端請求,並願意接收連接
            socket = serverSocket.accept();
            // 獲取socket的輸出流,並使用緩衝流進行包裝
            bw = new BufferedWriter(new     
                                    OutputStreamWriter(socket.getOutputStream()));
            // 向客戶端發送反饋信息
            bw.write("hhhh");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 關閉流及socket連接
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

TCP:單向通信Socket之客戶端

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Socket;
/**
 * 最簡單的Socket客戶端
 * @author Administrator
 */
public class BasicSocketClient {
    public static void main(String[] args) {
        Socket socket = null;
        BufferedReader br = null;
        try {
            /*
             * 創建Scoket對象:指定要連接的伺服器的IP和埠而不是自己機器的
             * 埠。發送埠是隨機的。
             */
            socket = new Socket(InetAddress.getLocalHost(), 8888);
            //獲取scoket的輸入流,並使用緩衝流進行包裝
            br = new BufferedReader(new  
                                   InputStreamReader(socket.getInputStream()));
            //接收伺服器端發送的信息
            System.out.println(br.readLine());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 關閉流及socket連接
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

TCP:雙向通信Socket之伺服器端

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
 
public class Server {
    public static void main(String[] args){
        Socket socket = null;
        BufferedReader in = null;
        BufferedWriter out = null;
        BufferedReader br = null;
        try {
            //創建伺服器端套接字:指定監聽埠
            ServerSocket server = new ServerSocket(8888);
            //監聽客戶端的連接
            socket = server.accept();
            //獲取socket的輸入輸出流接收和發送信息
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new BufferedWriter(new 
                                   OutputStreamWriter(socket.getOutputStream()));
            br = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                //接收客戶端發送的信息
                String str = in.readLine();
                System.out.println("客戶端說:" + str);
                String str2 = "";
                //如果客戶端發送的是“end”則終止連接 
                if (str.equals("end")){
                    break;
                }
                //否則,發送反饋信息
                str2 = br.readLine(); // 讀到\n為止,因此一定要輸入換行符!
                out.write(str2 + "\n");
                out.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //關閉資源
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(br != null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

TCP:雙向通信Socket之客戶端

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
 
public class Client {
    public static void main(String[] args) {
        Socket socket = null;
        BufferedReader in = null;
        BufferedWriter out = null;
        BufferedReader wt = null;
        try {
            //創建Socket對象,指定伺服器端的IP與埠
            socket = new Socket(InetAddress.getLocalHost(), 8888);
            //獲取scoket的輸入輸出流接收和發送信息
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new BufferedWriter(new 
                                   OutputStreamWriter(socket.getOutputStream()));
            wt = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                //發送信息
                String str = wt.readLine();
                out.write(str + "\n");
                out.flush();
                //如果輸入的信息為“end”則終止連接
                if (str.equals("end")) {
                    break;
                }
                //否則,接收並輸出伺服器端信息
                System.out.println("伺服器端說:" + in.readLine());
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 關閉資源
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (wt != null) {
                try {
                    wt.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

執行結果如圖所示: 運行效果圖—伺服器端

圖12-7示例12-9與12-10運行效果圖—伺服器端.png

運行效果圖—客戶端:

圖12-8示例12-9與12-10運行效果圖—客戶端.png

菜鳥雷區

運行時,要先啟動伺服器端,再啟動客戶端,才能得到正常的運行效果。

但是,上面這個程式,必須按照安排好的順序,伺服器和客戶端一問一答!不夠靈活!!可以使用多線程實現更加靈活的雙向通訊!!

伺服器端:一個線程專門發送消息,一個線程專門接收消息。

客戶端:一個線程專門發送消息,一個線程專門接收消息。

TCP:聊天室之伺服器端

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
 
public class ChatServer {
    public static void main(String[] args) {
        ServerSocket server = null;
        Socket socket = null;
        BufferedReader in = null;
        try {
            server = new ServerSocket(8888);
            socket = server.accept();
            //創建向客戶端發送消息的線程,並啟動
            new ServerThread(socket).start();
            // main線程負責讀取客戶端發來的信息
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while (true) {
                String str = in.readLine();
                System.out.println("客戶端說:" + str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
 
/**
 * 專門向客戶端發送消息的線程
 * 
 * @author Administrator
 *
 */
class ServerThread extends Thread {
    Socket ss;
    BufferedWriter out;
    BufferedReader br;
 
    public ServerThread(Socket ss) {
        this.ss = ss;
        try {
            out = new BufferedWriter(new OutputStreamWriter(ss.getOutputStream()));
            br = new BufferedReader(new InputStreamReader(System.in));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    public void run() {
        try {
            while (true) {
                String str2 = br.readLine();
                out.write(str2 + "\n");
                out.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(out != null){
                out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(br != null){
                    br.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

TCP:聊天室之客戶端

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
 
public class ChatClient {
    public static void main(String[] args) {
        Socket socket = null;
        BufferedReader in = null;
        try {
            socket = new Socket(InetAddress.getByName("127.0.1.1"), 8888);
            // 創建向伺服器端發送信息的線程,並啟動
            new ClientThread(socket).start();
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // main線程負責接收伺服器發來的信息
            while (true) {
                System.out.println("伺服器說:" + in.readLine());
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
 
/**
 * 用於向伺服器發送消息
 * 
 * @author Administrator
 *
 */
class ClientThread extends Thread {
    Socket s;
    BufferedWriter out;
    BufferedReader wt;
 
    public ClientThread(Socket s) {
        this.s = s;
        try {
            out = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            wt = new BufferedReader(new InputStreamReader(System.in));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    public void run() {
        try {
            while (true) {
                String str = wt.readLine();
                out.write(str + "\n");
                out.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (wt != null) {
                    wt.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

執行結果如圖所示:運行效果圖—伺服器端

圖12-9 示例12-11與12-12運行效果圖—伺服器端.png

運行效果圖—客戶端:

圖12-10 示例12-11與12-12運行效果圖—客戶端.png

UDP通訊的實現

DatagramSocket:用於發送或接收數據報包

當伺服器要向客戶端發送數據時,需要在伺服器端產生一個DatagramSocket對象,在客戶端產生一個DatagramSocket對象。伺服器端的DatagramSocket將DatagramPacket發送到網路上,然後被客戶端的DatagramSocket接收。

DatagramSocket有兩種常用的構造函數。一種是無需任何參數的,常用於客戶端;另一種需要指定埠,常用於伺服器端。如下所示:

DatagramSocket() :構造數據報套接字並將其綁定到本地主機上任何可用的埠。

DatagramSocket(int port) :創建數據報套接字並將其綁定到本地主機上的指定埠。

常用方法:

Ø send(DatagramPacket p) :從此套接字發送數據報包。

Ø receive(DatagramPacket p) :從此套接字接收數據報包。

Ø close() :關閉此數據報套接字。

▪ DatagramPacket:數據容器(封包)的作用

此類表示數據報包。 數據報包用來實現封包的功能。

常用方法:

Ø DatagramPacket(byte[] buf, int length) :構造數據報包,用來接收長度為 length 的數據包。

Ø DatagramPacket(byte[] buf, int length, InetAddress address, int port) :構造數據報包,用來將長度為 length 的包發送到指定主機上的指定埠號。

Ø getAddress() :獲取發送或接收方電腦的IP地址,此數據報將要發往該機器或者是從該機器接收到的。

Ø getData() :獲取發送或接收的數據。

Ø setData(byte[] buf) :設置發送的數據。

UDP通信編程基本步驟:

  1. 創建客戶端的DatagramSocket,創建時,定義客戶端的監聽埠。

    1. 創建伺服器端的DatagramSocket,創建時,定義伺服器端的監聽埠。

    2. 在伺服器端定義DatagramPacket對象,封裝待發送的數據包。

    3. 客戶端將數據報包發送出去。

    4. 伺服器端接收數據報包。

UDP:單向通信之客戶端

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
 
public class Client {
    public static void main(String[] args) throws Exception {
        byte[] b = "北京尚學堂".getBytes();
        //必須告訴數據報包要發到哪台電腦的哪個埠,發送的數據以及數據的長度
        DatagramPacket dp = new DatagramPacket(b,b.length,new 
InetSocketAddress("localhost",8999));
        //創建數據報套接字:指定發送信息的埠
        DatagramSocket ds = new DatagramSocket(9000);
        //發送數據報包
        ds.send(dp);
        //關閉資源
        ds.close();
    }
}

UDP:單向通信之伺服器端

import java.net.DatagramPacket;
import java.net.DatagramSocket;
 
public class Server {
    public static void main(String[] args) throws Exception {
        //創建數據報套接字:指定接收信息的埠
        DatagramSocket ds = new DatagramSocket(8999);
        byte[] b = new byte[1024];
        //創建數據報包,指定要接收的數據的緩存位置和長度
        DatagramPacket dp = new DatagramPacket(b, b.length);
        //接收客戶端發送的數據報
        ds.receive(dp); // 阻塞式方法
        //dp.getLength()返回實際收到的數據的位元組數
        String string = new String(dp.getData(), 0, dp.getLength());
        System.out.println(string);
        //關閉資源
        ds.close();
    }
}

通過位元組數組流ByteArrayInputStream、ByteArrayOutputStream與數據流DataInputStream、DataOutputStream聯合使用可以傳遞基本數據類型。

UDP:基本數據類型的傳遞之客戶端

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
 
public class Client {
    public static void main(String[] args) throws Exception {
        long n = 2000L;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);
        dos.writeLong(n);
        //獲取位元組數組流中的位元組數組(我們要發送的數據)
        byte[] b = bos.toByteArray();
        //必須告訴數據報包要發到哪台電腦的哪個埠,發送的數據以及數據的長度
        DatagramPacket dp = new DatagramPacket(b,b.length,new   
                                             InetSocketAddress("localhost",8999));
        //創建數據報套接字:指定發送信息的埠
        DatagramSocket ds = new DatagramSocket(9000);
        //發送數據報包
        ds.send(dp);
        //關閉資源
        dos.close();
        bos.close();
        ds.close();
    }
}

UDP:基本數據類型的傳遞之伺服器端

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
 
public class Server {
    public static void main(String[] args) throws Exception {
        //創建數據報套接字:指定接收信息的埠
        DatagramSocket ds = new DatagramSocket(8999);
        byte[] b = new byte[1024];
        //創建數據報包,指定要接收的數據的緩存位置和長度
        DatagramPacket dp = new DatagramPacket(b, b.length);
        //接收客戶端發送的數據報
        ds.receive(dp); // 阻塞式方法
        //dp.getData():獲取客戶端發送的數據,返回值是一個位元組數組
        ByteArrayInputStream bis = new ByteArrayInputStream(dp.getData());
        DataInputStream dis = new DataInputStream(bis);
        System.out.println(dis.readLong());
        //關閉資源
        dis.close();
        bis.close();
        ds.close();
    }
}

通過位元組數組流ByteArrayInputStream、ByteArrayOutputStream與數據流ObjectInputStream、ObjectOutputStream聯合使用可以傳遞對象。

UDP:對象的傳遞之Person類

import java.io.Serializable;
public class Person implements Serializable{
    private static final long serialVersionUID = 1L;
    int age;
    String name;
    public Person(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person [age=" + age + ", name=" + name + "]";
    }
}

UDP:對象的傳遞之客戶端

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
 
public class Client {
    public static void main(String[] args) throws Exception {
        //創建要發送的對象
        Person person = new Person(18, "高淇");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(person);
        //獲取位元組數組流中的位元組數組(我們要發送的數據)
        byte[] b = bos.toByteArray();
        //必須告訴數據報包要發到哪台電腦的哪個埠,發送的數據以及數據的長度
        DatagramPacket dp = new DatagramPacket(b,b.length,new 
                                             InetSocketAddress("localhost",8999));
        //創建數據報套接字:指定發送信息的埠
        DatagramSocket ds = new DatagramSocket(9000);
        //發送數據報包
        ds.send(dp);
        //關閉資源
        oos.close();
        bos.close();
        ds.close();
    }
}  

UDP:對象的傳遞之伺服器端

import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
 
public class Server {
    public static void main(String[] args) throws Exception {
        //創建數據報套接字:指定接收信息的埠
        DatagramSocket ds = new DatagramSocket(8999);
        byte[] b = new byte[1024];
        //創建數據報包,指定要接收的數據的緩存位置和長度
        DatagramPacket dp = new DatagramPacket(b, b.length);
        //接收客戶端發送的數據報
        ds.receive(dp); // 阻塞式方法
        //dp.getData():獲取客戶端發送的數據,返回值是一個位元組數組
        ByteArrayInputStream bis = new ByteArrayInputStream(dp.getData());
        ObjectInputStream ois = new ObjectInputStream(bis);
        System.out.println(ois.readObject());
        //關閉資源
        ois.close();
        bis.close();
        ds.close();
    }
}

總結

  1. 埠是虛擬的概念,並不是說在主機上真的有若幹個埠。

  2. 在www上,每一信息資源都有統一且唯一的地址,該地址就叫URL(Uniform Resource Locator),它是www的統一資源定位符。

  3. TCP與UDP的區別

    1)TCP是面向連接的,傳輸數據安全,穩定,效率相對較低。

    2)UDP是面向無連接的,傳輸數據不安全,效率較高。

  4. Socket通信是一種基於TCP協議,建立穩定連接的點對點的通信。

  5. 網路編程是由java.net包來提供網路功能。

    1)InetAddress:封裝電腦的IP地址和DNS(沒有埠信息!)。

    2)InetSocketAddress:包含IP和埠,常用於Socket通信。

    3)URL:以使用它的各種方法來對URL對象進行分割、合併等處理。

  6. 基於TCP協議的Socket編程和通信

    1)“請求-響應”模式:

​ --Socket類:發送TCP消息。

​ --ServerSocket類:創建伺服器。

  1. UDP通訊的實現

    1)DatagramSocket:用於發送或接收數據報包。

    2)常用方法:send()、receive()、 close()。

  2. DatagramPacket:數據容器(封包)的作用

    1)常用方法:構造方法、getAddrress(獲取發送或接收方電腦的IP地址)、getData(獲取發送或接收的數據)、setData(設置發送的數據)。\1. 埠是虛擬的概念,並不是說在主機上真的有若幹個埠。

  3. 在www上,每一信息資源都有統一且唯一的地址,該地址就叫URL(Uniform Resource Locator),它是www的統一資源定位符。

  4. TCP與UDP的區別

    1)TCP是面向連接的,傳輸數據安全,穩定,效率相對較低。

    2)UDP是面向無連接的,傳輸數據不安全,效率較高。

  5. Socket通信是一種基於TCP協議,建立穩定連接的點對點的通信。

  6. 網路編程是由java.net包來提供網路功能。

    1)InetAddress:封裝電腦的IP地址和DNS(沒有埠信息!)。

    2)InetSocketAddress:包含IP和埠,常用於Socket通信。

    3)URL:以使用它的各種方法來對URL對象進行分割、合併等處理。

  7. 基於TCP協議的Socket編程和通信

    1)“請求-響應”模式:

​ --Socket類:發送TCP消息。

​ --ServerSocket類:創建伺服器。

  1. UDP通訊的實現

    1)DatagramSocket:用於發送或接收數據報包。

    2)常用方法:send()、receive()、 close()。

  2. DatagramPacket:數據容器(封包)的作用

    1)常用方法:構造方法、getAddrress(獲取發送或接收方電腦的IP地址)、getData(獲取發送或接收的數據)、setData(設置發送的數據)。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • **UML**(Unified Modeling Language) 統一建模語言,又稱標準建模語言。是用來對軟體密集系統進行可視化建模的一種語言。UML的定義包括UML語義和UML表示法兩個元素。UML是在開發階段,說明、可視化、構建和書寫一個面向對象軟體密集系統的製品的開放方法。最佳的應用是工程... ...
  • 開發環境: Windows操作系統開發工具: MyEclipse/Eclipse+Jdk+mysql資料庫運行效果圖: 源碼及原文鏈接:https://javadao.xyz/forum.php?mod=viewthread&tid=36 ...
  • 資料庫設置 在上一章節中學習瞭如何創建Django項目,在Django項目中創建web應用,以及如何在Django主程式的URL中引用web應用中的URL。下麵來瞭解如何在Django中使用資料庫。Django中想要使用資料庫, 首先要瞭解mysite/mysite/settings.py中關於數據 ...
  • 需求 maven依賴 列印sql 配置要點: 1. 驅動配置 application.properties 2. psy配置 aop列印持久層執行時間 使用aop實現; 啟用aop註解: 小結 來個效果截圖: 通過本片文章,你可以學會: 1. 給代碼添加aop切麵,增加日誌或者列印出方法執行總耗時; ...
  • 一、問題剖析 看到這個問題,我想吹水兩句再做推薦。一般發出這個疑問都處在初學編程階段,編程語言都是相通的,只要你領悟了一門語言的“任督二脈”,以後你學哪一門語言都會輕易上手。學語言嘛,當你工作一兩年了,你還真會覺得像當初老師說的那樣,語言只是工具罷了。工作期間,可能要你接觸到其它語言,而且要你能快速 ...
  • 生產者是如何生產消息 如何創建生產者 發送消息到Kafka 生產者配置 分區 ...
  • GUI編程 組建 視窗 彈窗 面板 文本框 列表框 按鈕 圖片 監聽事件 滑鼠 鍵盤事件 破解工具 1、簡介 GUI的核心技術:Swing AWT 為什麼不流行? 界面不美觀。 需要jre環境。(沒必要為一個5M的小游戲下載幾百M的jre) 但是學了java的GUI編程,有助於瞭解MVC架構,瞭解監 ...
  • 字元和字元串是最常用的信息 1:char表示字元 字元常量-兩個單引號中間的字元表示字元常量'A' 2:字元串和String 字元串常量-雙引號中間的字元序列"Java" 字元串常量是String型的實例的引用。String s="ABC"與String s=new String("ABC"); 3 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...