第十七章.網絡編程
Java的基本網絡支持:
使用InetAddress:
1 import java.net.InetAddress; 2 3 public class InetAddressTest{ 4 public static void main(String[] args) throws Exception{ 5 //根據主機名來獲取對應的InetAddress實例 6 InetAddress ip = InetAddress.getByName("www.lanshanxiao.cc"); 7 //判斷是否可達 8 System.out.println("lanshanxiao是否可達:" + ip.isReachable(2000));View Code9 //獲取該InetAddress實例的IP字符串 10 System.out.println(ip.getHostAddress()); 11 //根據原始IP地址來獲取對應的InetAddress實例 12 InetAddress local = InetAddress.getByAddress(new byte[] {127,0,0,1}); 13 System.out.println("本機是否可達:" + local.isReachable(5000)); 14 //獲取該InetAddress實例對應的權限定域名15 System.out.println(local.getCanonicalHostName()); 16 } 17 }
註意上面程序中:InetAddress local = InetAddress.getByAddress(new byte[] {127,0,0,1});大括號中的127,0,0,1之間的符號是逗號
使用URLDecoder和URLEncoder:
URLDecoder和URLEncoder用於完成普通字符串和application/x-www-form-urlencoded MIME字符串之間的相互轉換。
1 importView Codejava.net.URLEncoder; 2 import java.net.URLDecoder; 3 4 public class URLDecoderTest{ 5 public static void main(String[] args) throws Exception{ 6 //將application/x-www-form-urlencoded字符串 7 //轉換成普通字符串 8 String keyWord = URLDecoder.decode("%E7%96%AF%E7%8B%82java", "utf-8"); 9 System.out.println(keyWord); 10 //將普通字符串轉換成 11 //application/x-www-form-urlencoded字符串 12 String urlStr = URLEncoder.encode("瘋狂Android講義", "GBK"); 13 System.out.println(urlStr); 14 } 15 }
URL、URLConnection、URLPermission:
URL(Uniform Resource Locator)對象代表統一資源定位器,它是指向互聯網“資源”的指針。資源可以是簡單的文件或目錄,也可以是復雜的對象的引用,如:對數據庫
或搜索引擎的查詢。
URL可以由協議名、主機、端口、資源組成:
protocol://host:port/resourceName
如下:
http://www.crazyit.org/index.php
多線程下載工具類:
1 import java.io.RandomAccessFile; 2 import java.io.InputStream; 3 import java.net.URL; 4 import java.net.HttpURLConnection; 5 6 public class DownUtil{ 7 //定義下載資源的路徑 8 private String path; 9 //指定所下載的文件的保存位置 10 private String targetFile; 11 //定義需要使用多少個線程下載資源 12 private int threadNum; 13 //定義下載的線程對象 14 private DownThread[] threads; 15 //定義下載的文件的總大小 16 private int fileSize; 17 18 public DownUtil(String path, String targetFile, int threadNum){ 19 this.path = path; 20 this.threadNum = threadNum; 21 //初始化threads數組 22 threads = new DownThread[threadNum]; 23 this.targetFile = targetFile; 24 } 25 26 public void download() throws Exception{ 27 URL url = new URL(path); 28 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 29 conn.setConnectTimeout(5 * 1000); 30 conn.setRequestMethod("GET"); 31 conn.setRequestProperty( 32 "Accept", 33 "image/gif, image/jpeg, image/pjpeg, image/pjpeg, " 34 + "application/x-shockwave-flash, application/xaml+xml, " 35 + "application/vnd.ms-xpsdocument, application/x-ms-xbap, " 36 + "application/x-ms-application, application/vnd.ms-excel, " 37 + "application/vnd.ms-powerpoint, application/msword, */*"); 38 conn.setRequestProperty("Accept-Language", "zh-CN"); 39 conn.setRequestProperty("Charset", "UTF-8"); 40 conn.setRequestProperty("Connection", "Keep-Alive"); 41 //得到文件大小 42 fileSize = conn.getContentLength(); 43 conn.disconnect(); 44 int currentPartSize = fileSize / threadNum + 1; 45 RandomAccessFile file = new RandomAccessFile(targetFile, "rw"); 46 //設置本地文件大小 47 file.setLength(fileSize); 48 file.close(); 49 for (int i = 0; i < threadNum; i++) 50 { 51 // 計算每條線程的下載的開始位置 52 int startPos = i * currentPartSize; 53 // 每個線程使用一個RandomAccessFile進行下載 54 RandomAccessFile currentPart = new RandomAccessFile(targetFile, 55 "rw"); 56 // 定位該線程的下載位置 57 currentPart.seek(startPos); 58 // 創建下載線程 59 threads[i] = new DownThread(startPos, currentPartSize, 60 currentPart); 61 // 啟動下載線程 62 threads[i].start(); 63 } 64 } 65 66 // 獲取下載的完成百分比 67 public double getCompleteRate() 68 { 69 // 統計多條線程已經下載的總大小 70 int sumSize = 0; 71 for (int i = 0; i < threadNum; i++) 72 { 73 sumSize += threads[i].length; 74 } 75 // 返回已經完成的百分比 76 return sumSize * 1.0 / fileSize; 77 } 78 79 private class DownThread extends Thread 80 { 81 // 當前線程的下載位置 82 private int startPos; 83 // 定義當前線程負責下載的文件大小 84 private int currentPartSize; 85 // 當前線程需要下載的文件塊 86 private RandomAccessFile currentPart; 87 // 定義已經該線程已下載的字節數 88 public int length; 89 90 public DownThread(int startPos, int currentPartSize, 91 RandomAccessFile currentPart) 92 { 93 this.startPos = startPos; 94 this.currentPartSize = currentPartSize; 95 this.currentPart = currentPart; 96 } 97 98 @Override 99 public void run() 100 { 101 try 102 { 103 URL url = new URL(path); 104 HttpURLConnection conn = (HttpURLConnection)url 105 .openConnection(); 106 conn.setConnectTimeout(5 * 1000); 107 conn.setRequestMethod("GET"); 108 conn.setRequestProperty( 109 "Accept", 110 "image/gif, image/jpeg, image/pjpeg, image/pjpeg, " 111 + "application/x-shockwave-flash, application/xaml+xml, " 112 + "application/vnd.ms-xpsdocument, application/x-ms-xbap, " 113 + "application/x-ms-application, application/vnd.ms-excel, " 114 + "application/vnd.ms-powerpoint, application/msword, */*"); 115 conn.setRequestProperty("Accept-Language", "zh-CN"); 116 conn.setRequestProperty("Charset", "UTF-8"); 117 InputStream inStream = conn.getInputStream(); 118 // 跳過startPos個字節,表明該線程只下載自己負責哪部分文件。 119 inStream.skip(this.startPos); 120 byte[] buffer = new byte[1024]; 121 int hasRead = 0; 122 // 讀取網絡數據,並寫入本地文件 123 while (length < currentPartSize 124 && (hasRead = inStream.read(buffer)) != -1) 125 { 126 currentPart.write(buffer, 0, hasRead); 127 // 累計該線程下載的總大小 128 length += hasRead; 129 } 130 currentPart.close(); 131 inStream.close(); 132 } 133 catch (Exception e) 134 { 135 e.printStackTrace(); 136 } 137 } 138 } 139 }View Code
程序中DownUtil類中的download()方法負責按如下步驟實現多線程下載:
1.創建URL對象
2.獲取指定URL對象所指向資源的大小(通過getContentLength()方法獲得),此處用到了URLConnection類,該類代表Java應用程序和URL之間的通信鏈接。
3.在本地磁盤上創建一個與網絡資源具有相同大小的空文件。
4.計算每個線程應該下載網絡資源的哪個部分(從哪個字節開始,到哪個字節結束)
5.依次創建、啟動多個線程來下載網絡資源的指定部分
上面程序已經實現了多線程下載的核心代碼,若要實現斷點下載,則需要額外增加一個配置文件(讀者可以發現,所有的斷點下載工具都會在下載開始時生成兩個文件:一個是與網絡資源具有相同大小的空文件,一個是配置文件),該配置文件分別記錄每個線程已經下載到哪個字節,當網絡斷開後再次下載時,每個線程根據配置文件裏記錄的位置向後下載即可。
1 public class MultiThreadDown 2 { 3 public static void main(String[] args) throws Exception 4 { 5 // 初始化DownUtil對象 6 final DownUtil downUtil = new DownUtil("http://www.crazyit.org/" 7 + "attachments/month_1403/1403202355ff6cc9a4fbf6f14a.png" 8 , "ios.png", 4); 9 // 開始下載 10 downUtil.download(); 11 new Thread(() -> { 12 while(downUtil.getCompleteRate() < 1) 13 { 14 // 每隔0.1秒查詢一次任務的完成進度, 15 // GUI程序中可根據該進度來繪制進度條 16 System.out.println("已完成:" 17 + downUtil.getCompleteRate()); 18 try 19 { 20 Thread.sleep(1000); 21 } 22 catch (Exception ex){} 23 } 24 }).start(); 25 } 26 }View Code
通常創建一個和URL的連接,並發送請求、讀取此URL引用的資源需要如下幾個步驟:
1.通過調用URL對象的openConnection()方法來創建URLConnection對象
2.設置URLConnection的參數和普通請求屬性
3.若只是發送GET方式請求,則使用connect()方法建立和遠程資源之間的實際連接即可;若要發送POST方式的請求,則需要獲取URLConnection實例對應的輸出流來發
送請求參數
4.遠程資源變為可用,程序可以訪問遠程資源的頭字段或通過輸入流讀取遠程資源的數據
若既要使用輸入流讀取URLConnection響應的內容又要使用輸出流發送請求參數,則一定要先使用輸出流,在使用輸入流。
下面程序示範了如何向Web站點發送GET請求、POST請求,並從Web站點取得響應:
1 import java.net.URL; 2 import java.net.URLConnection; 3 import java.util.Map; 4 import java.util.List; 5 import java.io.BufferedReader; 6 import java.io.InputStreamReader; 7 import java.io.PrintWriter; 8 9 public class GetPostTest 10 { 11 /** 12 * 向指定URL發送GET方法的請求 13 * @param url 發送請求的URL 14 * @param param 請求參數,格式滿足name1=value1&name2=value2的形式。 15 * @return URL所代表遠程資源的響應 16 */ 17 public static String sendGet(String url , String param) 18 { 19 String result = ""; 20 String urlName = url + "?" + param; 21 try 22 { 23 URL realUrl = new URL(urlName); 24 // 打開和URL之間的連接 25 URLConnection conn = realUrl.openConnection(); 26 // 設置通用的請求屬性 27 conn.setRequestProperty("accept", "*/*"); 28 conn.setRequestProperty("connection", "Keep-Alive"); 29 conn.setRequestProperty("user-agent" 30 , "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); 31 // 建立實際的連接 32 conn.connect(); 33 // 獲取所有響應頭字段 34 Map<String, List<String>> map = conn.getHeaderFields(); 35 // 遍歷所有的響應頭字段 36 for (String key : map.keySet()) 37 { 38 System.out.println(key + "--->" + map.get(key)); 39 } 40 try( 41 // 定義BufferedReader輸入流來讀取URL的響應 42 BufferedReader in = new BufferedReader( 43 new InputStreamReader(conn.getInputStream() , "utf-8"))) 44 { 45 String line; 46 while ((line = in.readLine())!= null) 47 { 48 result += "\n" + line; 49 } 50 } 51 } 52 catch(Exception e) 53 { 54 System.out.println("發送GET請求出現異常!" + e); 55 e.printStackTrace(); 56 } 57 return result; 58 } 59 /** 60 * 向指定URL發送POST方法的請求 61 * @param url 發送請求的URL 62 * @param param 請求參數,格式應該滿足name1=value1&name2=value2的形式。 63 * @return URL所代表遠程資源的響應 64 */ 65 public static String sendPost(String url , String param) 66 { 67 String result = ""; 68 try 69 { 70 URL realUrl = new URL(url); 71 // 打開和URL之間的連接 72 URLConnection conn = realUrl.openConnection(); 73 // 設置通用的請求屬性 74 conn.setRequestProperty("accept", "*/*"); 75 conn.setRequestProperty("connection", "Keep-Alive"); 76 conn.setRequestProperty("user-agent", 77 "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); 78 // 發送POST請求必須設置如下兩行 79 conn.setDoOutput(true); 80 conn.setDoInput(true); 81 try( 82 // 獲取URLConnection對象對應的輸出流 83 PrintWriter out = new PrintWriter(conn.getOutputStream())) 84 { 85 // 發送請求參數 86 out.print(param); 87 // flush輸出流的緩沖 88 out.flush(); 89 } 90 try( 91 // 定義BufferedReader輸入流來讀取URL的響應 92 BufferedReader in = new BufferedReader(new InputStreamReader 93 (conn.getInputStream() , "utf-8"))) 94 { 95 String line; 96 while ((line = in.readLine())!= null) 97 { 98 result += "\n" + line; 99 } 100 } 101 } 102 catch(Exception e) 103 { 104 System.out.println("發送POST請求出現異常!" + e); 105 e.printStackTrace(); 106 } 107 return result; 108 } 109 // 提供主方法,測試發送GET請求和POST請求 110 public static void main(String args[]) 111 { 112 // 發送GET請求 113 String s = GetPostTest.sendGet("http://localhost:8888/abc/a.jsp" 114 , null); 115 System.out.println(s); 116 // 發送POST請求 117 String s1 = GetPostTest.sendPost("http://localhost:8888/abc/login.jsp" 118 , "name=crazyit.org&pass=leegang"); 119 System.out.println(s1); 120 } 121 }View Code
這一部分需要創建Web應用,現在我還不會。但是會在接下來學習《輕量級Java EE企業應用實戰》的時候創建Web應用。Web應用的代碼我上傳到GitHub網站(會在文章最後寫出),是一個abc的文件夾。
基於TCP協議的網絡編程:
使用ServerSocket創建TCP服務器端:
Java中能接收其他通信實體連接請求的類是ServerSocket,ServerSocket對象用於監聽來自客戶端的Socket連接,若沒有連接,它將一直處於等待狀態。ServerSocket包含
一個監聽來自客戶端連接請求的方法。
1.Socket accept():若接收到一個客戶端Socket的連接請求,該方法返回一個與客戶端Socket對應的Socket;否則該方法一直處於等待狀態,線程也被阻塞
使用Socket進行通信:
1 import java.net.ServerSocket; 2 import java.net.Socket; 3 import java.io.PrintStream; 4 import java.io.IOException; 5 6 public class Server 7 { 8 public static void main(String[] args) 9 throws IOException 10 { 11 // 創建一個ServerSocket,用於監聽客戶端Socket的連接請求 12 ServerSocket ss = new ServerSocket(30000); 13 // 采用循環不斷接受來自客戶端的請求 14 while (true) 15 { 16 // 每當接受到客戶端Socket的請求,服務器端也對應產生一個Socket 17 Socket s = ss.accept(); 18 // 將Socket對應的輸出流包裝成PrintStream 19 PrintStream ps = new PrintStream(s.getOutputStream()); 20 // 進行普通IO操作 21 ps.println("您好,您收到了服務器的新年祝福!"); 22 // 關閉輸出流,關閉Socket 23 ps.close(); 24 s.close(); 25 } 26 } 27 }View Code
1 import java.net.Socket; 2 import java.io.BufferedReader; 3 import java.io.InputStreamReader; 4 import java.io.IOException; 5 6 public class Client 7 { 8 public static void main(String[] args) 9 throws IOException 10 { 11 Socket socket = new Socket("127.0.0.1" , 30000); // ① 12 // 將Socket對應的輸入流包裝成BufferedReader 13 BufferedReader br = new BufferedReader( 14 new InputStreamReader(socket.getInputStream())); 15 // 進行普通IO操作 16 String line = br.readLine(); 17 System.out.println("來自服務器的數據:" + line); 18 // 關閉輸入流、socket 19 br.close(); 20 socket.close(); 21 } 22 }View Code
在Windows系統下分別用兩個cmd,一個運行Server端,一個運行Client端,先運行Server端:
第十七章.網絡編程