周游世界

          喂馬, 劈柴, 周游世界

            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            28 隨筆 :: 0 文章 :: 4 評論 :: 0 Trackbacks
          8.3.10 據報Datagram通訊

            前面在介紹TCP/IP協議的時候,我們已經提到,在TCP/IP協議的傳輸層除了TCP協議之外還有一個UDP協議,相比而言UDP的應用不如TCP廣泛,幾個標準的應用層協議 HTTP,FTP,SMTP…使用的都是TCP協議。但是,隨著計算機網絡的發展,UDP協議正越來越來顯示出其威力,尤其是在需要很強的實時交互性的場合,如網絡游戲,視頻會議等,UDP更是顯示出極強的威力,下面我們就介紹一下Java環境下如何實現UDP網絡傳輸。

            8.3.11 什么是Datagram

            所謂數據報(Datagram)就跟日常生活中的郵件系統一樣,是不能保證可靠的寄到的,而面向鏈接的TCP就好比電話,雙方能肯定對方接受到了信息。在本章前面,我們已經對UDP和TCP進行了比較,在這里再稍作小節:

            TCP,可靠,傳輸大小無限制,但是需要連接建立時間,差錯控制開銷大。
            UDP,不可靠,差錯控制開銷較小,傳輸大小限制在64K以下,不需要建立連接。

            總之,這兩種協議各有特點,應用的場合也不同,是完全互補的兩個協議,在TCP/IP協議中占有同樣重要的地位,要學好網絡編程,兩者缺一不可。

            8.3.12 Datagram通訊的表示方法:DatagramSocket;DatagramPacket

            包java.net中提供了兩個類DatagramSocket和DatagramPacket用來支持數據報通信,DatagramSocket用于在程序之間建立傳送數據報的通信連接, DatagramPacket則用來表示一個數據報。先來看一下DatagramSocket的構造方法:
             DatagramSocket();
             DatagramSocket(int prot);
             DatagramSocket(int port, InetAddress laddr)
            
            其中,port指明socket所使用的端口號,如果未指明端口號,則把socket連接到本地主機上一個可用的端口。laddr指明一個可用的本地地址。給出端口號時要保證不發生端口沖突,否則會生成SocketException類例外。注意:上述的兩個構造方法都聲明拋棄非運行時例外 SocketException,程序中必須進行處理,或者捕獲、或者聲明拋棄。

            用數據報方式編寫client/server程序時,無論在客戶方還是服務方,首先都要建立一個DatagramSocket對象,用來接收或發送數據報,然后使用DatagramPacket類對象作為傳輸數據的載體。下面看一下DatagramPacket的構造方法 :
             DatagramPacket(byte buf[],int length);
             DatagramPacket(byte buf[], int length, InetAddress addr, int port);
             DatagramPacket(byte[] buf, int offset, int length);
             DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port);

            其中,buf中存放數據報數據,length為數據報中數據的長度,addr和port旨明目的地址,offset指明了數據報的位移量。

            在接收數據前,應該采用上面的第一種方法生成一個DatagramPacket對象,給出接收數據的緩沖區及其長度。然后調用DatagramSocket 的方法receive()等待數據報的到來,receive()將一直等待,直到收到一個數據報為止。
            DatagramPacket packet=new DatagramPacket(buf, 256);
            Socket.receive (packet);

            發送數據前,也要先生成一個新的DatagramPacket對象,這時要使用上面的第二種構造方法,在給出存放發送數據的緩沖區的同時,還要給出完整的目的地址,包括IP地址和端口號。發送數據是通過DatagramSocket的方法send()實現的,send()根據數據報的目的地址來尋徑,以傳遞數據報。
            DatagramPacket packet=new DatagramPacket(buf, length, address, port);
            Socket.send(packet);

            在構造數據報時,要給出InetAddress類參數。類InetAddress在包java.net中定義,用來表示一個Internet地址,我們可以通過它提供的類方法getByName()從一個表示主機名的字符串獲取該主機的IP地址,然后再獲取相應的地址信息。

            8.3.13 基于UDP的簡單的Client/Server程序設計

            有了上面的知識,我們就可以來構件一個基于UDP的C/S 網絡傳輸模型

            1. 客戶方程序 QuoteClient.java

            import java.io.*;
            import java.net.*;
            import java.util.*;
            public class QuoteClient {
             public static void main(String[] args) throws IOException
             {
              if(args.length!=1) {
              //如果啟動的時候沒有給出Server的名字,那么出錯退出
               System.out.println("Usage:java QuoteClient ");
               //打印出錯信息
               return; //返回
              }

              DatagramSocket socket=new DatagramSocklet();
              //創建數據報套接字

              Byte[] buf=new byte[256]; //創建緩沖區
              InetAddress address=InetAddress.getByName(args [0]);
            //由命令行給出的第一個參數默認為Server的名字,通過它得到Server的IP信息
              DatagramPacket packet=new DatagramPacket (buf, buf.length, address, 4445);
              //創建DatagramPacket對象
              socket.send(packet); //發送
              packet=new DatagramPacket(buf,buf.length);
              //創建新的DatagramPacket對象,用來接收數據報
              socket.receive(packet); //接收
              String received=new String(packet.getData());
              //根據接收到的字節數組生成相應的字符串
              System.out.println("Quote of the Moment:"+received );
              //打印生成的字符串

              socket.close(); //關閉套接口
             }
            }

            2. 服務器方程序:QuoteServer.java

            public class QuoteServer{
             public static void main(String args[]) throws java.io.IOException
             {
              new QuoteServerThread().start();
              //啟動一個QuoteServerThread線程
             }
            }

            3. 程序QuoteServerThread.java

            import java.io.*;
            import java.net.*;
            import java.util.*;
            //服務器線程
            public class QuoteServerThread extends Thread
            {
            protected DatagramSocket socket=null;
            //記錄和本對象相關聯的DatagramSocket對象
            protected BufferedReader in=null;
            //用來讀文件的一個Reader
            protected boolean moreQuotes=true;
            //標志變量,是否繼續操作

            public QuoteServerThread() throws IOException {
            //無參數的構造函數
              this("QuoteServerThread");
              //以QuoteServerThread為默認值調用帶參數的構造函數
            }
            public QuoteServerThread(String name) throws IOException {
              super(name); //調用父類的構造函數
              socket=new DatagramSocket(4445);
              //在端口4445創建數據報套接字
              try{
                in= new BufferedReader(new FileReader(" one-liners.txt"));
                //打開一個文件,構造相應的BufferReader對象
              }catch(FileNotFoundException e) { //異常處理
                System.err.println("Could not open quote file. Serving time instead.");
                 //打印出錯信息
              }
            }
            public void run() //線程主體
            {
              while(moreQuotes) {
               try{
                 byte[] buf=new byte[256]; //創建緩沖區
                 DatagramPacket packet=new DatagramPacket(buf,buf.length);
                 //由緩沖區構造DatagramPacket對象
                 socket.receive(packet); //接收數據報
                 String dString=null;
                 if(in= =null) dString=new Date().toString();
                 //如果初始化的時候打開文件失敗了,
                 //則使用日期作為要傳送的字符串
                 else dString=getNextQuote();
                 //否則調用成員函數從文件中讀出字符串
                 buf=dString.getByte();
                 //把String轉換成字節數組,以便傳送

                 InetAddress address=packet.getAddress();
                 //從Client端傳來的Packet中得到Client地址
                 int port=packet.getPort(); //和端口號
                 packet=new DatagramPacket(buf,buf.length,address,port);
                 //根據客戶端信息構建DatagramPacket
                 socket.send(packet); //發送數據報
                }catch(IOException e) { //異常處理
                 e.printStackTrace(); //打印錯誤棧
                 moreQuotes=false; //標志變量置false,以結束循環
                }
              }
              socket.close(); //關閉數據報套接字
            }

            protected String getNextQuotes(){
            //成員函數,從文件中讀數據
              String returnValue=null;
              try {
                 if((returnValue=in.readLine())= =null) {
                 //從文件中讀一行,如果讀到了文件尾
                 in.close( ); //關閉輸入流
                 moreQuotes=false;
                 //標志變量置false,以結束循環
                 returnValue="No more quotes. Goodbye.";
                 //置返回值
                 } //否則返回字符串即為從文件讀出的字符串
              }catch(IOEception e) { //異常處理
                 returnValue="IOException occurred in server";
                 //置異常返回值
              }
              return returnValue; //返回字符串
            }
            }
          可以看出使用UDP和使用TCP在程序上還是有很大的區別的。一個比較明顯的區別是,UDP的Socket編程是不提供監聽功能的,也就是說通信雙方更為平等,面對的接口是完全一樣的。但是為了用UDP實現C/S結構,在使用UDP時可以使用DatagramSocket.receive()來實現類似于監聽的功能。因為receive()是阻塞的函數,當它返回時,緩沖區里已經填滿了接受到的一個數據報,并且可以從該數據報得到發送方的各種信息,這一點跟 accept()是很相象的,因而可以根據讀入的數據報來決定下一步的動作,這就達到了跟網絡監聽相似的效果。

            

            8.3.14 用數據報進行廣播通訊

            DatagramSocket只允許數據報發送一個目的地址,java.net包中提供了一個類MulticastSocket,允許數據報以廣播方式發送到該端口的所有客戶。MulticastSocket用在客戶端,監聽服務器廣播來的數據。

            我們對上面的程序作一些修改,利用MulticastSocket實現廣播通信。新程序完成的功能是使同時運行的多個客戶程序能夠接收到服務器發送來的相同的信息,顯示在各自的屏幕上。

            1. 客戶方程序:MulticastClient.java

            import java.io.*;
            import java.net.*;
            import java.util.*;
            public class MulticastClient {
              public static void main(String args[]) throws IOException
              {
               MulticastSocket socket=new MulticastSocket(4446);
               //創建4446端口的廣播套接字
               InetAddress address=InetAddress.getByName("230.0.0.1");
               //得到230.0.0.1的地址信息
               socket.joinGroup(address);
               //使用joinGroup()將廣播套接字綁定到地址上
               DatagramPacket packet;

               for(int i=0;i<5;i++) {
                 byte[] buf=new byte[256];
                 //創建緩沖區
                 packet=new DatagramPacket(buf,buf.length);
                 //創建接收數據報
                 socket.receive(packet); //接收
                 String received=new String(packet.getData());
                 //由接收到的數據報得到字節數組,
                 //并由此構造一個String對象
                 System.out.println("Quote of theMoment:"+received);
                 //打印得到的字符串
               } //循環5次
               socket.leaveGroup(address);
               //把廣播套接字從地址上解除綁定
               socket.close(); //關閉廣播套接字
             }
            }

            2. 服務器方程序:MulticastServer.java

            public class MulticastServer{
              public static void main(String args[]) throws java.io.IOException
              {
                new MulticastServerThread().start();
                //啟動一個服務器線程
              }
            }

            3. 程序MulticastServerThread.java

            import java.io.*;
            import java.net.*;
            import java.util.*;
            public class MulticastServerThread extends QuoteServerThread
            //從QuoteServerThread繼承得到新的服務器線程類MulticastServerThread
            {
              Private long FIVE_SECOND=5000; //定義常量,5秒鐘
              public MulticastServerThread(String name) throws IOException
              {
                super("MulticastServerThread");
                //調用父類,也就是QuoteServerThread的構造函數
              }

              public void run() //重寫父類的線程主體
              {
               while(moreQuotes) {
               //根據標志變量判斷是否繼續循環
                try{
                  byte[] buf=new byte[256];
                  //創建緩沖區
                  String dString=null;
                  if(in==null) dString=new Date().toString();
                  //如果初始化的時候打開文件失敗了,
                  //則使用日期作為要傳送的字符串
                  else dString=getNextQuote();
                  //否則調用成員函數從文件中讀出字符串
                  buf=dString.getByte();
                  //把String轉換成字節數組,以便傳送send it
                  InetAddress group=InetAddress.getByName("230.0.0.1");
                  //得到230.0.0.1的地址信息
                  DatagramPacket packet=new DatagramPacket(buf,buf.length,group,4446);
                  //根據緩沖區,廣播地址,和端口號創建DatagramPacket對象
                  socket.send(packet); //發送該Packet
                  try{
                    sleep((long)(Math.random()*FIVE_SECONDS));
                    //隨機等待一段時間,0~5秒之間
                  }catch(InterruptedException e) { } //異常處理
                }catch(IOException e){ //異常處理
                  e.printStackTrace( ); //打印錯誤棧

                  moreQuotes=false; //置結束循環標志
                }
              }
              socket.close( ); //關閉廣播套接口
             }
            }

            至此,Java網絡編程這一章已經講解完畢。讀者通過學習,應該對網絡編程有了一個清晰的認識,可能對某些概念還不是十分的清楚,還是需要更多的實踐來進一步掌握。編程語言的學習不同于一般的學習,及其強調實踐的重要性。讀者應該對URL網絡編程,Socket中的TCP,UDP編程進行大量的練習才能更好的掌握本章中所提到的一些概念,才能真正學到Java網絡編程的精髓!

            最后幾個小節所舉的例子,讀者務必要親自試驗一下,如果遇到問題,想辦法解決之。最好能根據自己的意圖加以改進。這樣才能更好的理解這幾個程序,理解其中所包含的編程思想。

            本講小結】

            本講主要講解了Java環境下的網絡編程。因為TCP/IP協議是Java網絡編程的基礎知識,本講開篇重點介紹了TCP/IP協議中的一些概念, TCP/IP協議本身是一個十分龐大的系統,用幾個小節是不可能講清楚的。所以我們只是聯系實際,講解了一些最基本的概念,幫助學生理解后面的相關內容。重點有一下幾個概念:主機名,IP,端口,服務類型,TCP,UDP。

            后續的內容分為兩大塊,一塊是以URL為主線,講解如何通過URL類和URLConnection類訪問WWW網絡資源,由于使用URL十分方便直觀,盡管功能不是很強,還是值得推薦的一種網絡編程方法,尤其是對于初學者特別容易接受。本質上講,URL網絡編程在傳輸層使用的還是TCP協議。

            另一塊是以Socket接口和C/S網絡編程模型為主線,依次講解了如何用Java實現基于TCP的C/S結構,主要用到的類有Socket,ServerSocket。以及如何用Java實現基于 UDP的C/S結構,還討論了一種特殊的傳輸方式,廣播方式,這種方式是UDP所特有的,主要用到的類有DatagramSocket , DatagramPacket, MulticastSocket。這一塊在Java網絡編程中相對而言是最難的(盡管Java在網絡編程這方面已經做的夠"傻瓜"了,但是網絡編程在其他環境下的卻是一件極為頭痛的事情,再"傻瓜"還是有一定的難度),也是功能最為強大的一部分,讀者應該好好研究,領悟其中的思想。

            最后要強調的是要學好Java網絡編程,Java語言,最重要的還是在于多多練習!
          posted on 2006-11-05 12:36 周游世界 閱讀(516) 評論(0)  編輯  收藏 所屬分類: 網絡協議
          主站蜘蛛池模板: 湛江市| 达拉特旗| 黔东| 屏边| 潼关县| 靖边县| 明光市| 通城县| 石河子市| 赤壁市| 怀仁县| 黄石市| 临沧市| 阳西县| 大足县| 蓬莱市| 莆田市| 延吉市| 满洲里市| 屏东县| 瑞丽市| 庐江县| 辛集市| 云南省| 砀山县| 浮山县| 色达县| 五台县| 柳江县| 积石山| 漳州市| 弥渡县| 峨边| 龙南县| 西乡县| 长阳| 山西省| 清远市| 阆中市| 色达县| 雷山县|