SOCKET編程-java

本文轉載來至http://www.blogjava.net/Reg/archive/2010/07/17/326392.html
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流對象,向服務器發送數據流。

  1. 通訊完成後,關閉打開的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服務端後的效果

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

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

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

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

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

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

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

  1. 由於UDPServer.java代碼裏,我們通過一個while(true)的循環來監聽客戶端的請求,所以當程序運行結束後,可通過Ctrl+C的快捷鍵的方式退出這段程序。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章