Jakarta Commons HttpClient 學習(入門)

1、HttpClient的功能


基於標準,純正java,實現了http1.0和1.1。

在一個可擴展的OO框架內,實現了HTTP的全部方法(GET, POST,
PUT, DELETE, HEAD, OPTIONS, and TRACE)

支持HTTPS(ssl上的HTTP)的加密操作

透明地穿過HTTP代理建立連接

通過CONNECT方法,利用通過建立穿過HTTP代理的HTTPS連接

利用本地Java socket,透明地穿過SOCKS(版本5和4)代理建立連接

支持利用Basic、Digest和NTLM加密的認證

支持用於上傳大文件的Multi-Part表單POST方法

插件式安全socket實現,易於使用第三方的解決方案

連接管理,支持多線程應用,支持設定單個主機總連接和最高連接數量,自動檢測和關閉失效連接

直接將請求信息流送到服務器的端口

直接讀取從服務器的端口送出的應答信息

支持HTTP/1.0中用KeepAlive和HTTP/1.1中用persistance設置的持久連接

直接訪問由服務器送出的應答代碼和頭部信息

可設置連接超時時間

JAVA手機網[www.cnjm.net]



HttpMethods 實現Command Pattern,以允許並行請求或高效連接複用

遵循the Apache Software License協議,源碼免費可得



2、預備工作

  對jre1.3.*,如果要HttpClient支持https,則需要下載並安裝jsse和jce.安裝的步驟如下:
1)下載jsse和jce.
2)檢查CLASSPATH中沒有與jsse和jce相關的jar包
3)將 US_export_policy.jar、local_policy.jar、jsse.jar、jnet.jar、jce1_2_x.jar、sunjce_provider.jar、jcert.jar複製到目錄:
UNIX:$JDK_HOME/jre/lib/ext
Windows:%JDK_HOME%/jre/lib/ext
4)修改下述目錄下的java.security文件。
UNIX:$JDK_HOME/jre/lib/security/
Windows:%JDK_HOME%/jre/lib/security/
5)

JAVA手機網[www.cnjm.net]

#

JAVA手機網[www.cnjm.net]

# List of providers and their preference orders:
#
security.provider.1=sun.security.provider.Sun
security.provider.2=com.sun.rsajca.Provider
改爲:
#
# List of providers and their preference orders:
#
security.provider.1=com.sun.crypto.provider.SunJCE
security.provider.2=sun.security.provider.Sun
security.provider.3=com.sun.rsajca.Provider
security.provider.4=com.sun.net.ssl.internal.ssl.Provider

JAVA手機網[www.cnjm.net]

  HttpClient還要求安裝commons-logging,下面跟httpclient一塊安裝。

3、取得源碼


cvs -d :pserver:[email protected]:/home/cvspublic login
password: anoncvs
cvs -d :pserver:[email protected]:/home/cvspublic checkout jakarta-commons/logging
cvs -d :pserver:[email protected]:/home/cvspublic checkout jakarta-commons/httpclient

  編譯:
cd jakarta-commons/logging
ant dist
cp dis/*.jar ../httpclient/lib/
cd ../httpclient
ant dist


4、使用HttpClient編程的基本步聚


創建 HttpClient 的一個實例.

創建某個方法(DeleteMethod,EntityEnclosingMethod,ExpectContinueMethod,GetMethod,HeadMethod,MultipartPostMethod,OptionsMethod,PostMethod,PutMethod,TraceMethod)的一個實例,一般可用要目標URL爲參數。

讓 HttpClient 執行這個方法.

讀取應答信息.

釋放連接.

處理應答.

JAVA手機網[www.cnjm.net]



  在執行方法的過程中,有兩種異常,一種是HttpRecoverableException,表示偶然性錯誤發生,一般再試可能成功,另一種是IOException,嚴重錯誤。
  這兒有這個教程中的一個例程,可以下載。

JAVA手機網[www.cnjm.net]



5、認證

  HttpClient三種不同的認證方案: Basic, Digest and NTLM. 這些方案可用於服務器或代理對客戶端的認證,簡稱服務器認證或代理認證。
1)服務器認證(Server Authentication)
  HttpClient處理服務器認證幾乎是透明的,僅需要開發人員提供登錄信息(login credentials)。登錄信息保存在HttpState類的實例中,可以通過 setCredentials(String realm, Credentials cred)和getCredentials(String realm)來獲取或設置。注意,設定對非特定站點訪問所需要的登錄信息,將realm參數置爲null. HttpClient內建的自動認證,可以通過HttpMethod類的setDoAuthentication(boolean doAuthentication)方法關閉,而且這次關閉隻影響HttpMethod當前的實例。
  搶先認證(Preemptive Authentication)可以通過下述方法打開.
client.getState().setAuthenticationPreemptive(true);
  在這種模式時,HttpClient會主動將basic認證應答信息傳給服務器,即使在某種情況下服務器可能返回認證失敗的應答,這樣做主要是爲了減少連接的建立。爲使每個新建的 HttpState實例都實行搶先認證,可以如下設置系統屬性。
setSystemProperty(Authenticator.PREEMPTIVE_PROPERTY, "true");

Httpclient實現的搶先認證遵循rfc2617.
2)代理認證(proxy authentication)
  除了登錄信息需單獨存放以外,代理認證與服務器認證幾乎一致。用 setProxyCredentials(String realm, Credentials cred)和 getProxyCredentials(String realm)設、取登錄信息。
3)認證方案(authentication schemes)
  Basic
  是HTTP中規定最早的也是最兼容(?)的方案,遺憾的是也是最不安全的一個方案,因爲它以明碼傳送用戶名和密碼。它要求一個UsernamePasswordCredentials實例,可以指定服務器端的訪問空間或採用默認的登錄信息。
  Digest
  是在HTTP1.1中增加的一個方案,雖然不如Basic得到的軟件支持多,但還是有廣泛的使用。Digest方案比Basic方案安全得多,因它根本就不通過網絡傳送實際的密碼,傳送的是利用這個密碼對從服務器傳來的一個隨機數(nonce)的加密串。它要求一個UsernamePasswordCredentials實例,可以指定服務器端的訪問空間或採用默認的登錄信息。
  NTLM
  這是HttpClient支持的最複雜的認證協議。它M$設計的一個私有協議,沒有公開的規範說明。一開始由於設計的缺陷,NTLM的安全性比Digest差,後來經過一個ServicePack補丁後,安全性則比較Digest高。NTLM需要一個NTCredentials實例. 注意,由於NTLM不使用訪問空間(realms)的概念,HttpClient利用服務器的域名作訪問空間的名字。還需要注意,提供給NTCredentials的用戶名,不要用域名的前綴 - 如: "adrian" 是正確的,而 "DOMAIN/adrian" 則是錯的.
  NTLM認證的工作機制與basic和digest有很大的差別。這些差別一般由HttpClient處理,但理解這些差別有助避免在使用NTLM認證時出現錯誤。

從HttpClientAPI的角度來看,NTLM與其它認證方式一樣的工作,差別是需要提供'NTCredentials'實例而不是'UsernamePasswordCredentials'(其實,前者只是擴展了後者)

對NTLM認證,訪問空間是連接到的機器的域名,這對多域名主機會有一些麻煩.只有HttpClient連接中指定的域名纔是認證用的域名。建議將realm設爲null以使用默認的設置。

NTLM只是認證了一個連接而不是一請求,所以每當一個新的連接建立就要進行一次認證,且在認證的過程中保持連接是非常重要的。 因此,NTLM不能同時用於代理認證和服務器認證,也不能用於http1.0連接或服務器不支持持久連接的情況。


6、重定向

  由於技術限制,以及爲保證2.0發佈版API的穩定,HttpClient還不能自動處重定向,但對重定向到同一主機、同一端口且採用同一協議的情況HttpClient可以支持。不能自動的處理的情況,包括需要人工交互的情況,或超出httpclient的能力。
  當服務器重定向指令指到不同的主機時,HttpClient只是簡單地將重定向狀態碼作爲應答狀態。所有的300到399(包含兩端)的返回碼,都表示是重定向應答。常見的有:

301 永久移動. HttpStatus.SC_MOVED_PERMANENTLY

JAVA手機網[www.cnjm.net]

302 臨時移動. HttpStatus.SC_MOVED_TEMPORARILY

303 See Other. HttpStatus.SC_SEE_OTHER

307 臨時重定向. HttpStatus.SC_TEMPORARY_REDIRECT

  當收到簡單的重定向時,程序應從HttpMethod對象中抽取新的URL並將其下載。另外,限制一下重定向次數是個好的主意,這可以避免遞歸循環。新的URL可以從頭字段Location中抽取,如下:
String redirectLocation;
Header locationHeader = method.getResponseHeader("location");
if (locationHeader != null) {
redirectLocation = locationHeader.getValue();
} else {
// The response is invalid and did not provide the new location for
// the resource. Report an error or possibly handle the response
// like a 404 Not Found error.
}

特殊重定向:

300 多重選擇. HttpStatus.SC_MULTIPLE_CHOICES

304 沒有改動. HttpStatus.SC_NO T_MODIFIED

305 使用代理. HttpStatus.SC_USE_PROXY


  

7、字符編碼(character encoding)

  一個HTTP協議的請求或應答的頭部(在http協議中,數據包分爲兩部分,一部分是頭部,由一些名值對構成,一部分是主體(body),是真正傳辦理的數據(如HTML頁面等)),必須以US-ASCII編碼,這是因爲頭部不傳數據而只描述被要傳輸的數據的一些信息,一個例外是cookie,它是數據但是通過頭部進行傳輸的,所以它也要用US-ASCII編碼。
  HTTP數據包的主體部分,可以用任何一種方式進行編碼,默認是ISO-8859-1,具體可以用頭部字段Content-Type指定。可以利用 addRequestHeader方法,設定編碼方式;用 getResponseCharSet取得編碼方式。對HTML或XML等類型的文檔,它們的本身的Content-Type也可以指定編碼方式,主要區分兩者的作用範圍以得到正確實的解碼。
  URL的編碼標準,由RFC1738指定爲,只能是由可打印8位/字節的us-ascii字符組成,80-ff不是us-ascii字符,而00-1F是控制字符,這兩個區域中用的字符都須加以編碼(encoded)。
  
8、Cookies

   HttpClient能自動管理cookie,包括允許服務器設置cookie並在需要的時候自動將cookie返回服務器,它也支持手工設置cookie後發送到服務器端。不幸的是,對如何處理cookie,有幾個規範互相沖突:Netscape Cookie 草案, RFC2109, RFC2965,而且還有很大數量的軟件商的cookie實現不遵循任何規範. 爲了處理這種狀況,HttpClient提供了策略驅動的cookie管理方式。HttpClient支持的cookie規範有:
Netscape cookie草案,是最早的cookie規範,基於rfc2109。儘管這個規範與rc2109有較大的差別,這樣做可以與一些服務器兼容。

rfc2109,是w3c發佈的第一個官方cookie規範。理論上講,所有的服務器在處理cookie(版本1)時,都要遵循此規範,正因如此,HttpClient將其設爲默認的規範。遺憾的是,這個規範太嚴格了,以致很多服務器不正確的實施了該規範或仍在作用Netscape規範。在這種情況下,應使用兼容規範。

兼容性規範,設計用來兼容儘可能多的服務器,即使它們並沒有遵循標準規範。當解析cookie出現問題時,應考慮採用兼容性規範。

   RFC2965規範暫時沒有被HttpClient支持(在以後的版本爲會加上),它定義了cookie版本2,並說明了版本1cookie的不足,RFC2965有意有久取代rfc2109.
  在HttpClient中,有兩種方法來指定cookie規範的使用,
HttpClient client = new HttpClient();
client.getState().setCookiePolicy(CookiePolicy.COMPATIBILITY);
這種方法設置的規範只對當前的HttpState有效,參數可取值CookiePolicy.COMPATIBILITY,CookiePolicy.NETSCAPE_DRAFT或CookiePolicy.RFC2109。

System.setProperty("apache.commons.httpclient.cookiespec", "COMPATIBILITY");
此法指的規範,對以後每個新建立的HttpState對象都有效,參數可取值"COMPATIBILITY","NETSCAPE_DRAFT"或"RFC2109"。

JAVA手機網[www.cnjm.net]

  常有不能解析cookie的問題,但更換到兼容規範大都能解決。

  
9、使用HttpClient遇到問題怎麼辦?


用一個瀏覽器訪問服務器,以確認服務器應答正常

如果在使代理,關掉代理試試

另找一個服務器來試試(如果運行着不同的服務器軟件更好)

檢查代碼是否按教程中講的思路編寫

設置log級別爲debug,找出問題出現的原因

打開wiretrace,來追蹤客戶端與服務器的通信,以確實問題出現在什麼地方

用telnet或netcat手工將信息發送到服務器,適合於猜測已經找到了原因而進行試驗時

將netcat以監聽方式運行,用作服務器以檢查httpclient如何處理應答的。

利用最新的httpclient試試,bug可能在最新的版本中修復了

向郵件列表求幫助

向bugzilla報告bug.

  

10、SSL

  藉助Java Secure Socket Extension (JSSE),HttpClient全面支持Secure Sockets Layer (SSL)或IETF Transport Layer Security (TLS)協議上的HTTP。JSSE已經jre1.4及以後的版本中,以前的版本則需要手工安裝設置,具體過程參見Sun網站或本學習筆記。
  HttpClient中使用SSL非常簡單,參考下面兩個例子:
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://www.verisign.com/");
httpclient.executeMethod(httpget);
System.out.println(httpget.getStatusLine().toString());
,如果通過需要授權的代理,則如下:
HttpClient httpclient = new HttpClient();
httpclient.getHostConfiguration().setProxy("myproxyhost", 8080);

JAVA手機網[www.cnjm.net]

httpclient.getState().setProxyCredentials("my-proxy-realm", " myproxyhost",
new UsernamePasswordCredentials("my-proxy-username", "my-proxy-password"));
GetMethod httpget = new GetMethod("https://www.verisign.com/");
httpclient.executeMethod(httpget);

JAVA手機網[www.cnjm.net]

System.out.println(httpget.getStatusLine().toString());

  在HttpClient中定製SSL的步驟如下:

提供了一個實現了org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory接口的socket factory。這個 socket factory負責打一個到服務器的端口,使用標準的或第三方的SSL函數庫,並進行象連接握手等初始化操作。通常情況下,這個初始化操作在端口被創建時自動進行的。

JAVA手機網[www.cnjm.net]

實例化一個org.apache.commons.httpclient.protocol.Protocol對象。創建這個實例時,需要一個合法的協議類型(如https),一個定製的socket factory,和一個默認的端中號(如https的443端口).
Protocol myhttps = new Protocol("https", new MySSLSocketFactory(), 443);
然後,這個實例可被設置爲協議的處理器。
HttpClient httpclient = new HttpClient();
httpclient.getHostConfiguration().setHost("www.whatever.com", 443, myhttps);

JAVA手機網[www.cnjm.net]

GetMethod httpget = new GetMethod("/");
httpclient.executeMethod(httpget);


通過調用Protocol.registerProtocol方法,將此定製的實例,註冊爲某一特定協議的默認的處理器。由此,可以很方便地定製自己的協議類型(如myhttps)。
Protocol.registerProtocol("myhttps",
new Protocol("https", new MySSLSocketFactory(), 9443));
...
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("myhttps://www.whatever.com/");
httpclient.executeMethod(httpget);
如果想用自己定製的處理器取代https默認的處理器,只需要將其註冊爲"https"即可。
Protocol.registerProtocol("https",

JAVA手機網[www.cnjm.net]

new Protocol("https", new MySSLSocketFactory(), 443));
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://www.whatever.com/");
httpclient.executeMethod(httpget);

JAVA手機網[www.cnjm.net]

 

JAVA手機網[www.cnjm.net]

  已知的限制和問題

JAVA手機網[www.cnjm.net]

持續的SSL連接在Sun的低於1.4JVM上不能工作,這是由於JVM的bug造成。

通過代理訪問服務器時,非搶先認證( Non-preemptive authentication)會失敗,這是由於HttpClient的設計缺陷造成的,以後的版本中會修改。

JAVA手機網[www.cnjm.net]

  遇到問題的處理
  很多問題,特別是在jvm低於1.4時,是由jsse的安裝造成的。
  下面的代碼,可作爲最終的檢測手段。

JAVA手機網[www.cnjm.net]


import java.io.BufferedReader;

JAVA手機網[www.cnjm.net]

import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;

import javax.net.ssl.SSLSocketFactory;

public class Test {

public static final String TARGET_HTTPS_SERVER = "www.verisign.com";
public static final int TARGET_HTTPS_PORT = 443;

public static void main(String[] args) throws Exception {

JAVA手機網[www.cnjm.net]


Socket socket = SSLSocketFactory.getDefault().
createSocket(TARGET_HTTPS_SERVER, TARGET_HTTPS_PORT);
try {
Writer out = new OutputStreamWriter(
socket.getOutputStream(), "ISO-8859-1");
out.write("GET / HTTP/1.1/r/n");
out.write("Host: " + TARGET_HTTPS_SERVER + ":" +
TARGET_HTTPS_PORT + "/r/n");
out.write("Agent: SSL-TEST/r/n");
out.write("/r/n");
out.flush();
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "ISO-8859-1"));
String line = null;

JAVA手機網[www.cnjm.net]

while ((line = in.readLine()) != null) {
System.out.println(line);
}
} finally {
socket.close();
}
}
}


  
11、httpclient的多線程處理

  使用多線程的主要目的,是爲了實現並行的下載。在httpclient運行的過程中,每個http協議的方法,使用一個HttpConnection實例。由於連接是一種有限的資源,每個連接在某一時刻只能供一個線程和方法使用,所以需要確保在需要時正確地分配連接。HttpClient採用了一種類似jdbc連接池的方法來管理連接,這個管理工作由 MultiThreadedHttpConnectionManager完成。
MultiThreadedHttpConnectionManager connectionManager =
new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);
此是,client可以在多個線程中被用來執行多個方法。每次調用HttpClient.executeMethod() 方法,都會去鏈接管理器申請一個連接實例,申請成功這個鏈接實例被簽出(checkout),隨之在鏈接使用完後必須歸還管理器。管理器支持兩個設置:maxConnectionsPerHost 每個主機的最大並行鏈接數,默認爲2
maxTotalConnections 客戶端總並行鏈接最大數,默認爲20

  管理器重新利用鏈接時,採取早歸還者先重用的方式(least recently used approach)。
  由於是使用HttpClient的程序而不是HttpClient本身來讀取應答包的主體,所以HttpClient無法決定什麼時間連接不再使用了,這也就要求在讀完應答包的主體後必須手工顯式地調用releaseConnection()來釋放申請的鏈接。
MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);
...
// 在某個線程中。
GetMethod get = new GetMethod("http://jakarta.apache.org/");
try {
client.executeMethod(get);
// print response to stdout
System.out.println(get.getResponseBodyAsStream());

JAVA手機網[www.cnjm.net]

} finally {
// be sure the connection is released back to the connection
// manager
get.releaseConnection();

JAVA手機網[www.cnjm.net]

}
對每一個HttpClient.executeMethod須有一個method.releaseConnection()與之匹配.

12、HTTP方法


  HttpClient支持的HTTP方法有8種,下面分述之。

  1、Options

  HTTP方法Options用來向服務器發送請求,希望獲得針對由請求URL(request url)標誌的資源在請求/應答的通信過程可以使用的功能選項。通過這個方法,客戶端可以在採取具體行動之前,就可對某一資源決定採取什麼動作和/或以及一些必要條件,或者瞭解服務器提供的功能。這個方法最典型的應用,就是用來獲取服務器支持哪些HTTP方法。
  HttpClient中有一個類叫OptionsMethod,來支持這個HTTP方法,利用這個類的getAllowedMethods方法,就可以很簡單地實現上述的典型應用。


OptionsMethod options = new OptionsMethod("http://jakarta.apache.org");
// 執行方法並做相應的異常處理

JAVA手機網[www.cnjm.net]

...
Enumeration allowedMethods = options.getAllowedMethods();

JAVA手機網[www.cnjm.net]

options.releaseConnection();

JAVA手機網[www.cnjm.net]

 

JAVA手機網[www.cnjm.net]

  2、Get

JAVA手機網[www.cnjm.net]


   HTTP方法GET用來取回請求URI(request-URI)標誌的任何信息(以實體(entity)的形式),"get"這個單詞本意就是”獲取“的意思。如果請求URI指向的一個數據處理過程,那這個過程生成的數據,在應答中以實體的形式被返回,而不是將這個過程的代碼的返回。
  如果HTTP包中含有If-ModifiedSince, If-Unmodified-Since, If-Match, If-None-Match, 或 If-Range等頭字段,則GET也就變成了”條件GET“,即只有滿足上述字段描述的條件的實體才被取回,這樣可以減少一些非必需的網絡傳輸,或者減少爲獲取某一資源的多次請求(如第一次檢查,第二次下載)。(一般的瀏覽器,都有一個臨時目錄,用來緩存一些網頁信息,當再次瀏覽某個頁面的時候,只下載那些修改過的內容,以加快瀏覽速度,就是這個道理。至於檢查,則常用比GET更好的方法HEAD來實現。)如果HTTP包中含有Range頭字段,那麼請求URI指定的實體中,只有決定範圍條件的那部分才被取回來。(用過多線程下載工具的朋友,可能比較容易理解這一點)

JAVA手機網[www.cnjm.net]

  這個方法的典型應用,用來從web服務器下載文檔。HttpClient定義了一個類叫GetMethod來支持這個方法,用GetMethod類中getResponseBody, getResponseBodyAsStream 或 getResponseBodyAsString函數就可以取到應答包包體中的文檔(如HTML頁面)信息。這這三個函數中,getResponseBodyAsStream通常是最好的方法,主要是因爲它可以避免在處理下載的文檔之前緩存所有的下載的數據。

GetMethod get = new GetMethod("http://jakarta.apache.org");
// 執行方法,並處理失敗的請求.
...

JAVA手機網[www.cnjm.net]

InputStream in = get.getResponseBodyAsStream();
// 利用輸入流來處理信息。
get.releaseConnection();

  對GetMethod的最常見的不正確的使用,是沒有將全部的應答主體的數據讀出來。還有,必須注意要手工明確地將鏈接釋放。

  3、Head

  HTTP的Head方法,與Get方法完全一致,唯一的差別是服務器不能在應答包中包含主體(message-body),而且一定不能包含主體。使用這個方法,可以使得客戶無需將資源下載回就可就以得到一些關於它的基本信息。這個方法常用來檢查超鏈的可訪問性以及資源最近有沒有被修改。
  HTTP的head方法最典型的應用,是獲取資源的基本信息。HttpClient定義了HeadMethod類支持這個方法,HeadMethod類與其它*Method類一樣,用 getResponseHeaders()取回頭部信息,而沒有自己的特殊方法。

HeadMethod head = new HeadMethod("http://jakarta.apache.org");
// 執行方法,並處理失敗的請求.

JAVA手機網[www.cnjm.net]

...
// 取回應答包的頭字段信息.
Header[] headers = head.getResponseHeaders();

// 只取回最後修改日期字段的信息.
String lastModified = head.getResponseHeader("last-modified").getValue();



  4、Post

  Post在英文有“派駐”的意思,HTTP方法POST就是要求服務器接受請求包中的實體,並將其作爲請求URI的下屬資源。從本質上說,這意味着服務器要保存這個實體信息,而且通常由服務器端的程序進行處理。Post方法的設計意圖,是要以一種統一的方式實現下列功能:
對已有的資源做評註

將信息發佈到BBS、新聞組、郵件列表,或類似的文章組中

將一塊數據,提交給數據處理進程

通過追加操作,來擴展一個數據庫
  這些都操作期待着在服務器端產生一定的“副作用”,如修改了數據庫等。
  HttpClient定義PostMethod類以支持該HTTP方法,在httpclient中,使用post方法有兩個基本的步驟:爲請求包準備數據,然後讀取服務器來的應答包的信息。通過調用 setRequestBody()函數,來爲請求包提供數據,它可以接收三類參數:輸入流、名值對數組或字符串。至於讀取應答包需要調用 getResponseBody* 那一系列的方法,與GET方法處理應答包的方法相同。
  常見問題是,沒有將全部應答讀取(無論它對程序是否有用),或沒有釋放鏈接資源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章