HttpClient 的Timeout waiting for connection from pool


Timeout waiting for connection from pool 異常

httpClient大家用到地方會很多,先簡單描述一下幾個關鍵配置的意義 

httpClient版本為4.5.1

maxTotal:整個連接池的最大支持連接數

defaultMaxPerRoute:當前主機到目的主機的一個路由,主要作用在通過httpClient轉發請求到不同的目的主機的連接數限制,是maxTotal的一個細分;比如:

 maxtTotal=400 defaultMaxPerRoute=200

而我只連接到http://www.baidu.com時,到這個主機的並發最多只有200;而不是400;

而我連接到http://www.baidu.com 和 http://www.jd.com時,到每個主機的並發最多只有200;即加起來是400(但不能超過400);所以起作用的設置是defaultMaxPerRoute。

connectionRequestTimeout:從PoolingHttpClientConnectionManager中獲取連接超時時間(必填,如果未配置將一直等待從連接池中獲取可用連接,此值不易太大)

connectTimeout:和目的主機建立連接的超時時間

socketTimeout:從目的主機讀取數據超時時間

keepAliveDuration:連接存活時間(長連接使用)

事故現場:

 public static final int connTimeout = 5000;
  public static final int readTimeout = 5000;
  public static final int connRequestTimeout = 1000;
 
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
 cm.setMaxTotal(3000);
 cm.setDefaultMaxPerRoute(20);
 HttpClient client = HttpClients.custom().setConnectionManager(cm).build();

public static String postFormRedirect(String url, Map<String, String> params,
      Map<String, String> headers, Integer connTimeout, Integer readTimeout)
      throws ConnectTimeoutException, SocketTimeoutException, Exception {
    String resultStr = "";
    HttpClient client = null;
    HttpResponse res = null;
    HttpPost post = new HttpPost(url);
    try {
      if (params != null && !params.isEmpty()) {
        List<NameValuePair> formParams = new ArrayList<org.apache.http.NameValuePair>();
        Set<Entry<String, String>> entrySet = params.entrySet();
        for (Entry<String, String> entry : entrySet) {
          formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
        }
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
        post.setEntity(entity);
      }

      if (headers != null && !headers.isEmpty()) {
        for (Entry<String, String> entry : headers.entrySet()) {
          post.addHeader(entry.getKey(), entry.getValue());
        }
      }
      // 設置參數
      Builder customReqConf = RequestConfig.custom();
      customReqConf.setConnectTimeout(connTimeout);
      customReqConf.setSocketTimeout(readTimeout);
      customReqConf.setConnectionRequestTimeout(connRequestTimeout);
      post.setConfig(customReqConf.build());
      if (url.startsWith("https")) {
        // 執行 Https 請求.
        client = createSSLInsecureClient();
        res = client.execute(post);
      } else {
        // 執行 Http 請求.
        client = HttpClientUtils.client;
        res = client.execute(post);
      }
      int status = res.getStatusLine().getStatusCode();
      if (status == HttpStatus.SC_OK) {
        resultStr = IOUtils.toString(res.getEntity().getContent(), "UTF-8");
      } else if (status == HttpStatus.SC_MOVED_PERMANENTLY || // 處理轉向
          status == HttpStatus.SC_MOVED_TEMPORARILY || status == HttpStatus.SC_CREATED) { // 從頭中取出轉向地址
        Header locationHeader = res.getFirstHeader("location");
        if (locationHeader != null) {
          java.net.URI uri = new java.net.URI(locationHeader.getValue());
          post.setURI(uri);
          res = client.execute(post);
          resultStr = IOUtils.toString(res.getEntity().getContent(), "UTF-8");
        }
      }
      return resultStr;
    } finally {
      if (res != null) {
        EntityUtils.consume(res.getEntity());
      }
      post.releaseConnection();
      if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
        ((CloseableHttpClient) client).close();
      }
    }
  }

 

代碼邏輯很簡單,拿到一個url進行轉發請求,如果請求結果需要跳轉,則主動請求需要跳轉的地址,將跳轉地址的返回結果進行返回

defaultMaxPerRoute設置的20,maxTotal設置的3000;

寫個測試類循環調用該結果20次

ST-21142-9Na2eefb1nWWdcgSXW56-cas01.example.org,:20
0耗時:944
ST-21143-VFk6eu9gLZ5BrX3h3aLs-cas01.example.org,:20
1耗時:6
ST-21144-jf7cCBv4VPNpqjBsWOUJ-cas01.example.org,:20
2耗時:5
ST-21145-IDIIhlMdTjSDBPcGa0NN-cas01.example.org,:20
3耗時:6
ST-21146-2WGXIdfeQOarOKHhEcw3-cas01.example.org,:20
4耗時:5
ST-21147-tg6r1BGaEfu4GSkEbiX6-cas01.example.org,:20
5耗時:6
ST-21148-eLV2nq2EkW4CK7GCNFDk-cas01.example.org,:20
6耗時:5
ST-21149-xG3bXAdNFr9aNuBGtLRq-cas01.example.org,:20
7耗時:5
ST-21150-yL4ed9qT2fBmjXPbsswr-cas01.example.org,:20
8耗時:5
ST-21151-scWrQqQh25PRs3TX2mrs-cas01.example.org,:20
9耗時:6
ST-21152-KAwooFwbbZUBvF7MWNbK-cas01.example.org,:20
10耗時:5
ST-21153-HPC6gdigioPr1mqgOa4f-cas01.example.org,:20
11耗時:5
ST-21154-9ZgIqgfKUUiglfgymEie-cas01.example.org,:20
12耗時:5
ST-21155-mIDRgL1fAjpPoRuH1Lfg-cas01.example.org,:20
13耗時:5
ST-21156-SjdNbDbVYdC5rMmafMhp-cas01.example.org,:20
14耗時:5
ST-21157-3dFJcfbhffvW5fHryshZ-cas01.example.org,:20
15耗時:6
ST-21158-g3VnDke2UgqSEWHX1e1P-cas01.example.org,:20
16耗時:5
ST-21159-XtiDbc64IUfl0dZLOgD5-cas01.example.org,:20
17耗時:5
ST-21160-mbmUUbiabfvxh7iA2XWc-cas01.example.org,:20
18耗時:4
org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.leaseConnection(PoolingHttpClientConnectionManager.java:286)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager$1.get(PoolingHttpClientConnectionManager.java:263)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:190)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:55)
    at com.fwyun.common.rs.client.HttpClientUtils.postFormRedirect(HttpClientUtils.java:538)
    at com.fwyun.common.rs.client.HttpClientUtils.postFormParametersRedirect(HttpClientUtils.java:109)
    at Main.main(Main.java:64)

看到Timeout waiting for connection from pool這個異常很容易聯想到是connectionRequestTimeout超時了,為什么會超時呢?是資源沒有釋放嗎?代碼中也明確的寫了釋放資源了!總共才循環20次,為什么就從連接池中獲取不到資源呢?反復檢查了下代碼發現

if (status == HttpStatus.SC_OK) {
        resultStr = IOUtils.toString(res.getEntity().getContent(), "UTF-8");
      } else if (status == HttpStatus.SC_MOVED_PERMANENTLY || // 處理轉向
          status == HttpStatus.SC_MOVED_TEMPORARILY || status == HttpStatus.SC_CREATED) { // 從頭中取出轉向地址
        Header locationHeader = res.getFirstHeader("location");
        if (locationHeader != null) {
          java.net.URI uri = new java.net.URI(locationHeader.getValue());
          post.setURI(uri);
          res = client.execute(post);
          resultStr = IOUtils.toString(res.getEntity().getContent(), "UTF-8");
        }
      }

這段代碼存在問題,如果請求返回httpStatus==200的時候 正常邏輯走到finally 對資源進行釋放,是不存在問題的,問題在於httpStatus!=200需要處理跳轉的時候,采用的依然是第一個請求返回的res對象來接收返回值

res = client.execute(post);
 resultStr = IOUtils.toString(res.getEntity().getContent(), "UTF-8");

這樣寫的話就會將原本第一個請求的返回值對象丟失,導致無法釋放第一個連接的資源

這樣一下就開朗多了 稍微修改一下,兩個返回結果對象分開進行接收,並在finally中統一進行釋放

public static String postFormRedirect(String url, Map<String, String> params,
      Map<String, String> headers, Integer connTimeout, Integer readTimeout)
      throws ConnectTimeoutException, SocketTimeoutException, Exception {
    String resultStr = "";
    HttpClient client = null;
    HttpResponse res = null;
    HttpResponse redirtRes = null;
    HttpPost post = new HttpPost(url);
    try {
      if (params != null && !params.isEmpty()) {
        List<NameValuePair> formParams = new ArrayList<org.apache.http.NameValuePair>();
        Set<Entry<String, String>> entrySet = params.entrySet();
        for (Entry<String, String> entry : entrySet) {
          formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
        }
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
        post.setEntity(entity);
      }

      if (headers != null && !headers.isEmpty()) {
        for (Entry<String, String> entry : headers.entrySet()) {
          post.addHeader(entry.getKey(), entry.getValue());
        }
      }
      // 設置參數
      Builder customReqConf = RequestConfig.custom();
      if (connTimeout != null) {
        customReqConf.setConnectTimeout(connTimeout);
      }
      if (readTimeout != null) {
        customReqConf.setSocketTimeout(readTimeout);
      }
      customReqConf.setConnectionRequestTimeout(connRequestTimeout);
      post.setConfig(customReqConf.build());

      if (url.startsWith("https")) {
        // 執行 Https 請求.
        client = createSSLInsecureClient();
        res = client.execute(post);
      } else {
        // 執行 Http 請求.
        client = HttpClientUtils.client;
        res = client.execute(post);
      }
      int status = res.getStatusLine().getStatusCode();
      if (status == HttpStatus.SC_OK) {
        resultStr = IOUtils.toString(res.getEntity().getContent(), "UTF-8");
      } else if (status == HttpStatus.SC_MOVED_PERMANENTLY || // 處理轉向
          status == HttpStatus.SC_MOVED_TEMPORARILY || status == HttpStatus.SC_CREATED) { // 從頭中取出轉向地址
        Header locationHeader = res.getFirstHeader("location");
        if (locationHeader != null) {
          java.net.URI uri = new java.net.URI(locationHeader.getValue());
          post.setURI(uri);
          redirtRes = client.execute(post);
          resultStr = IOUtils.toString(redirtRes.getEntity().getContent(), "UTF-8");
        }
      }
      return resultStr;
    } finally {
      if (res != null) {
        EntityUtils.consume(res.getEntity());
      }
      if (redirtRes != null) {
        EntityUtils.consume(redirtRes.getEntity());
      }
      post.releaseConnection();
      if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
        ((CloseableHttpClient) client).close();
      }
    }
  }

 

再次驗證一下 剛剛的推理是否正確,還是采用剛剛的循環20次調用
ST-21161-6grVVqIgr0Bs4NVGDCIz-cas01.example.org,:20
0耗時:957
ST-21162-vepVQxbdLSisirnY1iqA-cas01.example.org,:20
1耗時:10
ST-21163-Xy1MlSofMwHCCLbv4DTA-cas01.example.org,:20
2耗時:9
ST-21164-jr3p6WdfH0QWYqv33Agm-cas01.example.org,:20
3耗時:9
ST-21165-49i1GzLr3U0edcPZoBTk-cas01.example.org,:20
4耗時:7
ST-21166-OVc7MnZQiLhScFu6RZrb-cas01.example.org,:20
5耗時:6
ST-21167-U9MWZrOlBQeYT9BfMzuY-cas01.example.org,:20
6耗時:5
ST-21168-FHXDdei5LyO9bDCxzh5i-cas01.example.org,:20
7耗時:7
ST-21169-aQuUtUMYUFSh77yNvuj1-cas01.example.org,:20
8耗時:7
ST-21170-PsbSatB6RZzjTxTuVFk4-cas01.example.org,:20
9耗時:4
ST-21171-cLjku1Pd9npdXLG7CRzs-cas01.example.org,:20
10耗時:5
ST-21172-iKAmCyksxCz6WNoaUOtV-cas01.example.org,:20
11耗時:3
ST-21173-gUfa7qimg4TpAMjfyVPf-cas01.example.org,:20
12耗時:4
ST-21174-f49ZWfbdyvMvaLe1Ge3a-cas01.example.org,:20
13耗時:4
ST-21175-LdtWVaRVmQUrhEA3Iceb-cas01.example.org,:20
14耗時:4
ST-21176-5AmcumhwGBpfTYpGccUd-cas01.example.org,:20
15耗時:4
ST-21177-zqVujMoOKz534PcSQI3x-cas01.example.org,:20
16耗時:4
ST-21178-X3RgCYubbmdwE4iXWxaj-cas01.example.org,:20
17耗時:4
ST-21179-dHAkOflr0qU04fgVCBim-cas01.example.org,:20
18耗時:3
ST-21180-wIuYRRQFUabgyQX5bHfN-cas01.example.org,:20
19耗時:4

 

看樣子問題已經解決了 

循環加大到50次再次測試

ST-21181-yd6JhdVUw1MsidkDqTc9-cas01.example.org,:50
0耗時:926
ST-21182-IE1gUdi4OlQX2qflXfDo-cas01.example.org,:50
1耗時:5
ST-21183-HUmsb7exvNdI0cRudevt-cas01.example.org,:50
2耗時:3
ST-21184-PwMc3SfVeQbKU0lBwvXq-cas01.example.org,:50
3耗時:4
ST-21185-1G7ruiPXdDkzUcCTWR1d-cas01.example.org,:50
4耗時:4
ST-21186-HDgDMkqyYkWw2IjcGVwK-cas01.example.org,:50
5耗時:5
ST-21187-3td91BvAobMxiZXVyueO-cas01.example.org,:50
6耗時:4
ST-21188-QC1Kvd3CybO24oYXuV0f-cas01.example.org,:50
7耗時:3
ST-21189-n6fqvtLnyovmZX4CuMPM-cas01.example.org,:50
8耗時:4
ST-21190-QNXV56amzenUqycRAkvI-cas01.example.org,:50
9耗時:4
ST-21191-f2pb9zatfZ5y2Fee6aSs-cas01.example.org,:50
10耗時:3
ST-21192-hVgsgtNKKdu2l0ztll4m-cas01.example.org,:50
11耗時:4
ST-21193-tEWmw62cfwAokMJd6I7h-cas01.example.org,:50
12耗時:4
ST-21194-MvACRw7PhGxM6OFGOWvh-cas01.example.org,:50
13耗時:6
ST-21195-4NLHy9Ip9d9yChN6egNz-cas01.example.org,:50
14耗時:8
ST-21196-ok9LLVdTiApd1YHclevd-cas01.example.org,:50
15耗時:4
ST-21197-Qrd0CDTV3LzFwTe6zeY3-cas01.example.org,:50
16耗時:4
ST-21198-YO9luE4nhdSNzJ4Betgg-cas01.example.org,:50
17耗時:3
ST-21199-k5mXx3PuBRkKYeb01Pwo-cas01.example.org,:50
18耗時:4
ST-21200-5WIVROMMvZuVV0npKfl2-cas01.example.org,:50
19耗時:3
ST-21201-X2bCusnlV1CaK7d2E5iO-cas01.example.org,:50
20耗時:4
ST-21202-0RYS4fBczjMfsr6XUHfW-cas01.example.org,:50
21耗時:3
ST-21203-WJVhKsf1DV3Hzv4X00Hw-cas01.example.org,:50
22耗時:4
ST-21204-arQVGHsQHDvUFFAvH33b-cas01.example.org,:50
23耗時:4
ST-21205-LuueDdc3ttzMUhMXkuCg-cas01.example.org,:50
24耗時:4
ST-21206-cvUDZT5GO2u7BXwKu3Gd-cas01.example.org,:50
25耗時:4
ST-21207-5C9vru22Kpmh7XUpuBJU-cas01.example.org,:50
26耗時:3
ST-21208-rD3cxkHNi5WNdQ1D5Ilt-cas01.example.org,:50
27耗時:4
ST-21209-ZnZ9on3CSNfdu6qvqGLF-cas01.example.org,:50
28耗時:4
ST-21210-nGDqq6kpYuSiZ1sd7GVt-cas01.example.org,:50
29耗時:3
ST-21211-YY0ecrlxuV1z6KGsAVil-cas01.example.org,:50
30耗時:4
ST-21212-gDXGHsNPgTg6uBAsVdMg-cas01.example.org,:50
31耗時:3
ST-21213-lVKxdqbLcIZWOiXUzdml-cas01.example.org,:50
32耗時:8
ST-21214-hUVHAY4AW2Hqk0JiaAEh-cas01.example.org,:50
33耗時:5
ST-21215-dkOuVEzshOgHfPkm1Q5x-cas01.example.org,:50
34耗時:3
ST-21216-1IgMpJ4y17dn0ddjwadJ-cas01.example.org,:50
35耗時:4
ST-21217-c6KlWVOBms3DTuVtsMS0-cas01.example.org,:50
36耗時:3
ST-21218-tcdsM1nXNs0o3EZnZK4M-cas01.example.org,:50
37耗時:3
ST-21219-d3b6A7H2zy2DNVXP2u6L-cas01.example.org,:50
38耗時:4
ST-21220-pEFI1bpQ1chk7EGjh9AZ-cas01.example.org,:50
39耗時:4
ST-21221-vet4doDAJsdCnWA01TFO-cas01.example.org,:50
40耗時:4
ST-21222-iscVPPkgtT7j3xf7b3HS-cas01.example.org,:50
41耗時:6
ST-21223-g0a4pfd3qb1hMsSdHOqi-cas01.example.org,:50
42耗時:6
ST-21224-DEzfJa9a7bE0sBrLWQAG-cas01.example.org,:50
43耗時:4
ST-21225-2ldk9S0ZvLXwkQmFLFJb-cas01.example.org,:50
44耗時:4
ST-21226-yiugvLRcsPteqAe31X0t-cas01.example.org,:50
45耗時:3
ST-21227-Gd3HN99vxmx6cAule0vn-cas01.example.org,:50
46耗時:5
ST-21228-YbGi4hILdYN4RaPvJYvy-cas01.example.org,:50
47耗時:3
ST-21229-VfR7iKA3r4WFG74kYid5-cas01.example.org,:50
48耗時:3
ST-21230-j7BUX4cApbcLJQ6ptKqJ-cas01.example.org,:50
49耗時:19

 

總結一下問題的原因,必須確保每一個response的資源都釋放掉了,否則連接池一直判定對象為正在使用狀態,這樣累積將達到defaultMaxPerRoute,滿了后再有請求需要獲取連接,因為已經沒有了可用資源,超時后將報出Timeout waiting for connection from pool異常。

 

TIME_WAIT和CLOSE_WAIT的區別

linux服務器上運行查找假死的http請求

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
TIME_WAIT 297
ESTABLISHED 53
CLOSE_WAIT 5

解釋
TIME_WAIT:表示主動關閉,通過優化系統內核參數可容易解決。
CLOSE_WAIT:表示被動關閉,需要從程序本身出發。
ESTABLISHED:表示正在通信

TIME_WAIT 

 TIME_WAIT是主動關閉連接的一方保持的狀態,對於服務器來說它本身就是“客戶端”,在完成一個爬取任務之后,它就會發起主動關閉連接,從而進入TIME_WAIT的狀態,然后在保持這個狀態2MSL(max segment lifetime)時間之后,徹底關閉回收資源。為什么要這么做?明明就已經主動關閉連接了為啥還要保持資源一段時間呢?這個是TCP/IP的設計者規定的,主要出於以下兩個方面的考慮:
1.防止上一次連接中的包,迷路后重新出現,影響新連接(經過2MSL,上一次連接中所有的重復包都會消失)
2.可靠的關閉TCP連接。在主動關閉方發送的最后一個 ack(fin) ,有可能丟失,這時被動方會重新發fin, 如果這時主動方處於 CLOSED 狀態 ,就會響應 rst 而不是 ack。所以主動方要處於 TIME_WAIT 狀態,而不能是 CLOSED 。另外這么設計TIME_WAIT 會定時的回收資源,並不會占用很大資源的,除非短時間內接受大量請求或者受到攻擊。
解決方案很簡單,通過修改/etc/sysctl.conf文件,服務器能夠快速回收和重用那些TIME_WAIT的資源

#表示開啟SYN Cookies。當出現SYN等待隊列溢出時,啟用cookies來處理,可防范少量SYN攻擊,默認為0,表示關閉    
net.ipv4.tcp_syncookies = 1    
#表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連接,默認為0,表示關閉    
net.ipv4.tcp_tw_reuse = 1    
#表示開啟TCP連接中TIME-WAIT sockets的快速回收,默認為0,表示關閉    
net.ipv4.tcp_tw_recycle = 1  
#表示如果套接字由本端要求關閉,這個參數決定了它保持在FIN-WAIT-2狀態的時間    
net.ipv4.tcp_fin_timeout=30  

CLOSE_WAIT(需要從程序本身出發)

TCP狀態轉移要點

  TCP協議規定,對於已經建立的連接,網絡雙方要進行四次握手才能成功斷開連接,如果缺少了其中某個步驟,將會使連接處於假死狀態,連接本身占用的資源不會被釋放。網絡服務器程序要同時管理大量連接,所以很有必要保證無用連接完全斷開,否則大量僵死的連接會浪費許多服務器資源.

    客戶端TCP狀態遷移: CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
    服務器TCP狀態遷移:CLOSED->LISTEN->SYN收到->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED
    
    但是CLOSE_WAIT就不一樣了,如果一直保持在CLOSE_WAIT狀態,那么只有一種情況,就是在對方關閉連接之后服務器程序自己沒有進一步發出ack信號。換句話說,就是在對方連接關閉之后,程序里沒有檢測到,或者程序壓根就忘記了這個時候需要關閉連接,於是這個資源就一直被程序占着。個人覺得這種情況,通過服務器內核參數也沒辦法解決,服務器對於程序搶占的資源沒有主動回收的權利,除非終止程序運行。

   什么情況下,連接處於CLOSE_WAIT狀態呢?

   答案一:在被動關閉連接情況下,在已經接收到FIN,但是還沒有發送自己的FIN的時刻,連接處於CLOSE_WAIT狀態。通常來講,CLOSE_WAIT狀態的持續時間應該很短,正如SYN_RCVD狀態。但是在一些特殊情況下,就會出現連接長時間處於CLOSE_WAIT狀態的情況。

   答案二:出現大量close_wait的現象,主要原因是某種情況下對方關閉了socket鏈接,但是我方忙與讀或者寫,沒有關閉連接。代碼需要判斷socket,一旦讀到0,斷開連接,read返回負,檢查一下errno,如果不是AGAIN,就斷開連接。

 

https://my.oschina.net/fuxingCoder/blog/809835

https://www.cnblogs.com/xzlive/p/11733756.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM