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的內部實際上採用了一個字節型數組來保存數據,它的初始化方法以下:app

 

      
      
               
      
      
  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的初始化須要傳遞端口號做爲參數,特別對於須要接受信息的端來講,它的端口號須要與發送端數據報中包含的端口號一致。具體代碼以下:socket

 
 

      
      
               
      
      
  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. 編碼中的問題總結ide

    a. 雙方初始化套接字之後,就等於創建了連接,表示雙方互相能夠知曉對方的狀態。服務器端能夠調用接收到的客戶端套接字進行輸入輸出流操做,客戶端能夠調用自身內部的套接字對象進行輸入輸出操做。這樣能夠保持輸入輸出的流暢性。例如,客戶端向服務器端發送消息時,能夠隔一段的時間輸入一段信息,而後服務器端使用循環不斷的讀取傳過來的輸入流。
    b. 對於可能出現阻塞的方法,例如客戶端進行循環不斷讀取來自服務器端的響應信息時,若是此時服務器端並無向客戶端進行輸出,那麼讀取的方法將處於阻塞狀態,直到收到信息爲止才向下執行代碼。那麼對於這樣容易產生阻塞的代碼,就須要將它放在一個單獨的線程中處理。
    c. 有一些流是順承的。例如,服務器端在收到客戶端的消息之後,就將消息再經過輸出流向其餘全部服務器發送。那麼,這個來自客戶端的輸入流和發向客戶端的輸出流就是順接的關係,沒必要對它們分在兩個不一樣的線程。
    d. println()方法對應readLine()。
    e. 在JFrame類中,通常不要將本身的代碼寫進main方法中,能夠將代碼寫到自定義的方法中,而後在main方法中調用。
 oop

相關文章
相關標籤/搜索