Socket通訊 :html
1.TCP協議是面向對象鏈接、可靠的、有序的,以字節流的方式發送數據。編程
2.基於TCP協議實現網絡通訊的類:小程序
Socket通訊模型用下圖所示:緩存
一、在服務端創建一個ServerSocket,綁定相應的端口,而且在指定的端口進行偵聽,等待客戶端的鏈接。服務器
二、當客戶端建立鏈接Socket而且向服務端發送請求。網絡
三、服務器收到請求,而且接受客戶端的請求信息。一旦接收到客戶端的鏈接請求後,會建立一個連接socket,用來與客戶端的socket進行通訊。 經過相應的輸入/輸出流進行數據的交換,數據的發送接收以及數據的響應等等。多線程
四、當客戶端和服務端通訊完畢後,須要分別關閉socket,結束通訊。socket
Socket通訊實現步驟:測試
瞭解Socket通訊模型後,就能夠簡化出Socket通訊的實現步驟:編碼
1.建立ServerSocket和Socket
2.打開連接到Socket的輸入/輸出流
3.按照協議對Socket進行讀/寫操做
4.關閉輸入輸出流、關閉Socket
ServerSocket經常使用方法:
Socket經常使用方法:
經過寫一個用戶登陸的小程序,來直觀瞭解Socket通訊的工做過程,首先咱們得知道在客戶端和服務器通訊時,二者的運做流程是如何的,先從服務器入手。
服務端:
一、建立ServerSocket對象,綁定監聽端口
二、經過accept()方法監聽客戶端請求
三、鏈接創建後,經過輸入流讀取客戶端發送的請求信息
四、經過輸出流向客戶端發送響應信息
五、關閉相關資源
那麼用戶登陸的測試案例的服務器端類能夠這樣(待完善):
1 //1.建立一個服務器端的Socket,即ServerSocket,指定綁定的端口 2 ServerSocket ss=new ServerSocket(8888); 3 //2.調用accept方法開始監聽,等待客戶端的鏈接 4 System.out.println("服務器即將啓動,等待客戶端的鏈接..."); 5 Socket so=ss.accept();//accept方法返回Socket實例 6 //3.獲取一個輸入流,並讀取客戶端信息 7 InputStream is=so.getInputStream();//字節輸入流 8 InputStreamReader isr=new InputStreamReader(is);//將字節輸入流包裝成字符輸入流 9 BufferedReader br=new BufferedReader(isr);//加上緩衝流,提升效率 10 String info=null; 11 while((info=br.readLine())!=null){//循環讀取客戶端信息 12 System.out.println("我是服務器,客戶端說:"+info); 13 14 } 15 so.shutdownInput();//關閉輸入流 16 //4.關閉資源 17 br.close(); 18 isr.close(); 19 is.close(); 20 so.close(); 21 ss.close();
接着咱們看一下客戶端的運做過程,其實和服務器端差很少,可是其中的區別仍是要清楚的。
客戶端:
一、建立Socket對象,指明須要鏈接的服務器的地址和端口號
二、鏈接創建後,經過輸出流向服務器端發送請求信息
三、經過輸入流獲取服務器相應的信息
四、關閉相關資源。
那麼用戶登陸的測試案例的客戶端類能夠這樣(待完善):
1 //1.建立客戶端Socket,指定服務器地址和端口 2 Socket so=new Socket("localhost", 8888);//端口號要和服務器端相同 3 //2.獲取輸出流,向服務器端發送登陸的信息 4 OutputStream os=so.getOutputStream();//字節輸出流 5 PrintWriter pw=new PrintWriter(os);//字符輸出流 6 BufferedWriter bw=new BufferedWriter(pw);//加上緩衝流 7 bw.write("用戶名:admin;密碼:123"); 8 bw.flush(); 9 so.shutdownOutput();//關閉輸出流 10 //3.關閉資源 11 bw.close(); 12 pw.close(); 13 os.close(); 14 so.close();
這樣服務端和客戶端就能進行最簡單的通訊了,目前這個還不能實現交互,下面會講二者的交互。
運行一下,看看服務器端是否能接收到客戶端發送的信息了呢...
分別啓動服務器和客戶端,注意必須先啓動服務器再啓動客戶端,不然客戶端找不到資源報錯:
完善用戶登陸之服務器響應客戶端
在上述的例子中,服務器和客戶端僅僅只是相互能夠通訊,但服務器對於接收到的客戶端信息並無向客戶端響應,客戶端沒有接收到服務器端的任何信息,這明顯是不完善不友好的,在這裏將對剛剛的例子進行完善,完成服務器對客戶端的響應。
修改後的服務器端:
1 //1.建立一個服務器端的Socket,即ServerSocket,指定綁定的端口 2 ServerSocket ss= new ServerSocket(8888); 3 //2.調用accept方法開始監聽,等待客戶端的鏈接 4 System.out.println("服務器即將啓動,等待客戶端的鏈接..."); 5 Socket so=ss.accept();//accept方法返回Socket實例 6 //3.獲取一個輸入流,並讀取客戶端信息 7 InputStream is=so.getInputStream();//字節輸入流 8 InputStreamReader isr=new InputStreamReader(is);//將字節輸入流包裝成字符輸入流 9 BufferedReader br=new BufferedReader(isr);//加上緩衝流,提升效率 10 String info=null; 11 while((info=br.readLine())!=null){//循環讀取客戶端信息 12 System.out.println("我是服務器,客戶端說:"+info); 13 14 } 15 so.shutdownInput();//關閉輸入流 16 //4.獲取一個輸出流,向客戶端輸出信息,響應客戶端的請求 17 OutputStream os=so.getOutputStream();//字節輸出流 18 PrintWriter pw=new PrintWriter(os);//字符輸出流 19 BufferedWriter bw=new BufferedWriter(pw);//緩衝輸出流 20 bw.write("歡迎您!"); 21 bw.newLine(); 22 bw.flush(); 23 24 //5.關閉資源 25 os.close(); 26 pw.close(); 27 bw.close(); 28 br.close(); 29 isr.close(); 30 is.close(); 31 so.close(); 32 ss.close();
修改後的客戶端:
1 //1.建立客戶端Socket,指定服務器地址和端口 2 Socket so=new Socket("localhost", 8888);//端口號要和服務器端相同 3 //2.獲取輸出流,向服務器端發送登陸的信息 4 OutputStream os=so.getOutputStream();//字節輸出流 5 PrintWriter pw=new PrintWriter(os);//字符輸出流 6 BufferedWriter bw=new BufferedWriter(pw);//加上緩衝流 7 bw.write("用戶名:admin;密碼:123"); 8 bw.flush(); 9 so.shutdownOutput();//關閉輸出流 10 //3.獲取輸入流,獲得服務端的響應信息 11 InputStream is=so.getInputStream(); 12 InputStreamReader isr=new InputStreamReader(is); 13 BufferedReader br=new BufferedReader(isr); 14 String info=null; 15 while((info=br.readLine())!=null){ 16 System.out.println("我是客戶端,服務器說:"+info); 17 } 18 19 20 //4.關閉資源 21 bw.close(); 22 pw.close(); 23 os.close(); 24 so.close();
運行結果:
應用多線程來實現服務器與多客戶端之間的通訊。<關於多線程更多的內容之後再總結>
多線程基本步驟:
1.服務器端建立ServerSocket,循環調用accept()等待客戶端鏈接。
2.客戶端建立一個socket並請求和服務器端鏈接。
3.服務器端接收客戶端請求,建立socket與該客戶創建專線鏈接。
4.創建鏈接的兩個socket在一個單獨的線程上對話。
5.服務器端繼續等待新的鏈接。
------------------------------------------------------------------------------
下面再將上述的例子修改爲多線程的通訊
新建一個服務器線程處理類ServerThread,該類繼承Thread類:
1 /* 2 * 服務器線程處理類 3 */ 4 public class ServerThread extends Thread { 5 // 和本線程相關的Socket 6 Socket so = null; 7 8 public ServerThread(Socket socket) {// 初始化與本線程相關的Socket 9 so = socket; 10 } 11 12 // 線程執行的操做,響應客戶端的請求 13 public void run() {// 重寫父類的run方法 14 InputStream is = null; 15 InputStreamReader isr = null; 16 BufferedReader br = null; 17 OutputStream os = null; 18 PrintWriter pw = null; 19 BufferedWriter bw = null; 20 try { 21 // 3.獲取一個輸入流,並讀取客戶端信息 22 is = so.getInputStream();// 字節輸入流 23 isr = new InputStreamReader(is);// 將字節輸入流包裝成字符輸入流 24 br = new BufferedReader(isr);// 加上緩衝流,提升效率 25 String info = null; 26 while ((info = br.readLine()) != null) {// 循環讀取客戶端信息 27 System.out.println("我是服務器,客戶端說:" + info); 28 29 } 30 so.shutdownInput();// 關閉輸入流 31 // 4.獲取一個輸出流,向客戶端輸出信息,響應客戶端的請求 32 os = so.getOutputStream();// 字節輸出流 33 pw = new PrintWriter(os);// 字符輸出流 34 bw = new BufferedWriter(pw);// 緩衝輸出流 35 bw.write("歡迎您!"); 36 bw.newLine(); 37 bw.flush(); 38 39 } catch (IOException e) { 40 // TODO Auto-generated catch block 41 e.printStackTrace(); 42 } finally { 43 // 5.關閉資源 44 try { 45 if (os != null) 46 os.close(); 47 if (pw != null) 48 pw.close(); 49 if (bw != null) 50 bw.close(); 51 if (br != null) 52 br.close(); 53 if (isr != null) 54 isr.close(); 55 if (is != null) 56 is.close(); 57 if (!so.isClosed()) 58 so.close(); 59 } catch (IOException e) { 60 // TODO Auto-generated catch block 61 e.printStackTrace(); 62 } 63 } 64 65 } 66 }
<解 惑>關閉資源爲什麼要加一個判斷條件:不爲空 ???
<回 答>這是一種正確、嚴謹的寫法。 驗證非NULL是編碼中很重要的一環。假如原本就是NULL,這是調用各自的close()方法是會報錯的。 若是在實例化這些對象時出錯致使這些對象爲NULL,或是實例化沒問題但中途出了什麼異常致使這些對象爲NULL,都會在未經驗證非NULL前嘗試調用close()方法關閉時報錯。
<提 示>集中異常處理的快捷鍵:Alt+shift+z
服務器端:
1 try { 2 //1.建立一個服務器端的Socket,即ServerSocket,指定綁定的端口 3 ServerSocket ss= new ServerSocket(8888); 4 5 System.out.println("服務器即將啓動,等待客戶端的鏈接..."); 6 Socket so=null; 7 //記錄客戶端的數量 8 int count=0; 9 //循環偵聽等待客戶端的鏈接 10 while(true){ 11 //2.調用accept方法開始監聽,等待客戶端的鏈接 12 so=ss.accept();//accept方法返回Socket實例 13 //建立一個新的線程 14 ServerThread st=new ServerThread(so); 15 //啓動線程,執行與客戶端的交互 16 st.start();//注意是start不是run 17 count++; 18 System.out.println("此時客戶端數量爲:"+count); 19 InetAddress add=so.getInetAddress(); 20 System.out.println("當前客戶端的ip地址爲"+add.getHostAddress()); 21 } 22 } catch (IOException e) { 23 // TODO Auto-generated catch block 24 e.printStackTrace(); 25 }
多個客戶端的運行結果:
這樣一個簡單的多線程通訊就完成了,這個多線程還不是並行操做的,要實現並行操做能夠按照下面的思路:
主線程負責建立socket
* 一個線程用來讀取
* 一個線程用來寫入,用兩個內部類
* 要用多線程實現,send線程和recived線程同時訪問buff數據區。
* 發送完成後notify,接收線程。接收線程自身wait
* 接收的說,我先wait一下,可能緩存中尚未數據,我會拿到空值得。
* 發送的說Ok,我先寫,寫完了我notifyall你。我就一直鎖着,這樣默契的合做了。
變成並行處理了 每一個客戶端鏈接服務端都會產生一個新的socket。
< 具體代碼還沒寫,你們能夠自行摸索。。。寫完了再更新>
---------------點擊查看更多關於Socket信息------------------