JAdam html
網絡編程是每一個開發人員工具相中的核心部分,咱們在學習了諸多Java的知識後,也將步入幾個大的方向,Java網絡編程就是其中之一。java
現在強調網絡的程序不比涉及網絡的更多。除了經典的應用程序,如電子郵件、Web瀏覽器和遠程登錄外,大多數主要的應用程序都有某種程度的內質網絡功能。好比咱們最常使用的IDE(Eclipse/IDEA)與源代碼存儲庫(GitHub等等)進行通訊;再好比Word,能夠從URL打開文件;又或者是咱們玩的衆多聯機遊戲,玩家實時相互對戰等等。Java是第一個從一開始就爲網絡應用而設計的編程語言,最先的兩個實用Java應用的程序之一就是Web瀏覽器,隨着Internet的不斷髮展,Java成爲了惟一適合構建下一代網絡應用程序的語言。(節選自Java NetWork Programming——Elliotte Rusty Harold著)編程
爲了實現兩臺計算機的通訊,必需要用一個網絡線路鏈接兩臺計算機。服務器(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 }
先啓動服務器,運行結果以下:app
再運行客戶端,並輸入要發送的信息,以下:socket
此時Server就接收到了Client發送的消息,以下:編程語言
這就是一個簡單的套接字通訊了,具體分析見下方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都有一個輸出流和一個輸入流,客戶端的輸出流鏈接到服務器端的輸入流,而客戶端的輸入流鏈接到服務器端的輸出流。
java.net包中的InetAddress類是與IP地址相關的類,利用該類能夠獲取IP地址、主機地址等信息。
InetAddress ip = InetAddress.getLocalHost(); String localName = ip.getHostName(); //獲取本地主機名String localIp = ip.getHostAddress(); //獲取本地主機的ip地址
java.net包中的ServetSocket類用於表示服務器套接字,其主要功能是等待來自網絡上的「請求」,它能夠經過指定的端口來等待鏈接的套接字(如上方實例中的9999)。
而服務器套接字一次能夠與一個套接字鏈接,若是多臺客戶機同時提出鏈接請求,服務器套接字會將請求鏈接的客戶機存入隊列中,而後從中取出一個套接字,與服務器新建的套接字鏈接起來。若請求鏈接數大於最大容納數,則多出的鏈接請求被拒絕。
ServerSocket的具體方法可參照API,這裏只對accept()方法進行一個說明。調用accept()方法將返回一個與客戶端Socket對象相連的Socket對象,服務器端的Socket對象使用getOutputStream()方法得到的輸出流對象,將指向客戶端Socket對象使用getInputStream()方法得到的輸入流對象,反之亦然。
須要注意的是,accept()方法會阻塞線程的繼續執行,直到接受客戶的呼叫,若是沒有客戶呼叫服務器,那麼下面的代碼將不會執行。
用戶數據包協議(UDP)是網絡信息傳輸的另外一種形式,基於UDP的通訊與基於TCP的通訊不一樣,UDP的信息傳遞更快,但不提供可靠的保證。
java.net包中的DatagramPacket類用來表示數據包,構造方法以下:
DatagramPacket(byte[] buf, int length) DatagramPacket(byte[] buf, int length, InetAddress address, int port)
上述構造方法中,第一種指定了數據包的內存空間和大小,第二種不只指定了數據包的內存空間和大小,而且指定了數據包的目標地址和端口。
java.net包中的DatagramSocket類用於表示發送和接受數據包的套接字,構造方法以下:
DatagramSocket() DatagramSocket(int port) DatagramSocket(int port, InetAddress addr)
上述構造方法中,第一種建立數據包套接字並將其綁定到本地主機上任何可用的端口,第二種建立數據包套接字並將其綁定到本地主機上的指定端口,建立數據包套接字並將其綁定到指定的本機地址。
下面寫一個完整的實例,題目以下:
創建服務端程序,服務器端程序接收來自客戶端的請求;
從網上下載程序,英語900句,每句佔一行;
服務端讀取該文件,保存到集合或者列表中;
創建客戶端程序,使用」sentence: <編號#>,<編號#>」的格式發生數據。例如:發送」sentense:1,2,3」 , 服務端把相應編號的句子發送給客戶端,並加以呈現;
客戶端須要把服務端發送的句子保存起來,若是已經保存有相應的句子,將再也不保存;
客戶端須要把從服務端獲取的數據存儲到文件中。
該類爲客戶端類。
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 }
該類爲服務器類。
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(); } } }
經過線程實現信息交互。
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 }
先運行服務器
再運行客戶端,並輸入信息
服務器接收到客戶端發來的請求
服務器將請求結果推送給客戶端,這裏客戶端就獲取到了它請求的第174句、第258句及第5句,並輸出到文件中
這裏的測試文件我測試到了162(也就是該文件的第15行),能夠看到在文件中追加了兩行新的句子(174和258),而第5句由於在以前已經出現過了(該文件的第1行),因此沒有追加第5句,完成上述題目。