Java學習

          java,spring,structs,hibernate,jsf,ireport,jfreechart,jasperreport,tomcat,jboss -----本博客已經(jīng)搬家了,新的地址是 http://www.javaly.cn 如果有對文章有任何疑問或者有任何不懂的地方,歡迎到www.javaly.cn (Java樂園)指出,我會盡力幫助解決。一起進步

           

          網(wǎng)絡編程技術3

              為了一步一步的掌握網(wǎng)絡編程,下面再研究網(wǎng)絡編程中的兩個基本問題,通過解決這兩個問題將對網(wǎng)絡編程的認識深入一層。

          1、如何復用Socket連接?

          在前面的示例中,客戶端中建立了一次連接,只發(fā)送一次數(shù)據(jù)就關閉了,這就相當于撥打電話時,電話打通了只對話一次就關閉了,其實更加常用的應該是撥通一次電話以后多次對話,這就是復用客戶端連接。

          那 么如何實現(xiàn)建立一次連接,進行多次數(shù)據(jù)交換呢?其實很簡單,建立連接以后,將數(shù)據(jù)交換的邏輯寫到一個循環(huán)中就可以了。這樣只要循環(huán)不結束則連接就不會被關 閉。按照這種思路,可以改造一下上面的代碼,讓該程序可以在建立連接一次以后,發(fā)送三次數(shù)據(jù),當然這里的次數(shù)也可以是多次,示例代碼如下:

          package tcp;

          import java.io.*;

          import java.net.*;

          /**

           * 復用連接的Socket客戶端

           * 功能為:發(fā)送字符串“Hello”到服務器端,并打印出服務器端的反饋

           */

          public class MulSocketClient {

                   public static void main(String[] args) {

                             Socket socket = null;

                             InputStream is = null;

                             OutputStream os = null;

                             //服務器端IP地址

                             String serverIP = "127.0.0.1";

                             //服務器端端口號

                             int port = 10000;

                             //發(fā)送內容

                             String data[] ={"First","Second","Third"};

                             try {

                                      //建立連接

                                      socket = new Socket(serverIP,port);

                                      //初始化流

                                      os = socket.getOutputStream();

                                      is = socket.getInputStream();

                                      byte[] b = new byte[1024];

                                      for(int i = 0;i < data.length;i++){

                                               //發(fā)送數(shù)據(jù)

                                               os.write(data[i].getBytes());

                                               //接收數(shù)據(jù)

                                               int n = is.read(b);

                                               //輸出反饋數(shù)據(jù)

                                               System.out.println("服務器反饋:" + new String(b,0,n));

                                      }

                             } catch (Exception e) {

                                      e.printStackTrace(); //打印異常信息

                             }finally{

                                      try {

                                               //關閉流和連接

                                               is.close();

                                               os.close();

                                               socket.close();

                                      } catch (Exception e2) {}

                             }

                   }

          }

          該示例程序和前面的代碼相比,將數(shù)據(jù)交換部分的邏輯寫在一個for循環(huán)的內容,這樣就可以建立一次連接,依次將data數(shù)組中的數(shù)據(jù)按照順序發(fā)送給服務器端了。

                             如果還是使用前面示例代碼中的服務器端程序運行該程序,則該程序的結果是:

                                      java.net.SocketException: Software caused connection abort: recv failed

                                               at java.net.SocketInputStream.socketRead0(Native Method)

                                               at java.net.SocketInputStream.read(SocketInputStream.java:129)

                                               at java.net.SocketInputStream.read(SocketInputStream.java:90)

                                               at tcp.MulSocketClient.main(MulSocketClient.java:30)

          服務器反饋:First

          顯然,客戶端在實際運行時出現(xiàn)了異常,出現(xiàn)異常的原因是什么呢?如果仔細閱讀前面的代碼,應該還記得前面示例代碼中的服務器端是對話一次數(shù)據(jù)以后就關閉了連接,如果服務器端程序關閉了,客戶端繼續(xù)發(fā)送數(shù)據(jù)肯定會出現(xiàn)異常,這就是出現(xiàn)該問題的原因。

          按照客戶端實現(xiàn)的邏輯,也可以復用服務器端的連接,實現(xiàn)的原理也是將服務器端的數(shù)據(jù)交換邏輯寫在循環(huán)中即可,按照該種思路改造以后的服務器端代碼為:

                   package tcp;

          import java.io.*;

          import java.net.*;

          /**

           * 復用連接的echo服務器

           * 功能:將客戶端發(fā)送的內容反饋給客戶端

           */

          public class MulSocketServer {

                   public static void main(String[] args) {

                             ServerSocket serverSocket = null;

                             Socket socket = null;

                             OutputStream os = null;

                             InputStream is = null;

                             //監(jiān)聽端口號

                             int port = 10000;

                             try {

                                      //建立連接

                                      serverSocket = new ServerSocket(port);

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

                                      //獲得連接

                                      socket = serverSocket.accept();

                                      //初始化流

                                      is = socket.getInputStream();

                                      os = socket.getOutputStream();

                                      byte[] b = new byte[1024];

                                      for(int i = 0;i < 3;i++){

                                               int n = is.read(b);

                                               //輸出

                                               System.out.println("客戶端發(fā)送內容為:" + new String(b,0,n));

                                               //向客戶端發(fā)送反饋內容

                                               os.write(b, 0, n);

                                      }

                             } catch (Exception e) {

                                      e.printStackTrace();

                             }finally{

                                      try{

                                               //關閉流和連接

                                               os.close();

                                               is.close();

                                               socket.close();

                                               serverSocket.close();

                                      }catch(Exception e){}

                             }

                   }

          }

          在該示例代碼中,也將數(shù)據(jù)發(fā)送和接收的邏輯寫在了一個for循環(huán)內部,只是在實現(xiàn)時硬性的將循環(huán)次數(shù)規(guī)定成了3次,這樣代碼雖然比較簡單,但是通用性比較差。

                             以該服務器端代碼實現(xiàn)為基礎運行前面的客戶端程序時,客戶端的輸出為:

                                      服務器反饋:First

          服務器反饋:Second

          服務器反饋:Third

                 服務器端程序的輸出結果為:

                     服務器已啟動:

          客戶端發(fā)送內容為:First

          客戶端發(fā)送內容為:Second

          客戶端發(fā)送內容為:Third

          在該程序中,比較明顯的體現(xiàn)出了“請求-響應”模型,也就是在客戶端發(fā)起連接以后,首先發(fā)送字符串“First”給服務器端,服務器端輸出客戶端發(fā)送的內容“First”,然后將客戶端發(fā)送的內容再反饋給客戶端,這樣客戶端也輸出服務器反饋“First”,這樣就完成了客戶端和服務器端的一次對話,緊接著客戶端發(fā)送“Second”給服務器端,服務端輸出“Second”,然后將“Second”再反饋給客戶端,客戶端再輸出“Second”,從而完成第二次會話,第三次會話的過程和這個一樣。在這個過程中,每次都是客戶端程序首先發(fā)送數(shù)據(jù)給服務器端,服務器接收數(shù)據(jù)以后,將結果反饋給客戶端,客戶端接收到服務器端的反饋,從而完成一次通訊過程。

          在該示例中,雖然解決了多次發(fā)送的問題,但是客戶端和服務器端的次數(shù)控制還不夠靈活,如果客戶端的次數(shù)不固定怎么辦呢?是否可以使用某個特殊的字符串,例如quit,表示客戶端退出呢,這就涉及到網(wǎng)絡協(xié)議的內容了,會在后續(xù)的網(wǎng)絡應用示例部分詳細介紹。下面開始介紹另外一個網(wǎng)絡編程的突出問題。

          2、如何使服務器端支持多個客戶端同時工作?

                   前面介紹的服務器端程序,只是實現(xiàn)了概念上的服務器端,離實際的服務器端程序結構距離還很遙遠,如果需要讓服務器端能夠實際使用,那么最需要解決的問題就是——如何支持多個客戶端同時工作。

                   一個服務器端一般都需要同時為多個客戶端提供通訊,如果需要同時支持多個客戶端,則必須使用前面介紹的線程的概念。簡單來說,也就是當服務器端接收到一個連接時,啟動一個專門的線程處理和該客戶端的通訊。

                   按照這個思路改寫的服務端示例程序將由兩個部分組成,MulThreadSocketServer類實現(xiàn)服務器端控制,實現(xiàn)接收客戶端連接,然后開啟專門的邏輯線程處理該連接,LogicThread類實現(xiàn)對于一個客戶端連接的邏輯處理,將處理的邏輯放置在該類的run方法中。該示例的代碼實現(xiàn)為:

                             package tcp;

          import java.net.ServerSocket;

          import java.net.Socket;

          /**

           * 支持多客戶端的服務器端實現(xiàn)

           */

          public class MulThreadSocketServer {

                   public static void main(String[] args) {

                             ServerSocket serverSocket = null;

                             Socket socket = null;

                             //監(jiān)聽端口號

                             int port = 10000;

                             try {

                                      //建立連接

                                      serverSocket = new ServerSocket(port);

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

                                      while(true){

                                               //獲得連接

                                               socket = serverSocket.accept();

                                               //啟動線程

                                               new LogicThread(socket);

                                      }

                             } catch (Exception e) {

                                      e.printStackTrace();

                             }finally{

                                      try{

                                               //關閉連接

                                               serverSocket.close();

                                      }catch(Exception e){}

                             }

                   }

          }

                   在該示例代碼中,實現(xiàn)了一個while形式的死循環(huán),由于accept方法是阻塞方法,所以當客戶端連接未到達時,將阻塞該程序的執(zhí)行,當客戶端到達時接收該連接,并啟動一個新的LogicThread線程處理該連接,然后按照循環(huán)的執(zhí)行流程,繼續(xù)等待下一個客戶端連接。這樣當任何一個客戶端連接到達時,都開啟一個專門的線程處理,通過多個線程支持多個客戶端同時處理。

                   下面再看一下LogicThread線程類的源代碼實現(xiàn):

                             package tcp;

          import java.io.*;

          import java.net.*;

          /**

           * 服務器端邏輯線程

           */

          public class LogicThread extends Thread {

                   Socket socket;

                   InputStream is;

                   OutputStream os;

                   public LogicThread(Socket socket){

                             this.socket = socket;

                             start(); //啟動線程

                   }

                  

                   public void run(){

                             byte[] b = new byte[1024];

                             try{

                                      //初始化流

                                      os = socket.getOutputStream();

                                      is = socket.getInputStream();

                                      for(int i = 0;i < 3;i++){

                                               //讀取數(shù)據(jù)

                                               int n = is.read(b);

                                               //邏輯處理

                                               byte[] response = logic(b,0,n);

                                               //反饋數(shù)據(jù)

                                               os.write(response);

                                      }

                             }catch(Exception e){

                                      e.printStackTrace();

                             }finally{

                                      close();

                             }

                   }

                  

                   /**

                    * 關閉流和連接

                    */

                   private void close(){

                             try{

                                      //關閉流和連接

                                      os.close();

                                      is.close();

                                      socket.close();

                             }catch(Exception e){}

                   }

                  

                   /**

                    * 邏輯處理方法,實現(xiàn)echo邏輯

                    * @param b 客戶端發(fā)送數(shù)據(jù)緩沖區(qū)

                    * @param off 起始下標

                    * @param len 有效數(shù)據(jù)長度

                    * @return

                    */

                   private byte[] logic(byte[] b,int off,int len){

                             byte[] response = new byte[len];

                             //將有效數(shù)據(jù)拷貝到數(shù)組response

                             System.arraycopy(b, 0, response, 0, len);

                             return response;

                   }

          }

                   在該示例代碼中,每次使用一個連接對象構造該線程,該連接對象就是該線程需要處理的連接,在線程構造完成以后,該線程就被啟動起來了,然后在run方法內部對客戶端連接進行處理,數(shù)據(jù)交換的邏輯和前面的示例代碼一致,只是這里將接收到客戶端發(fā)送過來的數(shù)據(jù)并進行處理的邏輯封裝成了logic方法,按照前面介紹的IO編程的內容,客戶端發(fā)送過來的內容存儲在數(shù)組b的起始下標為0,長度為n個中,這些數(shù)據(jù)是客戶端發(fā)送過來的有效數(shù)據(jù),將有效的數(shù)據(jù)傳遞給logic方法,logic方法實現(xiàn)的是echo服務的邏輯,也就是將客戶端發(fā)送的有效數(shù)據(jù)形成以后新的response數(shù)組,并作為返回值反饋。

                   在線程中將logic方法的返回值反饋給客戶端,這樣就完成了服務器端的邏輯處理模擬,其他的實現(xiàn)和前面的介紹類似,這里就不在重復了。

                   這里的示例還只是基礎的服務器端實現(xiàn),在實際的服務器端實現(xiàn)中,由于硬件和端口數(shù)的限制,所以不能無限制的創(chuàng)建線程對象,而且頻繁的創(chuàng)建線程對象效率也比較低,所以程序中都實現(xiàn)了線程池來提高程序的執(zhí)行效率。

                   這里簡單介紹一下線程池的概念,線程池(Thread pool)是池技術的一種,就是在程序啟動時首先把需要個數(shù)的線程對象創(chuàng)建好,例如創(chuàng)建5000個線程對象,然后當客戶端連接到達時從池中取出一個已經(jīng)創(chuàng)建完成的線程對象使用即可。當客戶端連接關閉以后,將該線程對象重新放入到線程池中供其它的客戶端重復使用,這樣可以提高程序的執(zhí)行速度,優(yōu)化程序對于內存的占用等。

                   關于基礎的TCP方式的網(wǎng)絡編程就介紹這么多,下面介紹UDP方式的網(wǎng)絡編程在Java語言中的實現(xiàn)。

          posted on 2009-06-15 13:28 找個美女做老婆 閱讀(319) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導航:
           

          導航

          統(tǒng)計

          公告

          本blog已經(jīng)搬到新家了, 新家:www.javaly.cn
           http://www.javaly.cn

          常用鏈接

          留言簿(6)

          隨筆檔案

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 洛阳市| 肥城市| 湘乡市| 理塘县| 邵武市| 永年县| 光山县| 昌吉市| 九江县| 江陵县| 宁城县| 仁布县| 自治县| 长阳| 临邑县| 内江市| 滨州市| 寿宁县| 金华市| 农安县| 磐安县| 乌鲁木齐市| 达拉特旗| 桓仁| 临武县| 龙井市| 买车| 平邑县| 磴口县| 阳春市| 育儿| 婺源县| 白河县| 翼城县| 常宁市| 茂名市| 澄迈县| 织金县| 威信县| 双流县| 枣阳市|