網路編程

網路編程

網路基礎

電腦網路

把分布在不同地理區域的電腦與專門的外部設備用通訊線路互連成一個規模大、功能強的網路系統,從而使眾多的電腦可以方便地互相傳遞資訊、共享硬體、軟體、數據資訊等資源。

網路編程的目的:

直接或間接地通過網路協議與其它電腦實現數據交換,進行通訊。

網路編程中有兩個主要的問題:

  • 如何準確地定位網路上一台或多台主機;定位主機上的特定的應用
  • 找到主機後如何可靠高效地進行數據傳輸

網路通訊要素

想要實現網路中的主機互相通訊,需要知道通訊雙方的地址(IP、埠號)和一定的規則(網路通訊協議)

IP和埠號

IP

IP的認識
  • 唯一的標識Internet上的電腦(通訊實體)

  • 在Java中,使用InetAddress類代表IP

  • 本地迴環地址(hostAddress)

    主機名(hostName):localhost

  • IP地址分類

    • 方式一:IPV4 和 IPV6
      • IPV4:4個位元組組成,4個範圍是0-255。2011年初已經用盡。以點分十進位表示,如192.168.0.1
      • IPV6:128位(16個位元組),寫成8個無符號整數,每個整數用4個十六進位位表示,數之間用冒號(:)分開,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
    • 方式二:公網地址(萬維網使用)和私有地址(區域網使用)。
      • 192.168.開頭的就是私有址址,範圍即為192.168.0.0--192.168.255.255,專門為組織機構內部使用
  • 特點:不易記憶

InetAddress類的使用
  • 兩種方式表示地址:

    • 域名(hostName):www.cnblogs.com
      • 本地迴環地址(hostAddress)
      • 主機名(hostName):localhost
    • IP 地址(hostAddress):192.168.56.2
      o1cOT.png
  • InetAddress類沒有提供公共的構造器,而是提供了幾個靜態方法來獲取InetAddress 實例

    • 獲取本地的IP地址:

      public static InetAddress getLocalHost()

    • 通用的方法(host可以是域名或是IP地址):

      public static InetAddress getByName(String host)

  • 幾個常用的方法(前兩個常用)

    • public String getHostAddress() :返回 IP 地址字元串(以文本表現形式)。
    • public String getHostName() :獲取此 IP 地址的主機名
    • public boolean isReachable(int timeout):測試是否可以達到該地址

o1eAm.png

埠號

埠號的認識

埠號標識正在電腦上運行的進程(程式),可以用埠號區別不同的進程

  • 不同的進程有不同的埠號
  • 埠號被規定為一個 16 位的整數 0~65535。
  • 埠分類:
    • 公認埠:0~1023。被預先定義的服務通訊佔用(如:HTTP佔用埠80,FTP佔用埠21,Telnet佔用埠23)
    • 註冊埠:1024~49151。分配給用戶進程或應用程式。(如:Tomcat佔用埠8080,MySQL佔用埠3306,Oracle佔用埠1521等)
    • 動態/ 私有埠:49152~65535。

埠號和IP地址組合在一起得出一個網路套接字:Socket

網路通訊協議

電腦網路中實現通訊必須有一些約定,即通訊協議,對速率、傳輸程式碼、程式碼結構、傳輸控制步驟、出錯控制等制定標準。

因為網路協議過於複雜,在制定協議時,把複雜成份分解成一些簡單的成份,再將它們複合起來。

最常用的複合方式是層次方式,即同層間可以通訊、上一層可以調用下一層,而與再下一層不發生關係。各層互不影響,利於系統的開發和擴展。

TCP/IP協議簇

傳輸層協議中兩個重要的協議:

  • 傳輸控制協議TCP(Transmission Control Protocol)
  • 用戶數據報協議UDP(User Datagram Protocol)
TCP協議

類似於生活中打電話

  • 使用TCP協議前,須先建立TCP連接,形成傳輸數據通道
  • 傳輸前,採用「三次握手」方式,點對點通訊,是可靠的
  • TCP協議進行通訊的兩個應用進程:客戶端、服務端。
  • 在連接中可進行大數據量的傳輸
  • 傳輸完畢,需釋放已建立的連接,效率低
UDP協議

類似於發簡訊

  • 將數據、源、目的封裝成數據包,不需要建立連接
  • 每個數據報的大小限制在64K內
  • 發送不管對方是否準備好,接收方收到也不確認,故是不可靠的
  • 可以廣播發送
  • 發送數據結束時無需釋放資源,開銷小,速度快

Socket

概念

  1. 網路上具有唯一標識的IP地址和埠號組合在一起構成唯一能識別的標識符套接字Socket
  2. 通訊的兩端都要有Socket,是兩台機器間通訊的端點。
  3. 網路通訊其實就是Socket間的通訊。
  4. Socket允許程式把網路連接當成一個流,數據在兩個Socket間通過IO傳輸。
  5. 一般主動發起通訊的應用程式屬客戶端,等待通訊請求的為服務端。

Socket常用的方法

Socket類的常用構造器

  1. public Socket(InetAddress address,int port)

    創建一個流套接字並將其連接到指定IP地址的指定埠號。

  2. public Socket(String host,int port)

    創建一個流套接字並將其連接到指定主機上的指定埠號。

Socket類的常用方法:

  1. public InputStream getInputStream()

    返回此套接字的輸入流。可以用於接收網路消息

  2. public OutputStream getOutputStream()

    返回此套接字的輸出流。可以用於發送網路消息

  3. public InetAddress getInetAddress()

    此套接字連接到的遠程IP地址;如果套接字是未連接的,則返回null。

  4. public InetAddress getLocalAddress()

    獲取套接字綁定的本地地址,即本端的IP地址

  5. public int getPort()

    此套接字連接到的遠程埠號;如果尚未連接套接字,則返回0。

  6. public int getLocalPort()

    返回此套接字綁定到的本地埠。如果尚未綁定套接字,則返回-1,即本端的埠號。

  7. public void close()

    關閉此套接字。套接字被關閉後,便不可在以後的網路連接中使用(即無法重新連接或重新綁定)。需要創建新的套接字對象。關閉此套接字也將會關閉該套接字的 InputStream 和OutputStream。

  8. public void shutdownInput()

    如果在套接字上調用shutdownInput()後從套接字輸入流讀取內容,則流將返回 EOF(文件結束符)。即不能在從此套接字的輸入流中接收任何數據。

  9. public void shutdownOutput()

    禁用此套接字的輸出流。對於TCP套接字,任何以前寫入的數據都將被發送,並且後跟TCP的正常連接終止序列。如果在套接字上調用shutdownOutput()後寫入套接字輸出流,則該流將拋出IOException。即不能通過此套接字的輸出流發送任何數據。

基於Socket的TCP編程

Java語言的基於套接字編程分為服務端編程和客戶端編程

客戶端Socket的基本的步驟

  1. 創建Socket對象,指明伺服器端的ip和埠號
    • 創建的同時會自動向伺服器方發起連接
  2. 獲取一個輸出流,用於輸出數據
  3. 寫出數據
  4. 資源關閉
public void client(){
//客戶端
    Socket socket = null;
    OutputStream os = null;
    try {
        //1. 創建Socket對象,指明伺服器端的ip和埠號
        InetAddress inet = InetAddress.getByName("127.0.0.1");
        socket = new Socket(inet,8899);
        //2. 獲取一個輸出流,用於輸出數據
        os = socket.getOutputStream();
        //3. 寫出數據
        os.write("您好,我是客戶端".getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4. 資源的關閉
        try {
            if(os != null)
                os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            if(socket != null)
                socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

服務端Socket的基本步驟

  1. 創建伺服器端的ServerSocket,指明自己的埠號
    • 伺服器必須事先建立一個等待客戶請求建立套接字
      的 連接的ServerSocket對象
  2. 調用accept()方法,表示接收來自客戶端的Socket
  3. 獲取一個輸入流
  4. 讀取輸入流中的數據
  5. 資源的關閉
public void server(){
    //服務端
    ServerSocket serverSocket = null;
    Socket socket = null;
    InputStream inputStream = null;
    ByteArrayOutputStream baos = null;
    try {
        //1. 創建伺服器端的ServerSocket,指明自己的埠號
        serverSocket = new ServerSocket(8899);
        //2. 調用accept()方法,表示接收來自客戶端的Socket
        socket = serverSocket.accept();
        //3. 獲取一個輸入流
        inputStream = socket.getInputStream();
        //4. 讀取輸入流中的數據
        baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[5];
        int len;
        while((len = inputStream.read(buffer)) != -1){
            baos.write(buffer,0,len);
        }

        System.out.println(baos.toString());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //5. 資源的關閉
        try {
            if(baos != null)
                baos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            if(inputStream != null)
                inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            if(socket != null)
                socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            if(serverSocket != null)
                serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意

  1. 客戶端發送文件給服務端,服務端將文件保存在本地

    • 客戶端發送文件時需要先利用節點流將文件讀取,在利用Socket寫出
      FileInputStream fis = new FileInputStream(new File("殷志源.png"));
      byte[] buffer = new byte[1024];
      int len;
      while((len = fis.read(buffer)) != -1){
          ops.write(buffer,0,len);
      }
      
    • 服務端接受文件時,因為要保存在本地,要利用節點流
      FileOutputStream fos = new FileOutputStream(new File("殷志源(1).png"));
      byte[] buffer = new byte[1024];
      int len;
      while((len = ips.read(buffer)) != -1){
          fos.write(buffer,0,len);
      }
      
  2. 從客戶端發送文件給服務端,服務端保存到本地。並返回「發送成功」給客戶端。

    • 服務端

      //伺服器端給客戶端回饋
      OutputStream os = socket.getOutputStream();
      os.write("發送成功".getBytes());
      
    • 客戶端

      //關閉數據的輸出
      socket.shutdownOutput();
      
      //接收來自伺服器端的數據並顯示在控制台上
      InputStream is = socket.getInputStream();
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      byte[] buffer1 = new byte[1024];
      int len1;
      while((len = is.read(buffer1)) != -1){
          baos.write(buffer1,0, len);
      }
      
      System.out.println(baos.toString());
      

      read()方法是阻塞式方法,程式一直停在read()方法這裡,等待數據。沒有數據就不繼續往下執行,直到得到數據。所以需要調用socket的shutdownOutput()方法,關閉數據的輸出

      阻塞式方法:在程式調用改方法時,必須等待輸入數據可用或者檢測到輸入結束或者拋出異常,否則程式會一直停留在該語句上,不會執行下面的語句。

UDP網路編程

  • 類DatagramSocket和DatagramPacket實現了基於 UDP協議網路程式。其中可以理解為DatagramSocket為快遞員,DatagramPacket為包裹,即為需要傳輸的數據
  • UDP數據報通過數據報套接字DatagramSocket發送和接收
  • DatagramPacket對象封裝了UDP數據報,在數據報中包含了發送端的IP地址和埠號以及接收端的IP地址和埠號。
  • UDP協議中每個數據報都給出了完整的地址資訊,因此無須建立發送方和接收方的連接

程式碼舉例

其中沒有用try-catch來處理異常,選擇拋出異常,但是實際使用時要用try-catch來處理異常,此處舉例為簡寫

@Test
public void send() throws IOException {
    //發送端
    DatagramSocket socket = new DatagramSocket();

    String str = "UDP方式發送文件";
    byte[] data = str.getBytes();
    InetAddress inetAddress = InetAddress.getLocalHost();
    DatagramPacket packet = new DatagramPacket(data,0,data.length,inetAddress,9090);

    socket.send(packet);

    socket.close();

}

@Test
public void receiver() throws IOException {
    //接收端

    DatagramSocket socket = new DatagramSocket(9090);

    byte[] buffer = new byte[100];
    DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);

    socket.receive(packet);

    System.out.println(new String(packet.getData(),0 ,packet.getLength()));

    socket.close();

}

URL編程

URL

  1. URL(Uniform Resource Locator):統一資源定位符,它表示Internet上某一資源的地址。
  2. URL的基本結構由5部分組成

    < 傳輸協議>://< 主機名>:< 埠號>/< 文件名># 片段名? 參數列表

常用方法

  • public String getProtocol( ) 獲取該URL的協議名
  • public String getHost( ) 獲取該URL的主機名
  • public String getPort( ) 獲取該URL的埠號
  • public String getFile( ) 獲取該URL的文件名
  • public String getQuery( ) 獲取該URL的查詢名
Tags: