悟心

          成功不是將來才有的,而是從決定去做的那一刻起,持續累積而成。 上人生的旅途罷。前途很遠,也很暗。然而不要怕。不怕的人的面前才有路。

            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            93 隨筆 :: 1 文章 :: 103 評論 :: 0 Trackbacks
          或許有點長
          但是一步步教你
          我想你也愿意看
          7.2 面向套接字編程    
          我們已經通過了解Socket的接口,知其所以然,下面我們就將通過具體的案例,來熟悉Socket的具體工作方式

          7.2.1使用套接字實現基于TCP協議的服務器和客戶機程序    
          依據TCP協議,在C
          /S架構的通訊過程中,客戶端和服務器的Socket動作如下:

          客戶端:

          1.用服務器的IP地址和端口號實例化Socket對象。

          2.調用connect方法,連接到服務器上。

          3.將發送到服務器的IO流填充到IO對象里,比如BufferedReader/PrintWriter。

          4.利用Socket提供的getInputStream和getOutputStream方法,通過IO流對象,向服務器發送數據流。

          5. 通訊完成后,關閉打開的IO對象和Socket。

          服務器:

          1. 在服務器,用一個端口來實例化一個 ServerSocket對象。此時,服務器就可以這個端口時刻監聽從客戶端發來的連接請求。

          2.調用ServerSocket的accept方法,開始監聽連接從端口上發來的連接請求。   

          3.利用accept方法返回的客戶端的Socket對象,進行讀寫IO的操作

          通訊完成后,關閉打開的流和Socket對象。

          7.2.1.1 開發客戶端代碼
          根據上面描述的通訊流程,我們可以按如下的步驟設計服務器端的代碼。

              第一步,依次點擊Eclipse環境里的“文件”
          |“新建”|“項目”選項,進入“新建項目”的向導對話框,在其中選中“Java項目”,點擊“下一步”按鈕,在隨后彈出的對話框里,在其中的“項目名”一欄里,輸入項目名“TCPSocket”,其它的選項目

          選擇系統默認值,再按“完成”按鈕,結束創建Java項目的動作。

             第二步,完成創建項目后,選中集成開發環境左側的項目名“TCPSocket”,點擊右鍵,在隨后彈出的菜單里依次選擇“新建”
          !“類”的選項,創建服務器類的代碼。

          在隨后彈出的“新建Java類”的對話框里,輸入包名“tcp”,輸入文件名“ServerCode”,請注意大小寫,在“修飾符”里選中“公用”,在“想要創建哪些方法存根”下,選中“
          public static void main(String[] args )”單選框,同時把其它兩項目取消掉,再按“完成”按鈕,可以生成代碼。

              第三步,在生成的代碼里,編寫引入Java包的代碼,只有當我們引入這些包后,我們才能調用這些包里提供的IO和Socket類的方法。

          package tcp;

          import java.io.BufferedReader;

          import java.io.BufferedWriter;

          import java.io.IOException;

          import java.io.InputStreamReader;

          import java.io.OutputStreamWriter;

          import java.io.PrintWriter;

          import java.net.ServerSocket;

          import java.net.Socket;

             第四步,編寫服務器端的主體代碼,如下所示。

            
          public class ServerCode 

          {

                 
          // 設置端口號

                 
          public static int portNo = 3333;

                 
          public static void main(String[] args) throws IOException 

          {

                        ServerSocket s 
          = new ServerSocket(portNo);

                        System.out.println(
          "The Server is start: " + s);

                        
          // 阻塞,直到有客戶端連接

                  Socket socket 
          = s.accept();

                        
          try 

          {              

                               System.out.println(
          "Accept the Client: " + socket);                   

                               
          //設置IO句柄

                               BufferedReader in 
          = new BufferedReader(new InputStreamReader(socket

                                             .getInputStream()));

                               PrintWriter out 
          = new PrintWriter(new BufferedWriter(

                              
          new OutputStreamWriter(socket.getOutputStream())), true);                    

                               
          while (true)

          {

                                      String str 
          = in.readLine();

                                      
          if (str.equals("byebye"))

                          {

                                             
          break;

                                      }

                                      System.out.println(
          "In Server reveived the info: " + str);

                                      out.println(str);

                               }

                        } 

                  
          finally 

          {

                               System.out.println(
          "close the Server socket and the io.");

                               socket.close();

                               s.close();

                        }

              }

          }

          這段代碼的主要業務邏輯是:

          1.         在上述代碼里的main函數前,我們設置了通訊所用到的端口號,為3333。

          2.         在main函數里,根據給定3333端口號,初始化一個ServerSocket對象s,該對象用來承擔服務器端監聽連接和提供通訊服務的功能。

          3.         調用ServerSocket對象的accept方法,監聽從客戶端的連接請求。當完成調用accept方法后,整段服務器端代碼將回阻塞在這里,直到客戶端發來connect請求。

          4.         當客戶端發來connect請求,或是通過構造函數直接把客戶端的Socket對象連接到服務器端后,阻塞于此的代碼將會繼續運行。此時服務器端將會根據accept方法的執行結果,用一個Socket對象來描述客戶端的連接句柄。

          5.         創建兩個名為in和out的對象,用來傳輸和接收通訊時的數據流。

          6.         創建一個while(true)的死循環,在這個循環里,通過in.readLine()方法,讀取從客戶端發送來的IO流(字符串),并打印出來。如果讀到的字符串是“byebye”,那么退出while循環。

          7.         在try…catch…finally語句段里,不論在try語句段里是否發生異常,并且不論這些異常的種類,finally從句都將會被執行到。在finally從句里,將關閉描述客戶端的連接句柄socket對象和ServerSocket類型的s對象。

          7.2.1.2 開發客戶端代碼
          我們可以按以下的步驟,開發客戶端的代碼。

          第一,在TCPSocket項目下的tcp包下,創建一個名為ClientCode.java的文件。在其中編寫引入Java包的代碼,如下所示:

              
          package tcp;

          import java.io.BufferedReader;

          import java.io.BufferedWriter;

          import java.io.IOException;

          import java.io.InputStreamReader;

          import java.io.OutputStreamWriter;

          import java.io.PrintWriter;

          import java.net.InetAddress;

          import java.net.Socket;

          第二,編寫客戶端的主體代碼,如下所示:

           
          public class ClientCode

           {

                 
          static String clientName = "Mike";

                 
          //端口號

          public static int portNo = 3333;

                 
          public static void main(String[] args) throws IOException

          {

                        
          // 設置連接地址類,連接本地

                        InetAddress addr 
          = InetAddress.getByName("localhost");        

                        
          //要對應服務器端的3333端口號

                        Socket socket 
          = new Socket(addr, portNo);

                        
          try

          {

                      System.out.println(
          "socket = " + socket);

                               
          // 設置IO句柄

                               BufferedReader in 
          = new BufferedReader(new InputStreamReader(socket

                                             .getInputStream()));

                               PrintWriter out 
          = new PrintWriter(new BufferedWriter(

                                             
          new OutputStreamWriter(socket.getOutputStream())), true);

                               out.println(
          "Hello Server,I am " + clientName);

                               String str 
          = in.readLine();

                               System.out.println(str);

                               out.println(
          "byebye");

                        }

          finally 

          {

                               System.out.println(
          "close the Client socket and the io.");

                               socket.close();

                  }

                 }

          }

          上述客戶端代碼的主要業務邏輯是:

          1.         同樣定義了通訊端口號,這里給出的端口號必須要和服務器端的一致。

          2.         在main函數里,根據地址信息“localhost”,創建一個InetAddress類型的對象addr。這里,因為我們把客戶端和服務器端的代碼都放在本機運行,所以同樣可以用“127.0.0.1”字符串,來創建InetAddress對象。

          3.         根據addr和端口號信息,創建一個Socket類型對象,該對象用來同服務器端的ServerSocket類型對象交互,共同完成C/S通訊流程。

          4.         同樣地創建in和out兩類IO句柄,用來向服務器端發送和接收數據流。

          5.         通過out對象,向服務器端發送"Hello Server,I am …"的字符串。發送后,同樣可以用in句柄,接收從服務器端的消息。

          6.         利用out對象,發送”byebye”字符串,用以告之服務器端,本次通訊結束。

          7.         在finally從句里,關閉Socket對象,斷開同服務器端的連接。

          7.2.1.3 運行效果演示
          在上述兩部分里,我們分別講述了C
          /S通訊過程中服務器端和客戶端代碼的業務邏輯,下面我們將在集成開發環境里,演示這里通訊流程。

          第一步,選中ServerCode.java代碼,在eclipse的“運行”菜單里,選中“運行方式”
          |1 Java應用程序”的菜單,開啟服務器端的程序。

          開啟服務端程序后,會在eclipse環境下方的控制臺里顯示如下的內容:

          The Server is start: ServerSocket[addr
          =0.0.0.0/0.0.0.0,port=0,localport=3333]

          在這里,由于ServerSocket對象并沒監聽到客戶端的請求,所以addr和后面的port值都是初始值。

          第二步,按同樣的方法,打開ClientCode.java程序,啟動客戶端。啟動以后,將在客戶端的控制臺里看到如下的信息:

          socket 
          = Socket[addr=localhost/127.0.0.1,port=3333,localport=1326]

          Hello Server,I am Mike

          close the Client socket and the io.

          從中可以看到,在第一行里,顯示客戶端Socket對象連接的IP地址和端口號,在第二行里,可以到到客戶端向服務器端發送的字符串,而在第三行里,可以看到通訊結束后,客戶端關閉連接Socket和IO對象的提示語句。

          第三步,在eclipse下方的控制臺里,切換到ServerCode服務端的控制臺提示信息里,我們可以看到服務器端在接收到客戶端連接請求后的響應信息。

            響應的信息如下所示:

          The Server is start: ServerSocket[addr
          =0.0.0.0/0.0.0.0,port=0,localport=3333]

          Accept the Client: Socket[addr
          =/127.0.0.1,port=1327,localport=3333]

          In Server reveived the info: Hello Server,I am Mike

          close the Server socket and the io.

          其中,第一行是啟動服務器程序后顯示的信息。在第二行里,顯示從客戶端發送的連接請求的各項參數。在第三行里,顯示了從客戶端發送過來的字符串。在第四行里,顯示了關閉服務器端ServerSocket和IO對象的提示信息。從中我們可以看出在服務器端里accept阻塞和繼續運行的這個過程。

          通過上述的操作,我們可以詳細地觀察到C
          /S通訊的全部流程,請大家務必要注意:一定要先開啟服務器端的程序再開啟客戶端,如果這個步驟做反的話,客戶端程序會應找不到服務器端而報異常。

          7.2.2使用套接字連接多個客戶機    
          在7.1的代碼里,客戶端和服務器之間只有一個通訊線程,所以它們之間只有一條Socket信道。

          如果我們在通過程序里引入多線程的機制,可讓一個服務器端同時監聽并接收多個客戶端的請求,并同步地為它們提供通訊服務。

          基于多線程的通訊方式,將大大地提高服務器端的利用效率,并能使服務器端能具備完善的服務功能。

          7.2.2.1 開發客戶端代碼
          我們可以按以下的步驟開發基于多線程的服務器端的代碼。

          第一步,在3.2里創建的“TCPSocket”項目里,新建一個名為ThreadServer.java的代碼文件,創建文件的方式大家可以參照3.2部分的描述。首先編寫package和import部分的代碼,用來打包和引入包文件,如下所示:

          package tcp;

          import java.io.*;

          import java.net.*;

          第二步,由于我們在服務器端引入線程機制,所以我們要編寫線程代碼的主體執行類ServerThreadCode,這個類的代碼如下所示:

          class ServerThreadCode extends Thread 

          {

                 
          //客戶端的socket

                 
          private Socket clientSocket;

                 
          //IO句柄

                 
          private BufferedReader sin;

                 
          private PrintWriter sout;    

                 
          //默認的構造函數

                 
          public ServerThreadCode()

                 {}  

                 
          public ServerThreadCode(Socket s) throws IOException 

                 {

                        clientSocket 
          = s;            

                        
          //初始化sin和sout的句柄

                        sin 
          = new BufferedReader(new InputStreamReader(clientSocket

                                      .getInputStream()));

                  sout 
          = new PrintWriter(new BufferedWriter(new OutputStreamWriter(

                                      clientSocket.getOutputStream())), 
          true);             

                        
          //開啟線程

                        start(); 

                 }

                 
          //線程執行的主體函數

                 
          public void run() 

                 {

                        
          try 

                        {

                               
          //用循環來監聽通訊內容

                               
          for(;;) 

                               {

                          String str 
          = sin.readLine();

                                      
          //如果接收到的是byebye,退出本次通訊

                                      
          if (str.equals("byebye"))

                                      {     

                                             
          break;

                                      }     

                                      System.out.println(
          "In Server reveived the info: " + str);

                                      sout.println(str);

                               }

                               System.out.println(
          "closing the server socket!");

                        } 

                  
          catch (IOException e) 

                        {

                               e.printStackTrace();

                        } 

                        
          finally 

                        {

                               System.out.println(
          "close the Server socket and the io.");

                               
          try 

                      {

                                      clientSocket.close();

                               } 

                               
          catch (IOException e) 

                               {

                                      e.printStackTrace();

                               }

                        }

                 }

          }

          這個類的業務邏輯說明如下:

          1.         這個類通過繼承Thread類來實現線程的功能,也就是說,在其中的run方法里,定義了該線程啟動后要執行的業務動作。

          2.         這個類提供了兩種類型的重載函數。在參數類型為Socket的構造函數里, 通過參數,初始化了本類里的Socket對象,同時實例化了兩類IO對象。在此基礎上,通過start方法,啟動定義在run方法內的本線程的業務邏輯。

          3.         在定義線程主體動作的run方法里,通過一個for(;;)類型的循環,根據IO句柄,讀取從Socket信道上傳輸過來的客戶端發送的通訊信息。如果得到的信息為“byebye”,則表明本次通訊結束,退出for循環。

          4.         catch從句將處理在try語句里遇到的IO錯誤等異常,而在finally從句里,將在通訊結束后關閉客戶端的Socket句柄。

          上述的線程主體代碼將會在ThreadServer類里被調用。

          第三步,編寫服務器端的主體類ThreadServer,代碼如下所示:

          public class ThreadServer 

          {

                 
          //端口號

                 
          static final int portNo = 3333;

                 
          public static void main(String[] args) throws IOException 

                 {

                        
          //服務器端的socket

                        ServerSocket s 
          = new ServerSocket(portNo);

                        System.out.println(
          "The Server is start: " + s);      

                        
          try 

                        {

                               
          for(;;)                          

                               {

                            
          //阻塞,直到有客戶端連接

                                      Socket socket 
          = s.accept();

                                      
          //通過構造函數,啟動線程

                                  
          new ServerThreadCode(socket);

                               }

                        }

                     
          finally 

                        {

                               s.close();

                        }

                 }

          }

          這段代碼的主要業務邏輯說明如下:

          1.         首先定義了通訊所用的端口號,為3333。

          2.         在main函數里,根據端口號,創建一個ServerSocket類型的服務器端的Socket,用來同客戶端通訊。

          3.         在for(;;)的循環里,調用accept方法,監聽從客戶端請求過來的socket,請注意這里又是一個阻塞。當客戶端有請求過來時,將通過ServerThreadCode的構造函數,創建一個線程類,用來接收客戶端發送來的字符串。在這里我們可以再一次觀察ServerThreadCode類,在其中,這個類通過構造函數里的start方法,開啟run方法,而在run方法里,是通過sin對象來接收字符串,通過sout對象來輸出。

          4.         在finally從句里,關閉服務器端的Socket,從而結束本次通訊。

          7.2.2.2 開發客戶端代碼
          我們可以按以下的步驟,編寫的基于多線程的客戶端代碼。

          第一步,在 “TCPSocket”項目里,新建一個名為ThreadClient.java的代碼文件。同樣是編寫package和import部分的代碼,用來打包和引入包文件,如下所示:

          package tcp;

          import java.net.*;

          import java.io.*;

          第二步,編寫線程執行主體的ClientThreadCode類,同樣,這個類通過繼承Thread來實現線程的功能。

          class ClientThreadCode extends Thread 

          {

            
          //客戶端的socket

            
          private Socket socket;    

            
          //線程統計數,用來給線程編號

            
          private static int cnt = 0;

            
          private int clientId = cnt++;

            
          private BufferedReader in;

            
          private PrintWriter out;

            
          //構造函數

            
          public ClientThreadCode(InetAddress addr) 

            {

              
          try 

              {

                socket 
          = new Socket(addr, 3333);

              }

              
          catch(IOException e) 

              {

                    e.printStackTrace();

              }

              
          //實例化IO對象

          try 

              {    

                in 
          = new BufferedReader(

                       
          new InputStreamReader(socket.getInputStream()));    

                 out 
          = new PrintWriter(

                         
          new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);

                  
          //開啟線程

                  start();

               } 

               
          catch(IOException e) 

               {

                  
          //出現異常,關閉socket 

                    
          try 

                    {

                      socket.close();

                  } 

                    
          catch(IOException e2) 

                    {

                        e2.printStackTrace();       

                    }

               }

            }  

            
          //線程主體方法

          public void run() 

            {

              
          try 

              {

                out.println(
          "Hello Server,My id is " + clientId );

                String str 
          = in.readLine();

                System.out.println(str);

                out.println(
          "byebye");

              } 

              
          catch(IOException e) 

              {

                 e.printStackTrace();  

              }

              
          finally 

              {

                
          try 

                {

                  socket.close();

                } 

                
          catch(IOException e) 

                {

                        e.printStackTrace();

                }    

              }

            }

          }

          這個類的主要業務邏輯是:

          1.         在構造函數里, 通過參數類型為InetAddress類型參數和3333,初始化了本類里的Socket對象,隨后實例化了兩類IO對象,并通過start方法,啟動定義在run方法內的本線程的業務邏輯。

          2.         在定義線程主體動作的run方法里,通過IO句柄,向Socket信道上傳輸本客戶端的ID號,發送完畢后,傳輸”byebye”字符串,向服務器端表示本線程的通訊結束。

          3.         同樣地,catch從句將處理在try語句里遇到的IO錯誤等異常,而在finally從句里,將在通訊結束后關閉客戶端的Socket句柄。

          第三步,編寫客戶端的主體代碼,在這段代碼里,將通過for循環,根據指定的待創建的線程數量,通過ClientThreadCode的構造函數,創建若干個客戶端線程,同步地和服務器端通訊。

          public class ThreadClient 

          {

            
          public static void main(String[] args) 

                
          throws IOException, InterruptedException 

            {

              
          int threadNo = 0;

                 InetAddress addr 
          = 

                 InetAddress.getByName(
          "localhost");

              
          for(threadNo = 0;threadNo<3;threadNo++)

              {

                 
          new ClientThreadCode(addr);

              }

            }

          }

          這段代碼執行以后,在客戶端將會有3個通訊線程,每個線程首先將先向服務器端發送
          "Hello Server,My id is "的字符串,然后發送”byebye”,終止該線程的通訊。

          7.2.2.3 運行效果演示
          接下來,我們來觀察一下基于多線程的C
          /S架構的運行效果。

          第一步,我們先要啟動服務器端的ThreadServer代碼,啟動后,在控制臺里會出現如下的提示信息:

          The Server is start: ServerSocket[addr
          =0.0.0.0/0.0.0.0,port=0,localport=3333]

          上述的提示信息里,我們同樣可以看到,服務器在開啟服務后,會阻塞在accept這里,直到有客戶端請求過來。

          第二步,我們在啟動完服務器后,運行客戶端的ThreadClient.java代碼,運行后,我們觀察服務器端的控制臺,會出現如下的信息:

          The Server is start: ServerSocket[addr
          =0.0.0.0/0.0.0.0,port=0,localport=3333]

          In Server reveived the info: Hello Server,My id is 
          0

          In Server reveived the info: Hello Server,My id is 
          1

          In Server reveived the info: Hello Server,My id is 
          2

          closing the server socket
          !

          close the Server socket and the io.

          closing the server socket
          !

          close the Server socket and the io.

          closing the server socket
          !

          close the Server socket and the io.

          其中,第一行是原來就有,在后面的幾行里,首先將會輸出了從客戶端過來的線程請求信息,比如

          In Server reveived the info: Hello Server,My id is 
          0

          接下來則會顯示關閉Server端的IO和Socket的提示信息。

          這里,請大家注意,由于線程運行的不確定性,從第二行開始的打印輸出語句的次序是不確定的。但是,不論輸出語句的次序如何變化,我們都可以從中看到,客戶端有三個線程請求過來,并且,服務器端在處理完請求后,會關閉Socker和IO。

          第三步,當我們運行完ThreadClient.java的代碼后,并切換到ThreadClient.java的控制臺,我們可以看到如下的輸出:

          Hello Server,My id is 
          0

          Hello Server,My id is 
          2

          Hello Server,My id is 
          1

          這說明在客戶端開啟了3個線程,并利用這3個線程,向服務器端發送字符串。

          而在服務器端,用accept方法分別監聽到了這3個線程,并與之對應地也開了3個線程與之通訊。

          7.2.3 UDP協議與傳輸數據報文    
          UDP協議一般應用在 “群發信息”的場合,所以它更可以利用多線程的機制,實現多信息的同步發送。

          為了改善代碼的架構,我們更可以把一些業務邏輯的動作抽象成方法,并封裝成類,這樣,基于UDP功能的類就可以在其它應用項目里被輕易地重用。

          7.2.3.1 開發客戶端代碼
          如果我們把客戶端的所有代碼都寫在一個文件中,那么代碼的功能很有可能都聚集在一個方法力,代碼的可維護性將會變得很差。

          所以我們專門設計了ClientBean類,在其中封裝了客戶端通訊的一些功能方法,在此基礎上,通過UDPClient.java文件,實現UDP客戶端的功能。

          另外,在這里以及以后的代碼里,我們不再詳細講述用Eclipse開發和運行Java程序的方法,而是重點講述Java代碼的業務邏輯和主要工作流程。

          首先,我們可以按如下的步驟,設計ClientBean這個類。通過import語句,引入所用到的類庫,代碼如下所示。

          import java.io.IOException;

          import java.net.DatagramPacket;

          import java.net.DatagramSocket;

          import java.net.InetAddress;

          import java.net.SocketException;

          import java.net.UnknownHostException;

          第二,定義ClientBean所用到的變量,并給出針對這些變量操作的get和set類型的方法,代碼如下所示。

          //描述UDP通訊的DatagramSocket對象

          private  DatagramSocket ds;

          //用來封裝通訊字符串

          private  byte buffer[];

          //客戶端的端口號

          private  int clientport ;

          //服務器端的端口號

          private  int serverport;

          //通訊內容

          private  String content;

          //描述通訊地址

          private  InetAddress ia;

          //以下是各屬性的Get和Set類型方法

          public byte[] getBuffer() 

          {

                 
          return buffer;

          }

          public void setBuffer(byte[] buffer)

          {

                 
          this.buffer = buffer;

          }

          public int getClientport()

          {

          return clientport;

          }

          public void setClientport(int clientport) 

          {

                 
          this.clientport = clientport;

          }

          public String getContent() 

          {

                 
          return content;

          }

          public void setContent(String content)

          {

                 
          this.content = content;

          }

          public DatagramSocket getDs() 

          {

                 
          return ds;

          }

          public void setDs(DatagramSocket ds)

          {

                 
          this.ds = ds;

          }

          public InetAddress getIa() 

          {

                 
          return ia;

          }

          public void setIa(InetAddress ia) 

          {

                 
          this.ia = ia;

          }

          public int getServerport() 

          {

                 
          return serverport;

          }

          public void setServerport(int serverport) 

          {

              
          this.serverport = serverport;

          }

          在上述的代碼里,我們定義了描述用來實現UDP通訊的DatagramSocket類型對象ds,描述客戶端和服務器端的端口號clientport和serverport,用于描述通訊信息的buffer和content對象,其中,buffer對象是byte數組類型的,可通過UDP的數據報文傳輸,而content是String類型的,在應用層面表示用戶之間的通訊內容,另外還定義了InetAddress類型的ia變量,用來封裝通訊地址信息。

          在隨后定義的一系列get和set方法里,給出了設置和獲取上述變量的方法。

          第三,編寫該類的構造函數,代碼如下所示。

          public ClientBean() throws SocketException, UnknownHostException

          {

                 buffer 
          = new byte[1024];

                 clientport 
          = 1985;

                 serverport 
          = 1986;

                 content 
          = "";

                 ds 
          = new DatagramSocket(clientport);

                 ia 
          = InetAddress.getByName("localhost");

          }

          在這個構造函數里,我們給各變量賦予了初始值,其中分別設置了客戶端和服務器端的端口號分別為1985和1985,設置了通訊連接地址為本地,并根據客戶端的端口號初始化了DatagramSocket對象。

          當程序員初始化ClientBean類時,這段構造函數會自動執行,完成設置通訊各參數等工作。

          第四,編寫向服務器端發送消息的sendToServer方法,代碼如下所示。

          public void sendToServer() throws IOException

          {

                 buffer 
          = content.getBytes();

                 ds.send(
          new DatagramPacket(buffer,content.length(),ia,serverport));

          }

          在這段代碼里,根據String類型的表示通訊信息的content變量,初始化UDP數據報文,即DatagramPacket對象,并通過調用DatagramSocket類型對象的send方法,發送該UDP報文。

          縱觀ClientBean類,我們可以發現在其中封裝了諸如通訊端口、通訊內容和通訊報文等對象以及以UDP方式發送信息的sendToServer方法。所以,在UDPClient類里,可以直接調用其中的接口,方便地實現通訊功能。

          其次,我們可以按如下的步驟,設計UDPClient這個類。

          第一步,通過import語句,引入所用到的類庫,代碼如下所示。

          import java.io.BufferedReader;

          import java.io.IOException;

          import java.io.InputStreamReader;

          第二步,編寫線程相關的代碼。

          由于我們要在UDP客戶端里通過多線程的機制,同時開多個客戶端,向服務器端發送通訊內容,所以我們的UDPClient類必須要實現Runnable接口,并在其中覆蓋掉Runnable接口里的run方法。定義類和實現run方法的代碼如下所示。

          public class UDPClient implements Runnable

          {

          public static String content;

          public static ClientBean client;

          public void run()

          {

                 
          try

          {

                        client.setContent(content);

                        client.sendToServer();

                 }

          catch(Exception ex)

          {

                        System.err.println(ex.getMessage());

                 }

          }
          //end of run

          //main 方法

            
          //

           }

          在上述代碼的run方法里,我們主要通過了ClientBean類里封裝的方法,設置了content內容,并通過了sentToServer方法,將content內容以數據報文的形式發送到服務器端。

          一旦線程被開啟,系統會自動執行定義在run方法里的動作。

          第三步,編寫主方法。在步驟(
          2)里的//main方法注釋的位置,我們可以插入UDPClient類的main方法代碼,具體如下所示。

          public static void main(String args[]) throws IOException

          {

                 BufferedReader br 
          = new BufferedReader(new InputStreamReader(System.in));

                 client 
          = new ClientBean();

                 System.out.println(
          "客戶端啟動");

                 
          while(true)

          {

                        
          //接收用戶輸入

          content 
          = br.readLine();

                        
          //如果是end或空,退出循環

          if(content==null||content.equalsIgnoreCase("end")||content.equalsIgnoreCase(""))

          {

                               
          break;

                        }

                        
          //開啟新線程,發送消息

          new Thread(new UDPClient()).start();

                 }            

          }

          這段代碼的主要業務邏輯是,首先初始化了BufferedReader類型的br對象,該對象可以接收從鍵盤輸入的字符串。隨后啟動一個while(
          true)的循環,在這個循環體里,接收用戶從鍵盤的輸入,如果用戶輸入的字符串不是“end”,或不是為空,則開啟一個UDPClient類型的線程,并通過定義在run方法里的線程主體動作,發送接收到的消息。如果在循環體里,接收到“end”或空字符,則通過break語句,退出循環。

          從上述代碼里,我們可以看出,對于每次UDP發送請求,UDPClient類都將會啟動一個線程來發送消息。

          7.2.3.2 開發客戶端代碼
          同樣,我們把服務器端所需要的一些通用方法以類的形式封裝,而在UDP的服務器端,通過調用封裝在ServerBean類里的方法來完成信息的接收工作。

          首先,我們可以按如下的步驟,設計ServerBean類的代碼。

          第一步,通過import語句,引入所用到的類庫,代碼如下所示。

          import java.io.IOException;

          import java.net.DatagramPacket;

          import java.net.DatagramSocket;

          import java.net.InetAddress;

          import java.net.SocketException;

          import java.net.UnknownHostException;

          第二步,同樣定義ServerBean類里用到的變量,并給出針對這些變量操作的get和set類型的方法。由于這里的代碼和ClientBean類里的非常相似,所以不再贅述,代碼部分大家可以參考光盤上。

          第三步,編寫該類的構造函數,在這個構造函數里,給該類里的一些重要屬性賦了初值,代碼如下所示。

          public ServerBean() throws SocketException, UnknownHostException

          {

                 buffer 
          = new byte[1024];

                 clientport 
          = 1985;

                 serverport 
          = 1986;

                 content 
          = "";

                 ds 
          = new DatagramSocket(serverport);

                 ia 
          = InetAddress.getByName("localhost");

          }

          從中我們可以看到,在UDP的服務端里,為了同客戶端對應,所以同樣把clientport和serverport值設置為1985和1986,同時初始化了DatagramSocket對象,并把服務器的地址也設置成本地。

          第四,編寫實現監聽客戶端請求的listenClient方法,代碼如下所示。

          public void listenClient() throws IOException

          {

                 
          //在循環體里接收消息

          while(true)

          {

                  
          //初始化DatagramPacket類型的變量

          DatagramPacket dp 
          = new DatagramPacket(buffer,buffer.length);

                        
          //接收消息,并把消息通過dp參數返回

          ds.receive(dp);

                        content 
          = new String(dp.getData(),0,dp.getLength());

                        
          //打印消息

          print();

                 }

          }

          在這個方法里,構造了一個while(
          true)的循環,在這個循環體內部,調用了封裝在DatagramSocket類型里的receive方法,接收客戶端發送過來的UDP報文,并通過print方法,把報文內容打印出來。

          而print方法的代碼比較簡單,只是通過輸出語句,打印報文里的字符串。

          public void print()

          {

                 System.out.println(content);

          }

          而UDP通訊的服務器端代碼相對簡單,以下是UDPServer類的全部代碼。

          import java.io.IOException;

          public class UDPServer

          {

                 
          public static void main(String args[]) throws IOException

          {

                        System.out.println(
          "服務器端啟動");

                        
          //初始化ServerBean對象

          ServerBean server 
          = new ServerBean();

                        
          //開啟監聽程序

          server.listenClient();

                 }

          }

          從上述代碼里,我們可以看到,在UDP的服務器端里,主要通過ServerBean類里提供的listenClient方法,監聽從客戶端發送過來的UDP報文,并通過解析得到其中包含的字符串,隨后輸出。

          7.3.2.3 開發客戶端代碼
          由于我們已經講述過通過Eclipse查看代碼運行結果的詳細步驟,所以這里我們將直接通過命令行的方式,通過javac和java等命令,查看基于多線程UDP通訊的演示效果。

          1.         首先我們把剛才編寫好的四段java代碼(即ClientBean.java、UDPClient.java、ServerBean.java和UDPServer.java)放到D盤下的work目錄下(如果沒有則新建)。

          2.         點擊“開始菜單”|“運行”選項,并在“運行程序”的對話框里輸入”cmd”命令,進入DOS命令界面,并進入到D:\work這個目錄里。

          3.         如果大家已經按照第一章的說明,成功地配置好關于java的path和classpath環境變量,在這里可以直接運行javac *.java命令,編譯這四個.java文件,編譯后,會在D:\work目錄下產生同四個java文件相對應的.class文件。

          4.         在這個命令窗口里運行java UDPServer命令,通過運行UDPServer代碼,開啟UDP服務器端程序,開啟后,會出現如圖7-3所示的信息。



          圖7
          -3啟動UDP服務端后的效果

          5.         在出現上圖的效果后,別關閉這個命令窗口,按步驟(2)里說明的流程,新開啟一個DOS命令窗口,并同樣進入到D:\work這個目錄下。

          6.         在新窗口里輸入java UDPClient,開啟UDP客戶端程序。開啟后,可通過鍵盤向服務器端輸入通訊字符串,這些字符串將會以數據報文的形式發送到服務器端。

          在圖7
          -4里,演示了UDP客戶端向服務器端發送消息的效果。



          圖7
          -4 UDP客戶端發送消息的效果

          每當我們在客戶端發送一條消息,服務器端會收到并輸出這條消息,從代碼里我們可以得知,每條消息是通過為之新開啟的線程發送到服務器端的。

          如果我們在客戶端輸入”end”或空字符串,客戶端的UDPClient代碼會退出。在圖7
          -5里演示了UDP服務器端接收并輸出通訊字符串的效果。



          圖7
          -5 UDP服務器端接收到消息的效果

          7.         由于UDPServer.java代碼里,我們通過一個while(true)的循環來監聽客戶端的請求,所以當程序運行結束后,可通過Ctrl+C的快捷鍵的方式退出這段程序。
          posted on 2010-07-17 13:39 艾波 閱讀(75630) 評論(6)  編輯  收藏 所屬分類: Java

          評論

          # re: Java網絡socket編程詳解[未登錄] 2012-03-07 11:59 as





































































































          as






            回復  更多評論
            

          # re: Java網絡socket編程詳解[未登錄] 2013-04-18 11:50 a




          a
































































































































































































































































































          a









            回復  更多評論
            

          # re: Java網絡socket編程詳解[未登錄] 2013-04-18 11:59 javaer
          寫得很好,以后可以用來做參考、copy  回復  更多評論
            

          # re: Java網絡socket編程詳解[未登錄] 2014-09-16 19:34 111
          非常好的教程  回復  更多評論
            

          # re: Java網絡socket編程詳解 2014-10-25 18:38 zzg
          對我這個大齡java初學者,這篇文章簡直是很好的教材!  回復  更多評論
            

          # re: Java網絡socket編程詳解[未登錄] 2015-10-02 15:57 Lee
          使用套接字連接多個客戶機 這個運行部正確啊  回復  更多評論
            

          主站蜘蛛池模板: 鹤岗市| 九江县| 巴林左旗| 思南县| 来宾市| 清涧县| 固阳县| 基隆市| 广汉市| 怀远县| 景泰县| 邯郸市| 德庆县| 凭祥市| 阿克苏市| 和顺县| 井陉县| 徐闻县| 师宗县| 静宁县| 富源县| 小金县| 鄢陵县| 佛山市| 余庆县| 东至县| 吴江市| 天水市| 郎溪县| 本溪| 育儿| 富民县| 湛江市| 保德县| 德化县| 陆川县| 永仁县| 沛县| 谷城县| 南溪县| 兴业县|