天天看點

【Java基礎】網絡程式設計:Socket、ServerSocket、Echo程式

《第一行代碼:Java》第12章、Java網絡程式設計 讀書筆記。有關計算機網絡的基礎知識建議去看計算機網絡相關的文章,本章中主要以“C/S”開發中的TCP程式實作為主。

文章目錄

    • Java網絡程式設計
      • 12.1 網絡程式設計
      • 12.2開發第一個網絡程式
      • 12.3 網絡開發的經典模型——Echo程式
      • 本章小結:

Java網絡程式設計

12.1 網絡程式設計

網絡程式設計的核心意義在于不同的電腦主機之間的資料互動,但是在Java中這一概念會進一步簡化。在Java中是以JVM程序劃分網絡的,即不同的JVM代表不同的主機。Java中同一台主機的不同JVM之間的資料通路也屬于遠端通路。

【Java基礎】網絡程式設計:Socket、ServerSocket、Echo程式
  • 網絡程式設計的是指意義在于資料的互動,而在互動的過程中一定會将互動的雙方分為伺服器端和用戶端,而這兩端的開發會存在以下兩種模式:
    • 模式一:C/S結構(Client/Server),此類模式的開發一般要編寫兩套程式,一套是用戶端代碼,另外一套屬于伺服器端代碼。由于需要有編寫程式,是以對于開發以及維護的成本較高。但是由于其使用的是自己的連接配接端口與交換協定,是以安全性比較高。而C/S結構程式的開發分為兩種:
      • TCP(傳輸控制協定,一種面向連接配接的通信協定,可靠的傳輸)
      • UDP(資料報協定,一種面向無連接配接的通訊協定)。
    • 形式二:B/S結構(Browser /Server ),不再單獨開發用戶端代碼,隻開發一套伺服器端程式,用戶端将利用浏覽器進行通路,這種模式隻需要開發一套程式,但是安全性不高,因為使用的是公共的HTTP 協定以及公共的80端口。
    本章主要以“C/S”開發中的TCP程式實作為主。

12.2開發第一個網絡程式

  • 在java.net包中,提供了網絡程式設計相關的開發工具類,在此包中有以下兩個主要的核心操作類。
    • ServerSocket類:是一個封裝支援TCP協定的操作類,主要工作是在伺服器端,用于接收用戶端請求。
    • Socket類:也是一個封裝了TCP協定的操作類,每個Socket對象都是一個用戶端
  • java.net.ServerSocket類常用方法:
    public ServerSocket(int port) throws IOException	// 構造,開辟一個指定的端口監聽,一般使用5000以上的端口
    public Socket accept() throws IOException			// 普通,伺服器端接收用戶端請求,通過Socket傳回
    public void close() throws IOException				// 普通,傳回伺服器端
               
  • java.net.Socket類常用方法:
    public Socket(String host, int port) throws UnknowHostException, IOException	// 構造,指定要連接配接的主機(IP位址)和端口
    public OutputStream getOutputStream() throws IOException	// 普通,取得指定用戶端的輸出對象,使用PrintStream操作
    public InputStream getInputStream() throws IOException		// 普通,從指定的用戶端讀取資料,使用Scanner操作
               
  • 在用戶端,程式可以通過Socket類的getInputStream()方法,取得伺服器的輸出資訊,在伺服器端可以通過getOutputStream()方法取得用戶端的輸出資訊,具體如下圖所示:
    【Java基礎】網絡程式設計:Socket、ServerSocket、Echo程式
  • 定義伺服器端——主要使用ServerSocket類:
    package com.yootk.demo;
    import java.io.PrintStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    public class HelloServer { 
        public static void main(String[] args) throws Exception {
            ServerSocket server = new ServerSocket(9999) ;	// 所有的伺服器必須有端口
            System.out.println("等待用戶端連接配接.....");			// 提示資訊
            Socket client = server.accept() ;				// 等待用戶端連接配接
            // OutputStream并不友善進行内容的輸出,是以利用列印流完成輸出
            PrintStream out = new PrintStream(client.getOutputStream()) ;
            out.println("Hello World !");					// 輸出資料
            out.println("你好世界 !");						 // 輸出資料
            out.close(); 
            client.close();
            server.close();
        }
    }
    // 程式執行結果:	等待用戶端連接配接.....
               
    本程式在本機的9999端口上設定了一個伺服器的監聽操作(accept()方法表示打開伺服器監聽,并且會一直等待,直到有用戶端連接配接後才會執行下一步),當有用戶端通過TCP連接配接方式連接配接到伺服器端後,伺服器端将會利用PrintSream輸出資料,當資料輸出完畢後,該伺服器将會關閉,也就是說本次定義的伺服器隻能處理一次用戶端的請求。
  • 編寫用戶端——主要使用Socket類:
    package com.yootk.demo;
    import java.net.Socket;
    import java.util.Scanner;
    public class HelloClient {
        public static void main(String[] args) throws Exception {
            Socket client = new Socket("localhost",9999) ;			// 連接配接伺服器端
            // 取得用戶端的輸入資料流對象,表示接收伺服器端的輸出資訊
            Scanner scan = new Scanner(client.getInputStream()) ;	// 接收伺服器端回應資料
            scan.useDelimiter("\n") ;								// 設定分隔符
            while (scan.hasNext()) {								// 是否有資料
               System.out.println("【回應資料】" + scan.next());		// 取出資料
            }
            scan.close();
            client.close();
        }
    }
    /*
    程式執行結果:
        Hello World !
        你好世界 !
    */
               
    在TCP程式中,每一個Socket對象都表示一個用戶端的資訊,是以用戶端程式要連接配接也必須依靠Socket對象操作。在執行個體化Socket對象時必須設定連接配接的主機名稱(本機為“localhost“,或者填寫IP位址)以及連接配接端口号,當連接配接成功後就可以利用Scanner進行輸入流資料的讀取,這樣就可以接收伺服器端的回應資訊了。

12.3 網絡開發的經典模型——Echo程式

在網絡程式設計中 Echo是一個經典的程式開發模型,本程式的意義在于:用戶端随意輸入資訊并且将資訊發送給伺服器端,伺服器端接收後前面加上一個”ECHO:”的字首标記後将資料返還給用戶端。在本程式中伺服器端既要接收用戶端發送來的資料,又要向用戶端輸出資料,同時考慮到需要進行多次資料交換,是以每次連接配接後不應該立刻關閉伺服器,而當使用者輸入了一些特定字元串(例如:“byebye”)後才表示可以結束本次的Echo操作。

  • 伺服器端實作:
    package com.yootk.demo;
    import java.io.PrintStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;
    public class EchoServer {
        public static void main(String[] args) throws Exception {
            ServerSocket server = new ServerSocket(9999) ;	// 定義連接配接端口
            Socket client = server.accept() ;				// 等待用戶端連接配接
            // 得到用戶端輸入資料以及向用戶端輸出資料的對象,利用掃描流接收,列印流輸出
            Scanner scan = new Scanner(client.getInputStream()) ;
            PrintStream out = new PrintStream(client.getOutputStream()) ;
            boolean flag = true ;							// 設定循環标記
            while(flag) {
                if (scan.hasNext()) {						// 是否有内容輸入
                   String str = scan.next().trim() ;		// 得到用戶端發送的内容,并删除空格
                   if (str.equalsIgnoreCase("byebye")) {	// 程式結束标記
                      out.println("拜拜,下次再會!");		   // 輸出結束資訊
                      flag = false ;						// 退出循環
                   } else {									// 回應輸入資訊
                      out.println("ECHO : " + str);			// 加“ECHO :”字首傳回
                   }
                }
            }
            scan.close();
            out.close();
            client.close(); 
            server.close();
        }
    }
               
  • 用戶端實作:
    package com.yootk.demo;
    import java.io.PrintStream;
    import java.net.Socket;
    import java.util.Scanner;
    public class EchoClient {
        public static void main(String[] args) throws Exception {
            Socket client = new Socket("localhost", 9999);		// 伺服器位址與端口
            Scanner input = new Scanner(System.in); 			// 鍵盤輸入資料
            // 利用Scanner包裝用戶端輸入資料(伺服器端輸出),PrintStream包裝用戶端輸出資料;
            Scanner scan = new Scanner(client.getInputStream());
            PrintStream out = new PrintStream(client.getOutputStream());
            input.useDelimiter("\n");						// 設定鍵盤輸入分隔符
            scan.useDelimiter("\n");						// 設定回應資料分隔符
            boolean flag = true;							// 循環标志
            while (flag) {
                System.out.print("請輸入要發送資料:");
                if (input.hasNext()) {						// 鍵盤是否輸入資料
                   String str = input.next().trim();		// 取得鍵盤輸入資料
                   out.println(str); 						// 發送資料到伺服器端
                   if (str.equalsIgnoreCase("byebye")) {	// 結束标記
                      flag = false; 						// 結束循環
                   }
                   if (scan.hasNext()) {					// 伺服器端有回應
                      System.out.println(scan.next()); 		// 輸出回應資料
                   }
                }
            }
            input.close();
            scan.close();
            out.close();
            client.close();
        }
    }
    /*
    程式執行結果:
        請輸入您需要發送的資料:aewcdsacsa5545
        Echo:aewcdsacsa5545
        請輸入您需要發送的資料:54d5f4ad
        Echo:54d5f4ad
        請輸入您需要發送的資料:sdafsa4
        Echo:sdafsa4
        請輸入您需要發送的資料:byebye
        拜拜,下次再見!
    */
               
  • 上面的程式實作了一個最簡單的伺服器端與用戶端通訊,但是該程式隻能連接配接一個用戶端,不能連接配接其他用戶端,因為所有的操作都是在主線程上進行的開發,也就是說該程式屬于單線程的網絡應用。而在實際的開發中一個伺服器需要同時處理多個用戶端的請求操作,在這樣的情況下就可以利用多線程來進行操作,把每一個連接配接到伺服器端的客戶都作為一個獨立的線程對象保留,如圖12-3所示:
    【Java基礎】網絡程式設計:Socket、ServerSocket、Echo程式
  • 多線程伺服器端實作:
    package com.yootk.demo;
    import java.io.PrintStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;
    class EchoThread implements Runnable {					// 建立線程類
        private Socket client;								// 每個線程處理一個用戶端
        public EchoThread(Socket client) {					// 建立線程對象時傳遞Socket
            this.client = client;
        }
        @Override
        public void run() {	
            try {	// 每個線程對象取得各自Socket的輸入流與輸出流
                Scanner scan = new Scanner(client.getInputStream());
                PrintStream out = new PrintStream(client.getOutputStream());
                boolean flag = true; 							// 控制多次接收操作
                while (flag) {
                    if (scan.hasNext()) {						// 是否有内容
                       String str = scan.next().trim(); 		// 得到用戶端發送的内容
                       if (str.equalsIgnoreCase("byebye")) { 	// 程式結束
                          out.println("拜拜,下次再會!");
                          flag = false; 						// 退出循環
                       } else { 								// 應該回應輸入資訊
                          out.println("ECHO : " + str);			// 回應資訊
                       }
                    }
                }
                scan.close();
                out.close();
                client.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public class EchoServer {
        public static void main(String[] args) throws Exception {
            ServerSocket server = new ServerSocket(9999);	// 在9999端口上監聽
            boolean flag = true;							// 循環标記
            while (flag) {									// 接收多個用戶端請求
                Socket client = server.accept(); 			// 用戶端連接配接
                new Thread(new EchoThread(client)).start();	// 建立并啟動新線程
            }
            server.close();
        }
    }
               

本章小結:

  • ServerSocket主要用于TCP協定的伺服器程式開發上,使用accept()方法等待用戶端連接配接,每個連接配接的用戶端都是用一個SOcket表示。
  • 伺服器端加入多線程機制後,就可以同時為多個使用者提供服務。