1、網絡通信方式html
在現有的網絡中,網絡通信的方式主要有兩種:java
1.TCP(傳輸控制協議)方式
2.UDP(用戶數據報協議)方式spring
爲了方便理解這兩種方式,仍是先來看一個例子。你們使用手機時,向別人傳遞信息時有兩種方式:撥打電話和發送短信。使用撥打電話的方式能夠保證將信息傳遞給別人,由於別人接聽電話時自己就確認接收到了該信息。而發送短信的方式價格低廉,使用方便,可是接收人有可能接收不到。編程
在網絡通信中,TCP方式就相似於撥打電話,使用該種方式進行網絡通信時,須要創建專門的虛擬鏈接,而後進行可靠的數據傳輸,若是數據發送失敗,則客戶端會自動重發該數據。而UDP方式就相似於發送短信,使用這種方式進行網絡通信時,不須要創建專門的虛擬鏈接,傳輸也不是很可靠,若是發送失敗則客戶端沒法得到。數組
這兩種傳輸方式都是實際的網絡編程中進行使用,重要的數據通常使用TCP方式進行數據傳輸,而大量的非核心數據則都經過UDP方式進行傳遞,在一些程序中甚至結合使用這兩種方式進行數據的傳遞。服務器
因爲TCP須要創建專用的虛擬鏈接以及確認傳輸是否正確,因此使用TCP方式的速度稍微慢一些,並且傳輸時產生的數據量要比UDP稍微大一些。網絡
關於網絡編程的基礎知識就介紹這麼多,若是須要深刻了解相關知識請閱讀專門的計算機網絡書籍,下面開始介紹Java語言中網絡編程的相關技術。socket
2、網絡編程步驟tcp
按照前面的基礎知識介紹,不管使用TCP方式仍是UDP方式進行網絡通信,網絡編程都是由客戶端和服務器端組成。固然,B/S結構的編程中只須要實現服務器端便可。因此,下面介紹網絡編程的步驟時,均以C/S結構爲基礎進行介紹。this
說明:這裏的步驟實現和語言無關,也就是說,這個步驟適用於各類語言實現,不侷限於Java語言。
2.1 客戶端網絡編程步驟
客戶端(Client)是指網絡編程中首先發起鏈接的程序,客戶端通常實現程序界面和基本邏輯實現,在進行實際的客戶端編程時,不管客戶端複雜仍是簡單,以及客戶端實現的方式,客戶端的編程主要由三個步驟實現:
(1)創建網絡鏈接
客戶端網絡編程的第一步都是創建網絡鏈接。在創建網絡鏈接時須要指定鏈接到的服務器的IP地址和端口號,創建完成之後,會造成一條虛擬的鏈接,後續的操做就能夠經過該鏈接實現數據交換了。
(2)交換數據
鏈接創建之後,就能夠經過這個鏈接交換數據了。交換數據嚴格按照請求響應模型進行,由客戶端發送一個請求數據到服務器,服務器反饋一個響應數據給客戶端,若是客戶端不發送請求則服務器端就不響應。
根據邏輯須要,能夠屢次交換數據,可是仍是必須遵循請求響應模型。
(3)關閉網絡鏈接
在數據交換完成之後,關閉網絡鏈接,釋放程序佔用的端口、內存等系統資源,結束網絡編程。
最基本的步驟通常都是這三個步驟,在實際實現時,步驟2會出現重複,在進行代碼組織時,因爲網絡編程是比較耗時的操做,因此通常開啓專門的現場進行網絡通信。
2.2 服務器端網絡編程步驟
服務器端(Server)是指在網絡編程中被動等待鏈接的程序,服務器端通常實現程序的核心邏輯以及數據存儲等核心功能。服務器端的編程步驟和客戶端不一樣,是由四個步驟實現,依次是:
(1)監聽端口
服務器端屬於被動等待鏈接,因此服務器端啓動之後,不須要發起鏈接,而只須要監聽本地計算機的某個固定端口便可。
這個端口就是服務器端開放給客戶端的端口,服務器端程序運行的本地計算機的IP地址就是服務器端程序的IP地址。
(2)得到鏈接
當客戶端鏈接到服務器端時,服務器端就能夠得到一個鏈接,這個鏈接包含客戶端的信息,例如客戶端IP地址等等,服務器端和客戶端也經過該鏈接進行數據交換。
通常在服務器端編程中,當得到鏈接時,須要開啓專門的線程處理該鏈接,每一個鏈接都由獨立的線程實現。
(3)交換數據
服務器端經過得到的鏈接進行數據交換。服務器端的數據交換步驟是首先接收客戶端發送過來的數據,而後進行邏輯處理,再把處理之後的結果數據發送給客戶端。簡單來講,就是先接收再發送,這個和客戶端的數據交換數序不一樣。
其實,服務器端得到的鏈接和客戶端鏈接是同樣的,只是數據交換的步驟不一樣。
固然,服務器端的數據交換也是能夠屢次進行的。
在數據交換完成之後,關閉和客戶端的鏈接。
(4)關閉鏈接
當服務器程序關閉時,須要關閉服務器端,經過關閉服務器端使得服務器監聽的端口以及佔用的內存能夠釋放出來,實現了鏈接的關閉。
其實服務器端編程的模型和呼叫中心的實現是相似的,例如移動的10086就是典型的呼叫中心,當一個用戶撥打10086時,轉接給一個專門的客服人員,由該客服實現和該用戶的問題解決,當另一個用戶撥打10086時,則轉接給另外一個客服,實現問題解決,依次類推。
在服務器端編程時,10086這個電話號碼就相似於服務器端的端口號碼,每一個用戶就至關於一個客戶端程序,每一個客服人員就至關於服務器端啓動的專門和客戶端鏈接的線程,每一個線程都是獨立進行交互的。
這就是服務器端編程的模型,只是TCP方式是須要創建鏈接的,對於服務器端的壓力比較大,而UDP是不須要創建鏈接的,對於服務器端的壓力比較小罷了。
3、TCP編程
1.複用性較高的Socket客戶端:
package tcp; import java.io.*; import java.net.*; /** * 複用鏈接的Socket客戶端 * 功能爲:發送字符串「Hello」到服務器端,並打印出服務器端的反饋 */ public class MulSocketClient { public static void main(String[] args) { Socket socket = null; InputStream is = null; OutputStream os = null; //服務器端IP地址 String serverIP = "127.0.0.1"; //服務器端端口號 int port = 10000; //發送內容 String data[] ={"First","Second","Third"}; try { //創建鏈接 socket = new Socket(serverIP,port); //初始化流 os = socket.getOutputStream(); is = socket.getInputStream(); byte[] b = new byte[1024]; for(int i = 0;i < data.length;i++){ //發送數據 os.write(data[i].getBytes()); //接收數據 int n = is.read(b); //輸出反饋數據 System.out.println("服務器反饋:" + new String(b,0,n)); } } catch (Exception e) { e.printStackTrace(); //打印異常信息 }finally{ try { //關閉流和鏈接 is.close(); os.close(); socket.close(); } catch (Exception e2) {} } } }
package tcp; import java.io.*; import java.net.*; /** * 複用鏈接的Socket客戶端 * 功能爲:發送字符串「Hello」到服務器端,並打印出服務器端的反饋 */ public class MulSocketClient { public static void main(String[] args) { Socket socket = null; InputStream is = null; OutputStream os = null; //服務器端IP地址 String serverIP = "127.0.0.1"; //服務器端端口號 int port = 10000; //發送內容 String data[] ={"First","Second","Third"}; try { //創建鏈接 socket = new Socket(serverIP,port); //初始化流 os = socket.getOutputStream(); is = socket.getInputStream(); byte[] b = new byte[1024]; for(int i = 0;i < data.length;i++){ //發送數據 os.write(data[i].getBytes()); //接收數據 int n = is.read(b); //輸出反饋數據 System.out.println("服務器反饋:" + new String(b,0,n)); } } catch (Exception e) { e.printStackTrace(); //打印異常信息 }finally{ try { //關閉流和鏈接 is.close(); os.close(); socket.close(); } catch (Exception e2) {} } } }
複用性較高的ServerSocket:
package tcp; import java.io.*; import java.net.*; /** * 複用鏈接的echo服務器 * 功能:將客戶端發送的內容反饋給客戶端 */ public class MulSocketServer { public static void main(String[] args) { ServerSocket serverSocket = null; Socket socket = null; OutputStream os = null; InputStream is = null; //監聽端口號 int port = 10000; try { //創建鏈接 serverSocket = new ServerSocket(port); System.out.println("服務器已啓動:"); //得到鏈接 socket = serverSocket.accept(); //初始化流 is = socket.getInputStream(); os = socket.getOutputStream(); byte[] b = new byte[1024]; for(int i = 0;i < 3;i++){ int n = is.read(b); //輸出 System.out.println("客戶端發送內容爲:" + new String(b,0,n)); //向客戶端發送反饋內容 os.write(b, 0, n); } } catch (Exception e) { e.printStackTrace(); }finally{ try{ //關閉流和鏈接 os.close(); is.close(); socket.close(); serverSocket.close(); }catch(Exception e){} } } }
package tcp; import java.io.*; import java.net.*; /** * 複用鏈接的echo服務器 * 功能:將客戶端發送的內容反饋給客戶端 */ public class MulSocketServer { public static void main(String[] args) { ServerSocket serverSocket = null; Socket socket = null; OutputStream os = null; InputStream is = null; //監聽端口號 int port = 10000; try { //創建鏈接 serverSocket = new ServerSocket(port); System.out.println("服務器已啓動:"); //得到鏈接 socket = serverSocket.accept(); //初始化流 is = socket.getInputStream(); os = socket.getOutputStream(); byte[] b = new byte[1024]; for(int i = 0;i < 3;i++){ int n = is.read(b); //輸出 System.out.println("客戶端發送內容爲:" + new String(b,0,n)); //向客戶端發送反饋內容 os.write(b, 0, n); } } catch (Exception e) { e.printStackTrace(); }finally{ try{ //關閉流和鏈接 os.close(); is.close(); socket.close(); serverSocket.close(); }catch(Exception e){} } } }
在該示例代碼中,也將數據發送和接收的邏輯寫在了一個for循環內部,只是在實現時硬性的將循環次數
規定成了3次,這樣代碼雖然比較簡單,可是通用性比較差。
以該服務器端代碼實現爲基礎運行前面的客戶端程序時,客戶端的輸出爲:
服務器反饋:First
服務器反饋:Second
服務器反饋:Third
服務器端程序的輸出結果爲:
服務器已啓動:
客戶端發送內容爲:First
客戶端發送內容爲:Second
客戶端發送內容爲:Third
2.服務器端支持多個客戶端同時工做
package tcp; import java.net.ServerSocket; import java.net.Socket; /** * 支持多客戶端的服務器端實現 */ public class MulThreadSocketServer { public static void main(String[] args) { ServerSocket serverSocket = null; Socket socket = null; //監聽端口號 int port = 10000; try { //創建鏈接 serverSocket = new ServerSocket(port); System.out.println("服務器已啓動:"); while(true){ //得到鏈接 socket = serverSocket.accept(); //啓動線程 new LogicThread(socket); } } catch (Exception e) { e.printStackTrace(); }finally{ try{ //關閉鏈接 serverSocket.close(); }catch(Exception e){} } } }
package tcp; import java.net.ServerSocket; import java.net.Socket; /** * 支持多客戶端的服務器端實現 */ public class MulThreadSocketServer { public static void main(String[] args) { ServerSocket serverSocket = null; Socket socket = null; //監聽端口號 int port = 10000; try { //創建鏈接 serverSocket = new ServerSocket(port); System.out.println("服務器已啓動:"); while(true){ //得到鏈接 socket = serverSocket.accept(); //啓動線程 new LogicThread(socket); } } catch (Exception e) { e.printStackTrace(); }finally{ try{ //關閉鏈接 serverSocket.close(); }catch(Exception e){} } } }
在該示例代碼中,實現了一個while形式的死循環,因爲accept方法是阻塞方法,因此當客戶端鏈接未到達時,將阻塞該程序的執行,當客戶端到達時接收該鏈接,並啓動一個新的LogicThread線程處理該鏈接,而後按照循環的執行流程,繼續等待下一個客戶端鏈接。這樣當任何一個客戶端鏈接到達時,都開啓一個專門的線程處理,經過多個線程支持多個客戶端同時處理。
下面再看一下LogicThread線程類的源代碼實現:
package tcp; import java.io.*; import java.net.*; /** * 服務器端邏輯線程 */ public class LogicThread extends Thread { Socket socket; InputStream is; OutputStream os; public LogicThread(Socket socket){ this.socket = socket; start(); //啓動線程 } public void run(){ byte[] b = new byte[1024]; try{ //初始化流 os = socket.getOutputStream(); is = socket.getInputStream(); for(int i = 0;i < 3;i++){ //讀取數據 int n = is.read(b); //邏輯處理 byte[] response = logic(b,0,n); //反饋數據 os.write(response); } }catch(Exception e){ e.printStackTrace(); }finally{ close(); } } /** * 關閉流和鏈接 */ private void close(){ try{ //關閉流和鏈接 os.close(); is.close(); socket.close(); }catch(Exception e){} } /** * 邏輯處理方法,實現echo邏輯 * @param b 客戶端發送數據緩衝區 * @param off 起始下標 * @param len 有效數據長度 * @return */ private byte[] logic(byte[] b,int off,int len){ byte[] response = new byte[len]; //將有效數據拷貝到數組response中 System.arraycopy(b, 0, response, 0, len); return response; } }
package tcp; import java.io.*; import java.net.*; /** * 服務器端邏輯線程 */ public class LogicThread extends Thread { Socket socket; InputStream is; OutputStream os; public LogicThread(Socket socket){ this.socket = socket; start(); //啓動線程 } public void run(){ byte[] b = new byte[1024]; try{ //初始化流 os = socket.getOutputStream(); is = socket.getInputStream(); for(int i = 0;i < 3;i++){ //讀取數據 int n = is.read(b); //邏輯處理 byte[] response = logic(b,0,n); //反饋數據 os.write(response); } }catch(Exception e){ e.printStackTrace(); }finally{ close(); } } /** * 關閉流和鏈接 */ private void close(){ try{ //關閉流和鏈接 os.close(); is.close(); socket.close(); }catch(Exception e){} } /** * 邏輯處理方法,實現echo邏輯 * @param b 客戶端發送數據緩衝區 * @param off 起始下標 * @param len 有效數據長度 * @return */ private byte[] logic(byte[] b,int off,int len){ byte[] response = new byte[len]; //將有效數據拷貝到數組response中 System.arraycopy(b, 0, response, 0, len); return response; } }
摘取開發中實際使用的部分:
http://www.cnblogs.com/springcsc/archive/2009/12/03/1616413.html