1. 程式人生 > 實用技巧 >Caused by: java.lang.IllegalStateException: Could not resolve element type of Iterable type @。。。。。web.bind.annotation.RequestParam java.util.List<?>. Not declared?

Caused by: java.lang.IllegalStateException: Could not resolve element type of Iterable type @。。。。。web.bind.annotation.RequestParam java.util.List<?>. Not declared?

網路程式設計

網路基礎

計算機網路

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

網路程式設計的目的:

直接或間接地通過網路協議與其它計算機實現資料交換,進行通訊。

網路程式設計中有兩個主要的問題:

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

網路通訊要素

想要實現網路中的主機互相通訊,需要知道通訊雙方的地址(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
  • 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):測試是否可以達到該地址

埠號

埠號的認識

埠號標識正在計算機上執行的程序(程式),可以用埠號區別不同的程序

  • 不同的程序有不同的埠號
  • 埠號被規定為一個 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的查詢名