• <output id="i6hun"><strong id="i6hun"><xmp id="i6hun"></xmp></strong></output><table id="i6hun"><strike id="i6hun"></strike></table>
        1. <table id="i6hun"><option id="i6hun"></option></table>
          <acronym id="i6hun"><strong id="i6hun"><xmp id="i6hun"></xmp></strong></acronym>

          Java網絡編程



          Java對于網絡通訊有著非常強大的支持。不僅可以獲取網絡資源,傳遞參數到遠程服務器,還可以通過Socket對象實現TCP協議,通過DatagramSocket對象實現UDP協議。同時,對于多點廣播以及代理服務器也有著非常強大的支持。以下是本人在學習過程中的總結和歸納。


          1. Java的基本網絡支持


          1.1 InetAddress
              Java中的InetAddress是一個代表IP地址的對象。IP地址可以由字節數組和字符串來分別表示,InetAddress將IP地址以對象的形式進行封裝,可以更方便的操作和獲取其屬性。InetAddress沒有構造方法,可以通過兩個靜態方法獲得它的對象。代碼如下:


           

          1. //根據主機名來獲取對應的InetAddress實例  
          2.         InetAddress ip = InetAddress.getByName("www.oneedu.cn");  
          3.         //判斷是否可達  
          4.         System.out.println("oneedu是否可達:" + ip.isReachable(2000));   
          5.         //獲取該InetAddress實例的IP字符串  
          6.         System.out.println(ip.getHostAddress());  
          7.         //根據原始IP地址(字節數組形式)來獲取對應的InetAddress實例  
          8.         InetAddress local = InetAddress.getByAddress(new byte[]  
          9.         {127,0,0,1});  
          10.         System.out.println("本機是否可達:" + local.isReachable(5000));   
          11.         //獲取該InetAddress實例對應的全限定域名  
          12.         System.out.println(local.getCanonicalHostName()); 


          1.2 URLDecoder和URLEncoder
              這兩個類可以別用于將application/x-www-form-urlencoded MIME類型的字符串轉換為普通字符串,將普通字符串轉換為這類特殊型的字符串。使用URLDecoder類的靜態方法decode()用于解碼,URLEncoder類的靜態方法encode()用于編碼。具體使用方法如下。


           

          1. //將application/x-www-form-urlencoded字符串  
          2.         //轉換成普通字符串  
          3.                 String keyWord = URLDecoder.decode(  
          4.             "%E6%9D%8E%E5%88%9A+j2ee""UTF-8");  
          5.         System.out.println(keyWord);  
          6.         //將普通字符串轉換成  
          7.         //application/x-www-form-urlencoded字符串  
          8.         String urlStr = URLEncoder.encode(  
          9.             "ROR敏捷開發最佳指南" , "GBK");  
          10.         System.out.println(urlStr); 


          1.3 URL和URLConnection
              URL可以被認為是指向互聯網資源的“指針”,通過URL可以獲得互聯網資源相關信息,包括獲得URL的InputStream對象獲取資源的信息,以及一個到URL所引用遠程對象的連接URLConnection。
              URLConnection對象可以向所代表的URL發送請求和讀取URL的資源。通常,創建一個和URL的連接,需要如下幾個步驟:
              a. 創建URL對象,并通過調用openConnection方法獲得URLConnection對象;
              b. 設置URLConnection參數和普通請求屬性;
              c. 向遠程資源發送請求;
              d. 遠程資源變為可用,程序可以訪問遠程資源的頭字段和通過輸入流來讀取遠程資源返回的信息。
              這里需要重點討論一下第三步:如果只是發送GET方式請求,使用connect方法建立和遠程資源的連接即可;如果是需要發送POST方式的請求,則需要獲取URLConnection對象所對應的輸出流來發送請求。這里需要注意的是,由于GET方法的參數傳遞方式是將參數顯式追加在地址后面,那么在構造URL對象時的參數就應當是包含了參數的完整URL地址,而在獲得了URLConnection對象之后,就直接調用connect方法即可發送請求。
          而POST方法傳遞參數時僅僅需要頁面URL,而參數通過需要通過輸出流來傳遞。另外還需要設置頭字段。以下是兩種方式的代碼。


           

          1. //1. 向指定URL發送GET方法的請求  
          2. String urlName = url + "?" + param;  
          3.             URL realUrl = new URL(urlName);  
          4.             //打開和URL之間的連接  
          5.             URLConnection conn = realUrl.openConnection();  
          6.             //設置通用的請求屬性  
          7.             conn.setRequestProperty("accept""*/*");   
          8.             conn.setRequestProperty("connection""Keep-Alive");   
          9.             conn.setRequestProperty("user-agent",   
          10.                 "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");   
          11.             //建立實際的連接  
          12.             conn.connect();   
          13.  
          14. //2. 向指定URL發送POST方法的請求  
          15. URL realUrl = new URL(url);  
          16.             //打開和URL之間的連接  
          17.             URLConnection conn = realUrl.openConnection();  
          18.             //設置通用的請求屬性  
          19.             conn.setRequestProperty("accept""*/*");   
          20.             conn.setRequestProperty("connection""Keep-Alive");   
          21.             conn.setRequestProperty("user-agent",   
          22.                 "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");   
          23.             //發送POST請求必須設置如下兩行  
          24.             conn.setDoOutput(true);  
          25.             conn.setDoInput(true);  
          26.             //獲取URLConnection對象對應的輸出流  
          27.             out = new PrintWriter(conn.getOutputStream());  
          28.             //發送請求參數  
          29.             out.print(param);  
          30.  


              另外需要注意的是,如果既需要讀取又需要發送,一定要先使用輸出流,再使用輸入流。因為遠程資源不會主動向本地發送請求,必須要先請求資源。


          2. 基于TCP協議的網絡編程
              TCP協議是一種可靠的通絡協議,通信兩端的Socket使得它們之間形成網絡虛擬鏈路,兩端的程序可以通過虛擬鏈路進行通訊。Java使用socket對象代表兩端的通信端口,并通過socket產生的IO流來進行網絡通信。


          2.1 ServerSocket
              在兩個通信端沒有建立虛擬鏈路之前,必須有一個通信實體首先主動監聽來自另一端的請求。ServerSocket對象使用accept()方法用于監聽來自客戶端的Socket連接,如果收到一個客戶端Socket的連接請求,該方法將返回一個與客戶端Socket對應的Socket對象。如果沒有連接,它將一直處于等待狀態。通常情況下,服務器不應只接受一個客戶端請求,而應該通過循環調用accept()不斷接受來自客戶端的所有請求。
              這里需要注意的是,對于多次接收客戶端數據的情況來說,一方面可以每次都在客戶端建立一個新的Socket對象然后通過輸入輸出通訊,這樣對于服務器端來說,每次循環所接收的內容也不一樣,被認為是不同的客戶端。另外,也可以只建立一次,然后在這個虛擬鏈路上通信,這樣在服務器端一次循環的內容就是通信的全過程。
              服務器端的示例代碼:


           

          1. //創建一個ServerSocket,用于監聽客戶端Socket的連接請求  
          2.         ServerSocket ss = new ServerSocket(30000);  
          3.         //采用循環不斷接受來自客戶端的請求  
          4.         while (true)  
          5.         {  
          6.             //每當接受到客戶端Socket的請求,服務器端也對應產生一個Socket  
          7.             Socket s = ss.accept();  
          8.             //將Socket對應的輸出流包裝成PrintStream  
          9.             PrintStream ps = new PrintStream(s.getOutputStream());  
          10.             //進行普通IO操作  
          11.             ps.println("您好,您收到了服務器的新年祝福!");  
          12.             //關閉輸出流,關閉Socket  
          13.             ps.close();  
          14.             s.close();  
          15.         } 
             


          2.2 Socket
              使用Socket可以主動連接到服務器端,使用服務器的IP地址和端口號初始化之后,服務器端的accept便可以解除阻塞繼續向下執行,這樣就建立了一對互相連接的Socket。
              客戶端示例代碼:


           

          1. Socket socket = new Socket("127.0.0.1" , 30000);  
          2.         //將Socket對應的輸入流包裝成BufferedReader  
          3.         BufferedReader br = new BufferedReader(  
          4.             new InputStreamReader(socket.getInputStream()));  
          5.         //進行普通IO操作  
          6.         String line = br.readLine();  
          7.         System.out.println("來自服務器的數據:" + line);  
          8.         //關閉輸入流、socket  
          9.         br.close();  
          10.         socket.close(); 


          2.3 使用多線程
              在復雜的通訊中,使用多線程非常必要。對于服務器來說,它需要接收來自多個客戶端的連接請求,處理多個客戶端通訊需要并發執行,那么就需要對每一個傳過來的Socket在不同的線程中進行處理,每條線程需要負責與一個客戶端進行通信。以防止其中一個客戶端的處理阻塞會影響到其他的線程。對于客戶端來說,一方面要讀取來自服務器端的數據,另一方面又要向服務器端輸出數據,它們同樣也需要在不同的線程中分別處理。
          具體代碼如下,服務器端:


           

          1. public class MyServer  
          2. {  
          3.     //定義保存所有Socket的ArrayList  
          4.     public static ArrayList<Socket> socketList = new ArrayList<Socket>();  
          5.     public static void main(String[] args)   
          6.         throws IOException  
          7.     {  
          8.         ServerSocket ss = new ServerSocket(30000);  
          9.         while(true)  
          10.         {  
          11.             //此行代碼會阻塞,將一直等待別人的連接  
          12.             Socket s = ss.accept();  
          13.             socketList.add(s);  
          14.             //每當客戶端連接后啟動一條ServerThread線程為該客戶端服務  
          15.             new Thread(new ServerThread(s)).start();  
          16.         }  
          17.     }  

              客戶端:


           

          1. public class MyClient  
          2. {  
          3.     public static void main(String[] args)  
          4.         throws IOException   
          5.     {  
          6.         Socket s = s = new Socket("127.0.0.1" , 30000);  
          7.         //客戶端啟動ClientThread線程不斷讀取來自服務器的數據  
          8.         new Thread(new ClientThread(s)).start();  
          9.         //獲取該Socket對應的輸出流  
          10.         PrintStream ps = new PrintStream(s.getOutputStream());  
          11.         String line = null;  
          12.         //不斷讀取鍵盤輸入  
          13.         BufferedReader br = new BufferedReader(new InputStreamReader(System.in));  
          14.         while ((line = br.readLine()) != null)  
          15.         {  
          16.             //將用戶的鍵盤輸入內容寫入Socket對應的輸出流  
          17.             ps.println(line);  
          18.         }  
          19.     }  

          2.4 使用協議字符
              協議字符用于標識一些字段的特定功能,用于說明傳輸內容的特性。它可以由用戶自定義。一般情況下,可以定義一個存放這些協議字符的接口。如下:

             

          1. public interface YeekuProtocol  
          2. {  
          3.     //定義協議字符串的長度  
          4.     int PROTOCOL_LEN = 2;  
          5.     //下面是一些協議字符串,服務器和客戶端交換的信息  
          6.     //都應該在前、后添加這種特殊字符串。  
          7.     String MSG_ROUND = "§γ";  
          8.     String USER_ROUND = "∏∑";  
          9.     String LOGIN_SUCCESS = "1";  
          10.     String NAME_REP = "-1";  
          11.     String PRIVATE_ROUND = "★【";  
          12.     String SPLIT_SIGN = "※";  


              在字段時可以加上這些字符,如下代碼:


           

          1. while(true)  
          2.             {  
          3.                 String userName = JOptionPane.showInputDialog(tip + "輸入用戶名");  
          4.                 //將用戶輸入的用戶名的前后增加協議字符串后發送  
          5.                 ps.println(YeekuProtocol.USER_ROUND + userName  
          6.                     + YeekuProtocol.USER_ROUND);  
          7.                 //讀取服務器的響應  
          8.                 String result = brServer.readLine();  
          9.                 //如果用戶重復,開始下次循環  
          10.                 if (result.equals(YeekuProtocol.NAME_REP))  
          11.                 {  
          12.                     tip = "用戶名重復!請重新";  
          13.                     continue;  
          14.                 }  
          15.                 //如果服務器返回登陸成功,結束循環  
          16.                 if (result.equals(YeekuProtocol.LOGIN_SUCCESS))  
          17.                 {  
          18.                     break;  
          19.                 }  
          20.             } 


              收到發送來的字段時候,也再次拆分成所需要的部分,如下代碼:


           

          1. if (line.startsWith(YeekuProtocol.PRIVATE_ROUND)   
          2.                     && line.endsWith(YeekuProtocol.PRIVATE_ROUND))  
          3.                 {  
          4.                     //得到真實消息  
          5.                     String userAndMsg = getRealMsg(line);  
          6.                     //以SPLIT_SIGN來分割字符串,前面部分是私聊用戶,后面部分是聊天信息  
          7.                     String user = userAndMsg.split(YeekuProtocol.SPLIT_SIGN)[0];  
          8.                     String msg = userAndMsg.split(YeekuProtocol.SPLIT_SIGN)[1];  
          9.                     //獲取私聊用戶對應的輸出流,并發送私聊信息  
          10.                     Server.clients.get(user).println(  
          11.                         Server.clients.getKeyByValue(ps) + "悄悄地對你說:" + msg);  
          12.                 } 


          3. UDP協議的網絡編程
              UDP協議是一種不可靠的網絡協議,它在通訊實例的兩端個建立一個Socket,但這兩個Socket之間并沒有虛擬鏈路,這兩個Socket只是發送和接受數據報的對象,Java提供了DatagramSocket對象作為基于UDP協議的Socket,使用DatagramPacket代表DatagramSocket發送和接收的數據報。


          3.1 使用DatagramSocket發送、接收數據
              DatagramSocket本身并不負責維護狀態和產生IO流。它僅僅負責接收和發送數據報。使用receive(DatagramPacket p)方法接收,使用send(DatagramPacket p)方法發送。
              這里需要首先明確的是,DatagramPacket對象的構造。DatagramPacket的內部實際上采用了一個字節型數組來保存數據,它的初始化方法如下:

          1. //接收端的DatagaramSocket內部包含一個空的數組,接收傳遞過來的數據報中的數組信息?梢酝ㄟ^DatagaramSocket對象的getData()方法返回的數組來獲取其中的包含的數組。  
          2. Private DatagaramSocket udpSocket=new DatagaramSocket(buf,buf.length);  
          3. //發送端的DatagaramSocket內部包含一個將要傳遞的數組,同時需要包含目標IP和端口。如果初始化時傳遞的數組參數是空,可以通過調用DatagaramSocket對象的setData()方法設置內容。  
          4. Private DatagaramSocket udpSocket=new DatagaramSocket(buf,buf.length,IP,PORT);  
          5. udpSocket。setData(outBuf); 

              作為這兩個方法的參數,作用和構造不同的。作為接收方法中的參數,DatagramPacket中的數組一個空的數組,用來存放接收到的DatagramPacket對象中的數組;而作為發送方法參數,DatagramPacket本身含有了目的端的IP和端口,以及存儲了要發送內容的指定了長度的字節型數組。
              另外,DatagramPacket對象還提供了setData(Byte[] b)和Byte[] b= getData()方法,用于設置DatagramPacket中包含的數組內容和獲得其中包含數組的內容。
              使用TCP和UDP通訊的編碼區別:
              a. 在TCP中,目標IP和端口由Socket指定包含;UDP中,目標IP由DatagramPacket包含指定,DatagramSocket只負責發送和接受。
              b. 在TCP中,通訊是通過Socket獲得的IO流來實現;在UDP中,則通過DatagramSocket的send和receive方法。
           

          3.2 使用MulticastSocket實現多點廣播
              MulticastSocket是DatagramSocket的子類,可以將數據報以廣播形式發送到數量不等的多個客戶端。實現策略就是定義一個廣播地址,使得每個MulticastSocket都加入到這個地址中。從而每次使用MulticastSocket發送數據報(包含的廣播地址)時,所有加入了這個廣播地址的MulticastSocket對象都可以收到信息。
              MulticastSocket的初始化需要傳遞端口號作為參數,特別對于需要接受信息的端來說,它的端口號需要與發送端數據報中包含的端口號一致。具體代碼如下:


           

          1. //創建用于發送、接收數據的MulticastSocket對象  
          2.             //因為該MulticastSocket對象需要接收,所以有指定端口  
          3.             socket = new MulticastSocket(BROADCAST_PORT);  
          4.             broadcastAddress = InetAddress.getByName(BROADCAST_IP);  
          5.             //將該socket加入指定的多點廣播地址  
          6.             socket.joinGroup(broadcastAddress);  
          7.             //設置本MulticastSocket發送的數據報被回送到自身  
          8.             socket.setLoopbackMode(false);  
          9.             //初始化發送用的DatagramSocket,它包含一個長度為0的字節數組  
          10.             outPacket = new DatagramPacket(new byte[0] , 0 ,  
          11.                 broadcastAddress , BROADCAST_PORT); 


          4. 使用代理服務器
              Java中可以使用Proxy直接創建連接代理服務器,具體使用方法如下:


           

          1. public class ProxyTest  
          2. {  
          3.     Proxy proxy;  
          4.     URL url;  
          5.     URLConnection conn;  
          6.     //從網絡通過代理讀數據  
          7.     Scanner scan;  
          8.     PrintStream ps ;  
          9.     //下面是代理服務器的地址和端口,  
          10.     //換成實際有效的代理服務器的地址和端口  
          11.     String proxyAddress = "202.128.23.32";  
          12.     int proxyPort;  
          13.     //下面是你試圖打開的網站地址  
          14.     String urlStr = "http://www.oneedu.cn";  
          15.  
          16.     public void init()  
          17.     {  
          18.         try 
          19.         {  
          20.             url = new URL(urlStr);  
          21.             //創建一個代理服務器對象  
          22.             proxy = new Proxy(Proxy.Type.HTTP,  
          23.                 new InetSocketAddress(proxyAddress , proxyPort));  
          24.             //使用指定的代理服務器打開連接  
          25.             conn = url.openConnection(proxy);  
          26.             //設置超時時長。  
          27.             conn.setConnectTimeout(5000);  
          28.             scan = new Scanner(conn.getInputStream());  
          29.             //初始化輸出流  
          30.             ps = new PrintStream("Index.htm");  
          31.             while (scan.hasNextLine())  
          32.             {  
          33.                 String line = scan.nextLine();  
          34.                 //在控制臺輸出網頁資源內容  
          35.                 System.out.println(line);  
          36.                 //將網頁資源內容輸出到指定輸出流  
          37.                 ps.println(line);  
          38.             }  
          39.         }  
          40.         catch(MalformedURLException ex)  
          41.         {  
          42.             System.out.println(urlStr + "不是有效的網站地址!");  
          43.         }  
          44.         catch(IOException ex)  
          45.         {  
          46.             ex.printStackTrace();  
          47.         }  
          48.         //關閉資源  
          49.         finally 
          50.         {  
          51.             if (ps != null)  
          52.             {  
          53.                 ps.close();  
          54.             }  
          55.         }  
          56.     }  
          57.  
          58.     

          5. 編碼中的問題總結

              a. 雙方初始化套接字以后,就等于建立了鏈接,表示雙方互相可以知曉對方的狀態。服務器端可以調用接收到的客戶端套接字進行輸入輸出流操作,客戶端可以調用自身內部的套接字對象進行輸入輸出操作。這樣可以保持輸入輸出的流暢性。例如,客戶端向服務器端發送消息時,可以隔一段的時間輸入一段信息,然后服務器端使用循環不斷的讀取傳過來的輸入流。
              b. 對于可能出現阻塞的方法,例如客戶端進行循環不斷讀取來自服務器端的響應信息時,如果此時服務器端并沒有向客戶端進行輸出,那么讀取的方法將處于阻塞狀態,直到收到信息為止才向下執行代碼。那么對于這樣容易產生阻塞的代碼,就需要將它放在一個單獨的線程中處理。
              c. 有一些流是順承的。例如,服務器端在收到客戶端的消息以后,就將消息再通過輸出流向其他所有服務器發送。那么,這個來自客戶端的輸入流和發向客戶端的輸出流就是順接的關系,不必對它們分在兩個不同的線程。
              d. println()方法對應readLine()。
              e. 在JFrame類中,一般不要將自己的代碼寫進main方法中,可以將代碼寫到自定義的方法中,然后在main方法中調用。
           

          北大青鳥網上報名
          北大青鳥招生簡章
          自拍偷拍2018视频
        2. <output id="i6hun"><strong id="i6hun"><xmp id="i6hun"></xmp></strong></output><table id="i6hun"><strike id="i6hun"></strike></table>
              1. <table id="i6hun"><option id="i6hun"></option></table>
                <acronym id="i6hun"><strong id="i6hun"><xmp id="i6hun"></xmp></strong></acronym>