1. 程式人生 > >Java高級篇——網絡通信

Java高級篇——網絡通信

編程語言 Java

JAdam

Java高級篇(二)——網絡通信

  網絡編程是每個開發人員工具相中的核心部分,我們在學習了諸多Java的知識後,也將步入幾個大的方向,Java網絡編程就是其中之一。

  如今強調網絡的程序不比涉及網絡的更多。除了經典的應用程序,如電子郵件、Web瀏覽器和遠程登陸外,大多數主要的應用程序都有某種程度的內質網絡功能。比如我們最常使用的IDE(Eclipse/IDEA)與源代碼存儲庫(GitHub等等)進行通信;再比如Word,可以從URL打開文件;又或者是我們玩的眾多聯機遊戲,玩家實時相互對戰等等。Java是第一個從一開始就為網絡應用而設計的編程語言,最早的兩個實用Java應用的程序之一就是Web瀏覽器,隨著Internet的不斷發展,Java成為了唯一適合構建下一代網絡應用程序的語言。(節選自Java NetWork Programming——Elliotte Rusty Harold著

一、Client/Server

  為了實現兩臺計算機的通信,必須要用一個網絡線路連接兩臺計算機。服務器(Server)是指提供信息的計算機或程序,客戶機(Client)是指請求信息的計算機或程序,而網絡用於連接服務器與客戶機,實現兩者相互通信。

  下面我們看一個簡單的通信例子。

  如下的Server程序是一個服務器端應用程序,使用 Socket 來監聽一個指定的端口。

技術分享圖片

 1 import java.io.IOException; 2 import java.io.InputStreamReader; 3 import java.io.Reader; 4 import java.net.ServerSocket; 5 import java.net.Socket; 6  7 public class Server { 8  9     public static void main(String[] args) throws IOException {10         int port = 9999;11         12         System.out.println("-----------客戶端啟動-----------");13         14         ServerSocket server = new ServerSocket(port);15         Socket socket = server.accept();16         Reader reader = new InputStreamReader(socket.getInputStream());17         char chars[] = new char[1024];18         int len;19         StringBuilder builder = new StringBuilder();20         while ((len=reader.read(chars)) != -1) {21            builder.append(new String(chars, 0, len));22         }23         System.out.println("收到來自客戶端的信息: " + builder);24         reader.close();25         socket.close();26         server.close();27     }28 29 }

技術分享圖片

  如下的Client是一個客戶端程序,該程序通過 socket 連接到服務器並發送一個請求,然後等待一個響應。

技術分享圖片

 1 import java.io.IOException; 2 import java.io.OutputStreamWriter; 3 import java.io.Writer; 4 import java.net.Socket; 5 import java.util.Scanner; 6  7 public class Client { 8  9     public static void main(String[] args) throws IOException {10         String host = "127.0.0.1";11         int port = 9999;12         13         System.out.println("-----------服務器啟動-----------");14         15         Socket client = new Socket(host, port);16         Writer writer = new OutputStreamWriter(client.getOutputStream());17         Scanner in = new Scanner(System.in);18         writer.write(in.nextLine());19         writer.flush();20         writer.close();21         client.close();22         in.close();23     }24     25 }

技術分享圖片

  先啟動服務器,運行結果如下:

  技術分享圖片

  再運行客戶端,並輸入要發送的信息,如下:

  技術分享圖片

  此時Server就接收到了Client發送的消息,如下:

  技術分享圖片

  這就是一個簡單的套接字通信了,具體分析見下方TCP。

二、TCP

  TCP網絡程序設計是指利用Socket類編寫通信程序,具體實例參照上例。

  套接字使用TCP提供了兩臺計算機之間的通信機制。 客戶端程序創建一個套接字,並嘗試連接服務器的套接字。當連接建立時,服務器會創建一個 Socket 對象。客戶端和服務器現在可以通過對 Socket 對象的寫入和讀取來進行通信。java.net.Socket 類代表一個套接字,並且 java.net.ServerSocket 類為服務器程序提供了一種來監聽客戶端,並與他們建立連接的機制。

  兩臺計算機間使用套接字建立TCP連接步驟如下:

  • 服務器實例化一個 ServerSocket 對象,表示通過服務器上的端口通信。

  • 服務器調用 ServerSocket 類的 accept() 方法,該方法將一直等待,直到客戶端連接到服務器上給定的端口。

  • 服務器正在等待時,一個客戶端實例化一個 Socket 對象,指定服務器名稱和端口號來請求連接。

  • Socket 類的構造函數試圖將客戶端連接到指定的服務器和端口號。如果通信被建立,則在客戶端創建一個 Socket 對象能夠與服務器進行通信。

  • 在服務器端,accept() 方法返回服務器上一個新的 socket 引用,該 socket 連接到客戶端的 socket。

  連接建立後,通過使用 I/O 流在進行通信,每一個socket都有一個輸出流和一個輸入流,客戶端的輸出流連接到服務器端的輸入流,而客戶端的輸入流連接到服務器端的輸出流。

  1. InetAddress

  java.net包中的InetAddress類是與IP地址相關的類,利用該類可以獲取IP地址、主機地址等信息。

InetAddress ip = InetAddress.getLocalHost();
String localName = ip.getHostName();    //獲取本地主機名String localIp = ip.getHostAddress();    //獲取本地主機的ip地址

  2. ServerSocket

  java.net包中的ServetSocket類用於表示服務器套接字,其主要功能是等待來自網絡上的“請求”,它可以通過指定的端口來等待連接的套接字(如上方實例中的9999)。

  而服務器套接字一次可以與一個套接字連接,如果多臺客戶機同時提出連接請求,服務器套接字會將請求連接的客戶機存入隊列中,然後從中取出一個套接字,與服務器新建的套接字連接起來。若請求連接數大於最大容納數,則多出的連接請求被拒絕。

  ServerSocket的具體方法可參照API,這裏只對accept()方法進行一個說明。調用accept()方法將返回一個與客戶端Socket對象相連的Socket對象,服務器端的Socket對象使用getOutputStream()方法獲得的輸出流對象,將指向客戶端Socket對象使用getInputStream()方法獲得的輸入流對象,反之亦然。

  需要註意的是,accept()方法會阻塞線程的繼續執行,直到接受客戶的呼叫,如果沒有客戶呼叫服務器,那麽下面的代碼將不會執行。

三、UDP

  用戶數據包協議(UDP)是網絡信息傳輸的另一種形式,基於UDP的通信與基於TCP的通信不同,UDP的信息傳遞更快,但不提供可靠的保證。

  1. DatagramPacket

  java.net包中的DatagramPacket類用來表示數據包,構造方法如下:

DatagramPacket(byte[] buf, int length)
DatagramPacket(byte[] buf, int length, InetAddress address, int port)

  上述構造方法中,第一種指定了數據包的內存空間和大小,第二種不僅指定了數據包的內存空間和大小,並且指定了數據包的目標地址和端口。

  2. DatagramSocket

  java.net包中的DatagramSocket類用於表示發送和接受數據包的套接字,構造方法如下:

DatagramSocket()
DatagramSocket(int port)
DatagramSocket(int port, InetAddress addr)

  上述構造方法中,第一種創建數據包套接字並將其綁定到本地主機上任何可用的端口,第二種創建數據包套接字並將其綁定到本地主機上的指定端口,創建數據包套接字並將其綁定到指定的本機地址

四、實例測試

  下面寫一個完整的實例,題目如下:

  • 建立服務端程序,服務器端程序接收來自客戶端的請求;

  • 從網上下載程序,英語900句,每句占一行;

  • 服務端讀取該文件,保存到集合或者列表中;

  • 建立客戶端程序,使用”sentence: <編號#>,<編號#>”的格式發生數據。例如:發送”sentense:1,2,3” , 服務端把相應編號的句子發送給客戶端,並加以呈現;

  • 客戶端需要把服務端發送的句子保存起來,如果已經保存有相應的句子,將不再保存;

  • 客戶端需要把從服務端獲取的數據存儲到文件中。

  1. 程序結構

  技術分享圖片

  2. Server.java

  該類為客戶端類。

技術分享圖片

 1 import java.io.*; 2 import java.net.ServerSocket; 3 import java.net.Socket; 4 import java.util.ArrayList; 5 import java.util.List; 6  7 /** 8  * 
 9  * @author adamjwh10  *11  */12 public class Server {13     14     public static List<String> sentence;15     private static String filename = "src/com/adamjwh/jnp/ex6_2/English900.txt";16     17     public static void main(String[] args) throws IOException {18         sentence=new ArrayList<>();19         System.out.println("-----------服務器啟動-----------");20         21         FileReader fileReader = new FileReader(filename);22         BufferedReader br = new BufferedReader(fileReader);23         24         String inputLine = null;25         while ((inputLine = br.readLine()) != null) {26             sentence.add(inputLine);27         }28 29         ServerSocket ss = new ServerSocket(9999);30         while(true){31             Socket sk =ss.accept();32             ServerThread st = new ServerThread(sk);33             st.start();34         }35         36     }37 }

技術分享圖片

  2. Client.java

  該類為服務器類。

技術分享圖片

import java.io.BufferedReader;import java.io.FileWriter;import java.io.InputStream;import java.io.InputStreamReader;import java.io.PrintStream;import java.net.Socket;import java.util.Scanner;/**
 * 
 * @author adamjwh
 * */public class Client {    
    private static String filename = "src/com/adamjwh/jnp/ex6_2/result.txt";    
    public static void main(String[] args) {        try {
            Socket sk = new Socket("127.0.0.1", 9999);
            System.out.println("-----------客戶端啟動-----------");

            PrintStream ps = new PrintStream(sk.getOutputStream());
            System.out.print("發送:");
            Scanner sn = new Scanner(System.in);
            String str = sn.nextLine();
            ps.println(str);

            InputStream is = sk.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);            
            //寫文件
            FileWriter fw = new FileWriter(filename, true);            //讀文件
            String result = new ReadFile().readFile(filename);
            
            String s;            while ((s = br.readLine()) != null) {
                System.out.println("服務器推送:" + s);                if(!result.contains(s)) {
                    fw.write(s + "\n");
                }
            }

            sk.shutdownInput();
            ps.close();
            sk.close();
            fw.close();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

技術分享圖片

  3. ServerThread

  通過線程實現信息交互。

技術分享圖片

 1 import java.io.*; 2 import java.net.Socket; 3  4 /** 5  * 
 6  * @author adamjwh 7  * 8  */ 9 public class ServerThread extends Thread{10     Socket sk;11     public ServerThread(Socket sk){12         this.sk= sk;13     }14     public void run() {15         BufferedReader br=null;16         try{17             br  = new BufferedReader(new InputStreamReader(sk.getInputStream()));18             String line = br.readLine();19             System.out.println("客戶端:"+line);20             String[] split = line.split(":");21             String[] split1 = split[split.length - 1].split(",");22             sk.shutdownInput();23 24             OutputStream os = sk.getOutputStream();25 26             PrintStream bw = new PrintStream(os);27 28             //給客戶端返回信息29             for(int i=0;i<split1.length;i++) {30                 bw.print(Server.sentence.get(Integer.parseInt(split1[i])%Server.sentence.size())+"\n");31             }32             bw.flush();33             br.close();34             sk.close();35         }36         catch(IOException e){37             e.printStackTrace();38         }39     }40 }

技術分享圖片

  4. 運行結果

  先運行服務器

  技術分享圖片

  再運行客戶端,並輸入信息

  技術分享圖片

  服務器接收到客戶端發來的請求

  技術分享圖片

  服務器將請求結果推送給客戶端,這裏客戶端就獲取到了它請求的第174句、第258句及第5句,並輸出到文件中

  技術分享圖片

  這裏的測試文件我測試到了162(也就是該文件的第15行),可以看到在文件中追加了兩行新的句子(174和258),而第5句因為在之前已經出現過了(該文件的第1行),所以沒有追加第5句,完成上述題目。

  技術分享圖片


Java高級篇——網絡通信