<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    悟心

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

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

    7.2.1使用套接字實現(xiàn)基于TCP協(xié)議的服務(wù)器和客戶機(jī)程序    
    依據(jù)TCP協(xié)議,在C
    /S架構(gòu)的通訊過程中,客戶端和服務(wù)器的Socket動作如下:

    客戶端:

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

    2.調(diào)用connect方法,連接到服務(wù)器上。

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

    4.利用Socket提供的getInputStream和getOutputStream方法,通過IO流對象,向服務(wù)器發(fā)送數(shù)據(jù)流。

    5. 通訊完成后,關(guān)閉打開的IO對象和Socket。

    服務(wù)器:

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

    2.調(diào)用ServerSocket的accept方法,開始監(jiān)聽連接從端口上發(fā)來的連接請求。   

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

    通訊完成后,關(guān)閉打開的流和Socket對象。

    7.2.1.1 開發(fā)客戶端代碼
    根據(jù)上面描述的通訊流程,我們可以按如下的步驟設(shè)計服務(wù)器端的代碼。

        第一步,依次點(diǎn)擊Eclipse環(huán)境里的“文件”
    |“新建”|“項目”選項,進(jìn)入“新建項目”的向?qū)υ捒颍谄渲羞x中“Java項目”,點(diǎn)擊“下一步”按鈕,在隨后彈出的對話框里,在其中的“項目名”一欄里,輸入項目名“TCPSocket”,其它的選項目

    選擇系統(tǒng)默認(rèn)值,再按“完成”按鈕,結(jié)束創(chuàng)建Java項目的動作。

       第二步,完成創(chuàng)建項目后,選中集成開發(fā)環(huán)境左側(cè)的項目名“TCPSocket”,點(diǎn)擊右鍵,在隨后彈出的菜單里依次選擇“新建”
    !“類”的選項,創(chuàng)建服務(wù)器類的代碼。

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

        第三步,在生成的代碼里,編寫引入Java包的代碼,只有當(dāng)我們引入這些包后,我們才能調(diào)用這些包里提供的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;

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

      
    public class ServerCode 

    {

           
    // 設(shè)置端口號

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

                         
    //設(shè)置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();

                  }

        }

    }

    這段代碼的主要業(yè)務(wù)邏輯是:

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

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

    3.         調(diào)用ServerSocket對象的accept方法,監(jiān)聽從客戶端的連接請求。當(dāng)完成調(diào)用accept方法后,整段服務(wù)器端代碼將回阻塞在這里,直到客戶端發(fā)來connect請求。

    4.         當(dāng)客戶端發(fā)來connect請求,或是通過構(gòu)造函數(shù)直接把客戶端的Socket對象連接到服務(wù)器端后,阻塞于此的代碼將會繼續(xù)運(yùn)行。此時服務(wù)器端將會根據(jù)accept方法的執(zhí)行結(jié)果,用一個Socket對象來描述客戶端的連接句柄。

    5.         創(chuàng)建兩個名為in和out的對象,用來傳輸和接收通訊時的數(shù)據(jù)流。

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

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

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

    第一,在TCPSocket項目下的tcp包下,創(chuàng)建一個名為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

    {

                  
    // 設(shè)置連接地址類,連接本地

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

                  
    //要對應(yīng)服務(wù)器端的3333端口號

                  Socket socket 
    = new Socket(addr, portNo);

                  
    try

    {

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

                         
    // 設(shè)置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();

            }

           }

    }

    上述客戶端代碼的主要業(yè)務(wù)邏輯是:

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

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

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

    4.         同樣地創(chuàng)建in和out兩類IO句柄,用來向服務(wù)器端發(fā)送和接收數(shù)據(jù)流。

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

    6.         利用out對象,發(fā)送”byebye”字符串,用以告之服務(wù)器端,本次通訊結(jié)束。

    7.         在finally從句里,關(guān)閉Socket對象,斷開同服務(wù)器端的連接。

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

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

    開啟服務(wù)端程序后,會在eclipse環(huán)境下方的控制臺里顯示如下的內(nèi)容:

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

    在這里,由于ServerSocket對象并沒監(jiān)聽到客戶端的請求,所以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地址和端口號,在第二行里,可以到到客戶端向服務(wù)器端發(fā)送的字符串,而在第三行里,可以看到通訊結(jié)束后,客戶端關(guān)閉連接Socket和IO對象的提示語句。

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

      響應(yīng)的信息如下所示:

    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.

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

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

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

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

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

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

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

    package tcp;

    import java.io.*;

    import java.net.*;

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

    class ServerThreadCode extends Thread 

    {

           
    //客戶端的socket

           
    private Socket clientSocket;

           
    //IO句柄

           
    private BufferedReader sin;

           
    private PrintWriter sout;    

           
    //默認(rèn)的構(gòu)造函數(shù)

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

           }

           
    //線程執(zhí)行的主體函數(shù)

           
    public void run() 

           {

                  
    try 

                  {

                         
    //用循環(huán)來監(jiān)聽通訊內(nèi)容

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

                         }

                  }

           }

    }

    這個類的業(yè)務(wù)邏輯說明如下:

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

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

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

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

    上述的線程主體代碼將會在ThreadServer類里被調(diào)用。

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

    public class ThreadServer 

    {

           
    //端口號

           
    static final int portNo = 3333;

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

           {

                  
    //服務(wù)器端的socket

                  ServerSocket s 
    = new ServerSocket(portNo);

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

                  
    try 

                  {

                         
    for(;;)                          

                         {

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

                                Socket socket 
    = s.accept();

                                
    //通過構(gòu)造函數(shù),啟動線程

                            
    new ServerThreadCode(socket);

                         }

                  }

               
    finally 

                  {

                         s.close();

                  }

           }

    }

    這段代碼的主要業(yè)務(wù)邏輯說明如下:

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

    2.         在main函數(shù)里,根據(jù)端口號,創(chuàng)建一個ServerSocket類型的服務(wù)器端的Socket,用來同客戶端通訊。

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

    4.         在finally從句里,關(guān)閉服務(wù)器端的Socket,從而結(jié)束本次通訊。

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

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

    package tcp;

    import java.net.*;

    import java.io.*;

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

    class ClientThreadCode extends Thread 

    {

      
    //客戶端的socket

      
    private Socket socket;    

      
    //線程統(tǒng)計數(shù),用來給線程編號

      
    private static int cnt = 0;

      
    private int clientId = cnt++;

      
    private BufferedReader in;

      
    private PrintWriter out;

      
    //構(gòu)造函數(shù)

      
    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) 

         {

            
    //出現(xiàn)異常,關(guān)閉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();

          }    

        }

      }

    }

    這個類的主要業(yè)務(wù)邏輯是:

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

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

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

    第三步,編寫客戶端的主體代碼,在這段代碼里,將通過for循環(huán),根據(jù)指定的待創(chuàng)建的線程數(shù)量,通過ClientThreadCode的構(gòu)造函數(shù),創(chuàng)建若干個客戶端線程,同步地和服務(wù)器端通訊。

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

        }

      }

    }

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

    7.2.2.3 運(yùn)行效果演示
    接下來,我們來觀察一下基于多線程的C
    /S架構(gòu)的運(yùn)行效果。

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

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

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

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

    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

    接下來則會顯示關(guān)閉Server端的IO和Socket的提示信息。

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

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

    Hello Server,My id is 
    0

    Hello Server,My id is 
    2

    Hello Server,My id is 
    1

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

    而在服務(wù)器端,用accept方法分別監(jiān)聽到了這3個線程,并與之對應(yīng)地也開了3個線程與之通訊。

    7.2.3 UDP協(xié)議與傳輸數(shù)據(jù)報文    
    UDP協(xié)議一般應(yīng)用在 “群發(fā)信息”的場合,所以它更可以利用多線程的機(jī)制,實現(xiàn)多信息的同步發(fā)送。

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

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

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

    另外,在這里以及以后的代碼里,我們不再詳細(xì)講述用Eclipse開發(fā)和運(yùn)行Java程序的方法,而是重點(diǎn)講述Java代碼的業(yè)務(wù)邏輯和主要工作流程。

    首先,我們可以按如下的步驟,設(shè)計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 ;

    //服務(wù)器端的端口號

    private  int serverport;

    //通訊內(nèi)容

    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;

    }

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

    在隨后定義的一系列g(shù)et和set方法里,給出了設(shè)置和獲取上述變量的方法。

    第三,編寫該類的構(gòu)造函數(shù),代碼如下所示。

    public ClientBean() throws SocketException, UnknownHostException

    {

           buffer 
    = new byte[1024];

           clientport 
    = 1985;

           serverport 
    = 1986;

           content 
    = "";

           ds 
    = new DatagramSocket(clientport);

           ia 
    = InetAddress.getByName("localhost");

    }

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

    當(dāng)程序員初始化ClientBean類時,這段構(gòu)造函數(shù)會自動執(zhí)行,完成設(shè)置通訊各參數(shù)等工作。

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

    public void sendToServer() throws IOException

    {

           buffer 
    = content.getBytes();

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

    }

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

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

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

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

    import java.io.BufferedReader;

    import java.io.IOException;

    import java.io.InputStreamReader;

    第二步,編寫線程相關(guān)的代碼。

    由于我們要在UDP客戶端里通過多線程的機(jī)制,同時開多個客戶端,向服務(wù)器端發(fā)送通訊內(nèi)容,所以我們的UDPClient類必須要實現(xiàn)Runnable接口,并在其中覆蓋掉Runnable接口里的run方法。定義類和實現(xiàn)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類里封裝的方法,設(shè)置了content內(nèi)容,并通過了sentToServer方法,將content內(nèi)容以數(shù)據(jù)報文的形式發(fā)送到服務(wù)器端。

    一旦線程被開啟,系統(tǒng)會自動執(zhí)行定義在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或空,退出循環(huán)

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

    {

                         
    break;

                  }

                  
    //開啟新線程,發(fā)送消息

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

           }            

    }

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

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

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

    首先,我們可以按如下的步驟,設(shè)計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類里的非常相似,所以不再贅述,代碼部分大家可以參考光盤上。

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

    public ServerBean() throws SocketException, UnknownHostException

    {

           buffer 
    = new byte[1024];

           clientport 
    = 1985;

           serverport 
    = 1986;

           content 
    = "";

           ds 
    = new DatagramSocket(serverport);

           ia 
    = InetAddress.getByName("localhost");

    }

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

    第四,編寫實現(xiàn)監(jiān)聽客戶端請求的listenClient方法,代碼如下所示。

    public void listenClient() throws IOException

    {

           
    //在循環(huán)體里接收消息

    while(true)

    {

            
    //初始化DatagramPacket類型的變量

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

                  
    //接收消息,并把消息通過dp參數(shù)返回

    ds.receive(dp);

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

                  
    //打印消息

    print();

           }

    }

    在這個方法里,構(gòu)造了一個while(
    true)的循環(huán),在這個循環(huán)體內(nèi)部,調(diào)用了封裝在DatagramSocket類型里的receive方法,接收客戶端發(fā)送過來的UDP報文,并通過print方法,把報文內(nèi)容打印出來。

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

    public void print()

    {

           System.out.println(content);

    }

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

    import java.io.IOException;

    public class UDPServer

    {

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

    {

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

                  
    //初始化ServerBean對象

    ServerBean server 
    = new ServerBean();

                  
    //開啟監(jiān)聽程序

    server.listenClient();

           }

    }

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

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

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

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

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

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



    圖7
    -3啟動UDP服務(wù)端后的效果

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

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

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



    圖7
    -4 UDP客戶端發(fā)送消息的效果

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

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



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

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

    評論

    # re: Java網(wǎng)絡(luò)socket編程詳解[未登錄] 2012-03-07 11:59 as





































































































    as






      回復(fù)  更多評論
      

    # re: Java網(wǎng)絡(luò)socket編程詳解[未登錄] 2013-04-18 11:50 a




    a
































































































































































































































































































    a









      回復(fù)  更多評論
      

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

    # re: Java網(wǎng)絡(luò)socket編程詳解[未登錄] 2014-09-16 19:34 111
    非常好的教程  回復(fù)  更多評論
      

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

    # re: Java網(wǎng)絡(luò)socket編程詳解[未登錄] 2015-10-02 15:57 Lee
    使用套接字連接多個客戶機(jī) 這個運(yùn)行部正確啊  回復(fù)  更多評論
      

    主站蜘蛛池模板: 中文字幕亚洲综合久久菠萝蜜| 日本成年免费网站| 亚洲精品福利视频| 久久久久亚洲av无码专区导航| 四虎影永久在线高清免费| 免费在线精品视频| 无码中文字幕av免费放| 色婷婷7777免费视频在线观看| 永久免费的网站在线观看| 女人18毛片水真多免费播放| 日日AV拍夜夜添久久免费| 亚洲不卡无码av中文字幕| 亚洲综合精品香蕉久久网| 中文字幕亚洲综合久久2| 亚洲乱码一二三四区麻豆| 亚洲爆乳大丰满无码专区| 亚洲妓女综合网99| 国产精品亚洲а∨天堂2021| 久久久久女教师免费一区| 18勿入网站免费永久| 国产精品久久香蕉免费播放| 国产偷国产偷亚洲高清日韩 | 亚洲六月丁香婷婷综合| 国产亚洲精品美女| 久久精品免费视频观看| 午夜电影免费观看| 亚洲成人免费在线| 国产精品亚洲小说专区| 99精品视频免费观看| 国产午夜无码视频免费网站| 亚洲成a人片在线观看日本| 亚洲va在线va天堂va不卡下载| 亚洲无码一区二区三区| 无码人妻一区二区三区免费看| 日韩毛片无码永久免费看| 久久av无码专区亚洲av桃花岛| 色屁屁www影院免费观看视频| 国产成人yy免费视频| 亚洲乱码无码永久不卡在线| 亚洲色欲啪啪久久WWW综合网| 久久午夜夜伦鲁鲁片无码免费|