首頁 > 軟體

java實現TCP socket和UDP socket的範例

2022-02-11 10:00:56

概述

        我們在網路程式設計時,通常是讓我們原生的應用程式和遠端的應用程式進行通訊,也就是分散式的程序之間的通訊,比如我寫的程式A和小明的程式B進行通訊,我的程式執行時在本機就是一個程序,是有pid號的,小明的也是。那這兩個程式是怎麼通訊的呢?

        這就要理解網路分層的概念了,網路層實現的是主機到主機之間的通訊,網路層的實現是ip協定,通過各自的ip地址就能實現遠端資料傳輸,而網路層只是保證了主機A的資料能夠到達主機B,並不能夠識別和傳送到對應的程序,而傳輸層實現的是程序到程序的通訊,對網路層的功能進行了加強,能夠把資料交付給對應的程序。

        有了傳輸層的功能,我們使用者才能進一步的去實現自己的應用層協定,實現應用。當我們需要遠端通訊時只需要通過下層的傳輸層傳輸資料即可

        但是想象一下,每次我在應用層傳送資料時至少都是需要把 要傳送的資訊、本機ip、本地埠、目的ip和目的埠這五個資料通過層間介面交給傳輸層(對於TCP協定來說的,UDP無連線不需要應答所以不需要本機ip和埠),如下圖

         但是每一次應用層向傳輸層傳送資料時都需要傳送這些資料是不是很多餘?TCP一旦雙方建立起連線了,那麼除了資料部分,其他的都是相同的,沒有必要每次傳送資料都傳送一遍,為了減少層間介面的傳輸量,就出現了socket,作業系統底層維護一個列表,用於存放多個socket,即多個對談關係。

        socket通訊端是傳輸層提供給應用層的一個API,底層實現就是一個整數,是傳輸層和應用層的一個約定,該整數就像是開啟一個檔案並得到檔案控制程式碼一樣,對這個控制程式碼進行的操作就是對該檔案進行操作,方便管理。

有了socket之後應用層資料的傳輸就變為了這樣

傳輸層概述

        先來說說網路層的ip,ip協定在網路上傳送封包是不可靠的,有可能造成丟失,亂序等問題,如傳送封包到對應的路由器,路由器有接收緩衝區,如果發現同一時刻來的封包太多了,緩衝區放不下,它是可以把裝不下的封包扔掉的。這就是不可靠的傳輸。

傳輸層提供了TCP和UDP兩種服務:

  • TCP:對ip協定進行了增強,通過一些方式來達到可靠的資料傳輸
  • UDP:不可靠的資料傳輸,它只對ip增加了程序到程序之間的通訊,其他的就沒了,原原本本

TCP通訊端程式設計

        TCP socket反應的是應用程序A和應用程序B對談關係的一個代表,A對對應的socket傳送資料,就是A對B傳送資料;A對對應的socket接收資料就是對B接收資料。

大致過程

1.伺服器程序必須執行,建立一個歡迎socket,該socket和原生的埠進行捆綁,在歡迎socket上阻塞式的等待接收使用者端的連線

2.使用者端建立原生的通訊端,隱式捆綁到原生的埠,再指定伺服器的ip和埠進行連線。

3.伺服器接受來自使用者端的請求 ,解除阻塞式等待,返回一個 新的socket(與歡迎socket不 一樣),與使用者端通訊

4.連線API呼叫有效時,使用者端與伺服器建立了TCP連線,即可以通訊了

程式碼如下:

@Test
public void server() throws IOException {
    //1.建立歡迎socket,繫結一個監聽的埠號
    ServerSocket welcomeSocket = new ServerSocket(8080);
    //2.阻塞的等待使用者端的連線請求,連線請求到來時建立一個新的socket,與使用者端繫結
    Socket socket = welcomeSocket.accept();
    //6.從該socket接收資料
    InputStream is = socket.getInputStream();
    int len = 0;
    while ((len = is.read()) != -1) {
        System.out.print((char) len);
    }
    socket.close();
}
 
@Test
public void client() throws IOException {
    //3.建立一個使用者端這邊的socket
    Socket socket = new Socket();
    //4.阻塞的請求連線到指定ip和埠號的伺服器程序
    socket.connect(new InetSocketAddress("localhost", 8080));
    //5.傳送資料到該socket
    OutputStream os = socket.getOutputStream();
    byte[] bytes = new byte[1024];
    os.write("hello".getBytes());
    socket.shutdownOutput();
}

詳細過程

首先來看java中socket的結構體(類),其他語言都大同小異。

public abstract class SocketImpl implements SocketOptions {    
    /**
     * The IP address of the remote end of this socket.遠端主機的ip地址
     */
    protected InetAddress address;
    /**
     * The port number on the remote host to which this socket is connected.遠端主機的埠
     */
    protected int port;
    /**
     * The local port number to which this socket is connected.本地埠
     */
    protected int localport;
    /**
     *實際還有一個本機的ip地址,被省略掉了
     */
}

InetAddress類就不介紹了,裡面就是封裝了ip地址等資訊。

ServerSocket和Socket類是一個東西,只時名字不同而已。具體可以看原始碼。

可以得出來socket其實大致就是這麼一個6元組(這裡省略了socket的狀態),當我們應用程序建立socket時,作業系統給該socket一個唯一的整數標識,且肯定是要儲存是哪個程序建立的socket,所以pid也應該對應起來,方便日後能給找到對應的程序。

 我們舉例如下圖的一個通訊過程來具體的說明socket的通訊過程:

1.首先伺服器程序先建立一個歡迎socket,用於監聽連線請求,並且繫結埠號為8080,阻塞監聽使用者端的連線請求,如下圖

 2.這時候使用者端也新建一個socket(該socket以後會當做和伺服器的通訊socket),這個socket不用像伺服器那樣繫結一個固定的埠號用於監聽,但是作業系統會給該socket繫結一個隨機的埠,這裡假設是4567。如下圖,此時使用者端的1號socket還是一個無效的狀態,因為還沒有連線

3.使用者端用剛剛建立的socket和遠端伺服器進行連線connect,指明伺服器的地址和對應的埠號,此時socket的狀態也就補齊了,隨後使用者端進入阻塞模式進行TCP的三次握手,請求和伺服器建立連線

 4.伺服器和使用者端TCP連線建立好後,解除阻塞,並且返回一個新的socket(因為不能佔用welcome socket),新的socket就是伺服器和使用者端的一個連線狀態,該socket變為一個有效狀態,此時伺服器繼續進入阻塞狀態等待此socket的資料。

5. 連線建立好後,使用者端也解除阻塞,它的socket1也變為有效狀態。然後使用者端把需要傳送的資料和對應的socket1(輸出流裡面封裝了socket)交給下層傳輸層,此時的傳輸層得到了它相應的資訊,根據socket就可以從表中查到需要傳送的目的ip和埠,繼續交給下層直到傳送到伺服器。

6.伺服器的tcp層收到封包後,檢視源ip、目的ip、源埠、目的埠,一一對照自己的socket表,發現2號socket真好對應,且得知2號socket是pid為100的應用程序,所以tcp把資料傳送給該程序,伺服器的java程序解除阻塞,read使用者端傳送來的資料。

7.最後如果伺服器和使用者端某一方沒有資料發了,不想建立連線了,就呼叫close方法,進行TCP的四次揮手,解除連線,使兩邊對應的socket都變得無效。

UDP通訊端程式設計

        UDP是沒有建立連線這一過程的,也不需要維持對談關係,每個報文都是獨立傳輸的。因此UDP只能使用一個整數來標誌當前應用程序,不能夠固定住對方的ip和埠號,因為UDP通訊前不建立連線,可能現在發的這個ip和埠是一臺主機,而下次用同樣的ip和埠發的就是另一臺主機了。所以每次傳送的時候都需要指定傳送的目的ip和埠。

UDP的socket大致如下

 java中UDPsocket結構如下

public abstract class DatagramSocketImpl implements SocketOptions {
 
    /**
     * The local port number.
     */
    protected int localPort;
    /**
     * 省略本機ip
     */
}

UDP簡單通訊端程式設計如下:

@Test
public void server() throws IOException {
    //1.建立udp socket,並繫結到本機ip和8080埠號
    DatagramSocket socket = new DatagramSocket(8080);
    byte[] buff = new byte[100];
    //存放封包的容器
    DatagramPacket packet = new DatagramPacket(buff, 0, buff.length);
    //接收封包
    socket.receive(packet);
    System.out.println(new String(packet.getData(), 0, packet.getLength()));
    socket.close();
}
 
@Test
public void client() throws IOException {
    DatagramSocket socket = new DatagramSocket();
    byte[] data = "hello".getBytes();
    //指明傳送端的ip和埠和資料部分
    DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("127.1.1.1"), 8080);
    //使用該socket傳送封包
    socket.send(packet);
}

詳細步驟如下:

1.伺服器建立一個udp的socket,繫結埠8080用於監聽封包,通過呼叫receive方法阻塞的監聽。

  2.使用者端建立一個自己的socket,該socket的埠假設作業系統分配的是4567

 3.使用者端傳送封包、目標ip和目標埠給下層的傳輸層,傳輸層就能夠得到源ip、源埠、目的ip和目的埠,然後一步一步的打包交給下層,傳送到伺服器主機,伺服器主機通過封包的目的ip和目的埠,對比發現socket對應,然後把資料傳送給對應的pid號為100的應用程序。

4.伺服器解除阻塞,收取資料。

5.最後關閉連線,刪除對應的socket

注意的是UDP是沒有welcome socket的

補充

再補充一個小知識點,那就是埠號和程序的聯絡

程序pid是否可供計算機之間使用呢?

        應用層代表的就是我們的應用程序,既然程序代表著應用層,那為什麼程序pid不能作為應用層的標識來進行計算機之間傳輸呢?而是使用額外的埠號呢?

(1)首先: 單個計算機中的程序使用pid來標誌的,但是在網際網路環境下使用的計算機作業系統種類很多,而不同的作業系統又使用不同格式的程序識別符號,為了使執行不同作業系統的計算機的應用程序能夠互相通訊,就必須使用統一的方法對TCP/IP體系的程序進行標識;

(2)其次:一個機器上執行的程序不能成為網際網路上通訊的最後終點,因為程序的建立和復原都是動態的,通訊的一方几乎無法識別對方機器的程序是哪一個;

例如:要和網際網路上某個郵件伺服器聯絡,幾乎無法得知其伺服器郵件程序的程序識別符號,因為程序識別符號是隨機分配的;所以,我們並不一定要知道這個伺服器服務是由目的主機那個程序實現的;

所以,不能使用程序識別符號來做計算機之間的程序通訊標識;

如何使用埠號進行通訊?
   
    兩個計算機中程序要互相通訊,除了必須指定對方的IP地址,還需要知道對方的埠號;

例如:我們寄信的過程說明,當我們要給某人寫信時,除了通訊地址還要有收件人的名字,這裡的通訊地址就是IP地址,但是收件人的名字卻不是程序識別符號,因為有可能這個人用的是法文、德文、英文名字,快遞員無法識別,因此採用 “菜鳥驛站" 的模式,為每個地址配備多個快遞箱(埠號),快遞員只是將包裹放置具體的快遞箱(埠號),收件人通過監聽某個快遞箱是否有快遞(TCP或者UDP),來進行資料接收,最終拿到需要的包裹(資料);

埠號如何分配?

        (1)伺服器使用的埠號:

        一類為熟知埠號或系統埠號(0~1023),將一些重要的應用程式進行登記,所以將一些埠號固定的分配給它們,以便於讓所以的使用者的瞭解,與之建立聯絡; 

        另一類為登記埠號(1024~49151),為那些不知名的應用程式使用;

        (2)客戶機使用的埠號:

        也稱為短暫埠號,由於這類埠僅僅在客戶程序進行時才動態選擇,留給客戶程序短暫使用,當通訊結束後,剛才使用過的使用者埠號不復存在,可以繼續供其他客戶程序使用;

到此這篇關於java實現TCP socket和UDP socket的文章就介紹到這了,更多相關java實現TCP socket和UDP socket內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com