【Socket編程】通過Socket實現TCP編程


通過Socket實現TCP編程

Socket通信 :

1.TCP協議是面向對象連接、可靠的、有序的,以字節流的方式發送數據。

2.基於TCP協議實現網絡通信的類:

  • 客戶端----Socket類
  • 服務器端----ServerSocket類

一、通信過程(Socket通信模型)

Socket通信模型用下圖所示:

 

1、在服務端建立一個ServerSocket,綁定相應的端口,並且在指定的端口進行偵聽,等待客戶端的連接。

2、當客戶端創建連接Socket並且向服務端發送請求。

3、服務器收到請求,並且接受客戶端的請求信息。一旦接收到客戶端的連接請求后,會創建一個鏈接socket,用來與客戶端的socket進行通信。 通過相應的輸入/輸出流進行數據的交換,數據的發送接收以及數據的響應等等。

4、當客戶端和服務端通信完畢后,需要分別關閉socket,結束通信。

Socket通信實現步驟:

了解Socket通信模型后,就可以簡化出Socket通信的實現步驟:

1.創建ServerSocket和Socket

2.打開鏈接到Socket的輸入/輸出流

3.按照協議對Socket進行讀/寫操作

4.關閉輸入輸出流、關閉Socket

二、Socket和ServerSocket常用方法

ServerSocket常用方法:

  • ServerSocket(int port)——創建並綁定到特定端口的服務器套接字
  • accept()——偵聽並接受到此套接字的連接
  • close()——關閉此套接字 getInetAddress()——得到ServerSocket對象綁定的IP地址。如果ServerSocket對象未綁定IP地址,返回0.0.0.0
  • getLocalPort()——返回此套接字在其上偵聽的端口

Socket常用方法:

  • Socket(InetAddress address, int port)——創建一個套接字並將其連接到指定ip地址的指定端口號
  • Socket(String host, int port)——創建一個套接字並將其連接到指定主機上的指定端口號
  • close()——關閉此套接字
  • getInetAddress()——返回套接字連接的地址
  • getInputStream()——返回此套接字的輸入流
  • getOutputStream——返回此套接字的輸出流

三、編程實現基於TCP/IP的用戶登錄小程序

通過寫一個用戶登錄的小程序,來直觀了解Socket通信的工作過程,首先我們得知道在客戶端和服務器通信時,兩者的運作流程是如何的,先從服務器入手。

服務端:

1、創建ServerSocket對象,綁定監聽端口

2、通過accept()方法監聽客戶端請求

3、連接建立后,通過輸入流讀取客戶端發送的請求信息

4、通過輸出流向客戶端發送響應信息

5、關閉相關資源

那么用戶登錄的測試案例的服務器端類可以這樣(待完善):

 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();

 

接着我們看一下客戶端的運作過程,其實和服務器端差不多,但是其中的區別還是要清楚的。

客戶端:

1、創建Socket對象,指明需要連接的服務器的地址和端口號

2、連接建立后,通過輸出流向服務器端發送請求信息

3、通過輸入流獲取服務器相應的信息

4、關閉相關資源。

那么用戶登錄的測試案例的客戶端類可以這樣(待完善):

 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信息------------------


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM