http client遭遇Connection reset问题,以及解决方法

博客讲述多线程连接池管理器发送HTTP请求时出现Connection reset异常。分析发现客户端有CLOSE_WAIT状态的socket连接,服务端有FIN_WAIT2状态连接,是服务端达到keepalive超时阈值主动断连。指出http client有重发机制却未成功,最后提出不使用httpclient重发机制,自己实现的解决办法。


客户端使用200个线程往服务器端发送数据,每次数据量约100K.

发送的时候使用了多线程连接池管理器

private MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
private HttpClient httpClient = new HttpClient(connectionManager);


httpClient.getHttpConnectionManager().getParams()
.setDefaultMaxConnectionsPerHost(WORKER_COUNT * 2);
httpClient.getHttpConnectionManager().getParams()
.setMaxTotalConnections(WORKER_COUNT * 5);
httpClient.getHttpConnectionManager().getParams().setSoTimeout(0);
httpClient.getHttpConnectionManager().getParams()
.setStaleCheckingEnabled(true);
<span style="font-family: Arial, Helvetica, sans-serif;">// 使用DefaultHttpMethodRetryHandler是希望在发送失败后能够自动重新发送</span>
<span style="font-family: Arial, Helvetica, sans-serif;">httpClient.getHttpConnectionManager().getParams()</span>
.setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());

结果在实际的运行过程中发现不少的异常,导致数据没有发送成功:

[INFO ] 2014-08-31 13:00:35,804 -xxx.http.HttpMessageManager -430870 [pool-2-thread-126] INFO  xxx.http.HttpMessageManager  - io exception when send http.
 java.net.SocketException: Connection reset
        at java.net.SocketInputStream.read(SocketInputStream.java:168)
        at java.io.BufferedInputStream.fill(BufferedInputStream.java:218)
        at java.io.BufferedInputStream.read(BufferedInputStream.java:237)
        at org.apache.commons.httpclient.HttpParser.readRawLine(Unknown Source)
        at org.apache.commons.httpclient.HttpParser.readLine(Unknown Source)
        at org.apache.commons.httpclient.HttpConnection.readLine(Unknown Source)
        at org.apache.commons.httpclient.MultiThreadedHttpConnectionManager$HttpConnectionAdapter.readLine(Unknown Source)
        at org.apache.commons.httpclient.HttpMethodBase.readStatusLine(Unknown Source)
        at org.apache.commons.httpclient.HttpMethodBase.readResponse(Unknown Source)
        at org.apache.commons.httpclient.HttpMethodBase.execute(Unknown Source)
        at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(Unknown Source)
        at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(Unknown Source)
        at org.apache.commons.httpclient.HttpClient.executeMethod(Unknown Source)
        at org.apache.commons.httpclient.HttpClient.executeMethod(Unknown Source)
        at xxx.http.HttpMessageManager$MessageSendWorker.sendHttp(HttpMessageManager.java:308)
        at xxx.http.HttpMessageManager$MessageSendWorker.run(HttpMessageManager.java:253)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
        at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
        at java.util.concurrent.FutureTask.run(FutureTask.java:138)
        at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
        at java.lang.Thread.run(Thread.java:662)
[ERROR] 2014-08-31 13:00:35,805 - xxx.http.HttpMessageManager -430871 [pool-2-thread-126] ERROR xxx.http.HttpMessageManager  - send http fail after retry 3 times, url=http://10.10.10.10/metrics/put

加粗部分的日志是我自己打印的,因为DefaultHttpMethodRetryHandler在发送失败时会重试3次。


分析发现,通过netstat -an命令查看,客户端会出现几十个CLOSE_WAIT状态的socket连接,server端会出现一些FIN_WAIT2状态的连接,很明显server端主动发起了断连。查看server端的keepalive超时设置为15000毫秒,达到了此阈值时,就会强制关闭连接。


server端断连是一个正常的操作,关键是http client端不是有重发机制吗?所以不应该最后没有发送成功呀


http Client下 多个线程共享connet连接池,每次发送时从池中获取一个连接:


    public void executeMethod(final HttpMethod method) throws IOException, HttpException {
        if (method == null) {
            throw new IllegalArgumentException("Method may not be null");
        }
        // Link all parameter collections to form the hierarchy:
        // Global -> HttpClient -> HostConfiguration -> HttpMethod
        this.hostConfiguration.getParams().setDefaults(this.params);
        method.getParams().setDefaults(this.hostConfiguration.getParams());
        
        // Generate default request headers
        Collection defaults = (Collection)this.hostConfiguration.getParams().
            getParameter(HostParams.DEFAULT_HEADERS);
        if (defaults != null) {
            Iterator i = defaults.iterator();
            while (i.hasNext()) {
                method.addRequestHeader((Header)i.next());
            }
        }
        
        try {
            int maxRedirects = this.params.getIntParameter(HttpClientParams.MAX_REDIRECTS, 100);


            for (int redirectCount = 0;;) {


                // make sure the connection we have is appropriate
                if (this.conn != null && !hostConfiguration.hostEquals(this.conn)) {
                    this.conn.setLocked(false);
                    this.conn.releaseConnection();
                    this.conn = null;
                }
        
                // get a connection, if we need one
                if (this.conn == null) {
                    <strong>this.conn = connectionManager.getConnectionWithTimeout(
                        hostConfiguration,
                        this.params.getConnectionManagerTimeout() 
                    );
                    this.conn.setLocked(true);
                    if (this.params.isAuthenticationPreemptive()
                     || this.state.isAuthenticationPreemptive()) 
                    {
                        LOG.debug("Preemptively sending default basic credentials");
                        method.getHostAuthState().setPreemptive();
                        method.getHostAuthState().setAuthAttempted(true);
                        if (this.conn.isProxied() && !this.conn.isSecure()) {
                            method.getProxyAuthState().setPreemptive();
                            method.getProxyAuthState().setAuthAttempted(true);
                        }
                    }</strong>
                }
                authenticate(method);
                <strong>executeWithRetry(method);</strong>
                if (this.connectMethod != null) {
                    fakeResponse(method);
                    break;
                }
                
                。。。。。


            } //end of retry loop
        } finally {
            if (this.conn != null) {
                this.conn.setLocked(false);
            }
            // If the response has been fully processed, return the connection
            // to the pool.  Use this flag, rather than other tests (like
            // responseStream == null), as subclasses, might reset the stream,
            // for example, reading the entire response into a file and then
            // setting the file as the stream.
            if (
                (releaseConnection || method.getResponseBodyAsStream() == null) 
                && this.conn != null
            ) {
                this.conn.releaseConnection();
            }
        }


    }


    private void executeWithRetry(final HttpMethod method) 
        throws IOException, HttpException {
        
        /** How many times did this transparently handle a recoverable exception? */
        int execCount = 0;
        // loop until the method is successfully processed, the retryHandler 
        // returns false or a non-recoverable exception is thrown
        try {
            while (true) {
                execCount++;
                try {


                    if (LOG.isTraceEnabled()) {
                        LOG.trace("Attempt number " + execCount + " to process request");
                    }
                    if (this.conn.getParams().isStaleCheckingEnabled()) {
                        this.conn.closeIfStale();
                    }
                    if (!this.conn.isOpen()) {
                        // this connection must be opened before it can be used
                        // This has nothing to do with opening a secure tunnel
                        this.conn.open();
                        if (this.conn.isProxied() && this.conn.isSecure() 
                        && !(method instanceof ConnectMethod)) {
                            // we need to create a secure tunnel before we can execute the real method
                            if (!executeConnect()) {
                                // abort, the connect method failed
                                return;
                            }
                        }
                    }
                    applyConnectionParams(method);                    
                    method.execute(state, this.conn);
                    break;
                } catch (HttpException e) {
                    // filter out protocol exceptions which cannot be recovered from
                    throw e;
                } catch (IOException e) {
                    LOG.debug("Closing the connection.");
                    this.conn.close();
                    。。。。
<strong>                    // ========================================
                    HttpMethodRetryHandler handler = 
                        (HttpMethodRetryHandler)method.getParams().getParameter(
                                HttpMethodParams.RETRY_HANDLER);</strong>
                    if (handler == null) {
                        handler = new DefaultHttpMethodRetryHandler();
                    }
                   <strong> if (!handler.retryMethod(method, e, execCount)) {</strong>
                        LOG.debug("Method retry handler returned false. "
                                + "Automatic recovery will not be attempted");
                        throw e;
                    }
                    。。。。
                }
            }
        } catch (IOException e) {
            if (this.conn.isOpen()) {
                LOG.debug("Closing the connection.");
                this.conn.close();
            }
            releaseConnection = true;
            throw e;
        } catch (RuntimeException e) {
            if (this.conn.isOpen()) {
                LOG.debug("Closing the connection.");
                this.conn.close();
            }
            releaseConnection = true;
            throw e;
        }
    }

    public boolean retryMethod(
        final HttpMethod method, 
        final IOException exception, 
        int executionCount) {
        if (method == null) {
            throw new IllegalArgumentException("HTTP method may not be null");
        }
        if (exception == null) {
            throw new IllegalArgumentException("Exception parameter may not be null");
        }
        // HttpMethod interface is the WORST thing ever done to HttpClient
        if (method instanceof HttpMethodBase) {
<strong>            // 此处判断如果连接异常,则不重发
            if (((HttpMethodBase)method).isAborted()) {
                return false;
            }</strong>
        }
        。。。。。
        return false;
    }

一直以为连接异常时也会重发,当server端主动断开时,client会根据StaleCheckingEnabled的值检测连接是否正常,如果正常才发送,发送异常时会重发。今天才发现http client的重发机制也是有限制的,如果网络异常退出,就不会重发,而是直接抛出异常。


解决办法:


不适用httpclient的重发机制,自己搞一个:


// 重试3次
int retry = 0;
do {
try {
int status = httpClient.executeMethod(postMethod);

if (HttpStatus.SC_OK == status) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("send http success, url=" + url
+ ", content=" + message);
}
}else {
String rsp = HttpUtils
.getInputStreamAsString(postMethod
.getResponseBodyAsStream());
LOGGER.error("send http fail, status is: " + status
+ ", response is: " + rsp);
}
return;
} catch (HttpException e) {
postMethod.abort();
LOGGER.info("http exception when send http.", e);
} catch (IOException e) {
postMethod.abort();
LOGGER.info("io exception when send http.", e);
} finally {
postMethod.releaseConnection();
}
LOGGER.info("send http fail, retry:" + retry);
} while (++retry < 3);
// 如果到了第3次还未发送成功,则记录日志
LOGGER.error("send http fail after retry 3 times, url=" + url);






<think>好的,用户的问题是关于如何在Java中使用OkHttpKit库,并解决与OkHttpClient相关的警告问题。首先,我需要回顾一下OkHttpKit的基本用法,以及常见的警告和错误。 用户提到的OkHttpKit可能是一个基于OkHttp3封装的工具库,通常这类库会简化OkHttp的使用流程。因此,我需要先解释如何引入依赖,创建实例,并发送请求。例如,GET和POST请求的示例代码会是关键点。 接下来是解决连接警告的问题。常见的警告可能涉及连接池管理、未关闭响应体、过时的配置方法等。引用中的资料显示,OkHttpClient的单例模式推荐使用,这样可以避免资源浪费。同时,用户可能没有正确关闭响应体,导致资源泄漏,这也需要提醒。另外,像sslSocketFactory在JDK9+上的问题,可能需要调整SSL配置方法,或者检查依赖版本是否兼容。例如,引用3提到JDK版本升级后出现的错误,可能需要使用推荐的X509TrustManager来替代旧方法。 还要考虑用户可能遇到的版本冲突问题,如引用4中提到的Minio集成问题,可能是因为OkHttp的版本不兼容。所以需要建议用户检查依赖版本,确保OkHttpKit和OkHttp3的版本匹配。 另外,用户可能没有正确配置连接池参数,导致警告,比如连接存活时间或最大空闲连接数的设置。这部分需要给出具体的配置示例,如设置连接池参数和超时时间。 最后,需要总结常见问题解决方案,并提醒用户注意资源管理和依赖版本。可能还需要提到如何通过日志调试或查看官方文档来进一步解决问题。</think>### Java中使用OkHttpKit库的方法及警告解决方案 #### 一、OkHttpKit基础使用方法 1. **添加依赖** 首先在项目中引入OkHttp3和OkHttpKit的依赖(假设OkHttpKit基于OkHttp3封装): ```xml <!-- OkHttp3 --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.10.0</version> </dependency> <!-- OkHttpKit(根据实际库调整) --> <dependency> <groupId>com.example</groupId> <artifactId>okhttp-kit</artifactId> <version>1.0.0</version> </dependency> ``` 2. **创建单例OkHttpClient** ```java public class HttpUtils { private static final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build(); public static OkHttpClient getClient() { return client; } } ``` 3. **发送GET请求示例** ```java public String doGet(String url) throws IOException { Request request = new Request.Builder().url(url).build(); try (Response response = HttpUtils.getClient().newCall(request).execute()) { return response.body().string(); } } ``` 4. **发送POST请求示例** ```java public String doPost(String url, String jsonBody) throws IOException { RequestBody body = RequestBody.create(jsonBody, MediaType.get("application/json")); Request request = new Request.Builder().url(url).post(body).build(); try (Response response = HttpUtils.getClient().newCall(request).execute()) { return response.body().string(); } } ``` --- #### 二、常见`WARN okhttp3.OkHttpClient`警告及解决方案 1. **连接池资源未释放** **现象**:出现类似`A connection to ... was leaked`的警告。 **原因**:未正确关闭响应体(Response Body)。 **解决方案**:使用`try-with-resources`确保自动关闭: ```java try (Response response = client.newCall(request).execute()) { // 处理响应 } ``` 2. **SSL配置兼容性问题** **现象**:`clientBuilder.sslSocketFactory() not supported on JDK 9+`[^3]。 **原因**:JDK9+中废弃了部分SSL配置方法。 **解决方案**:改用推荐的SSL配置方式: ```java X509TrustManager trustManager = ...; // 自定义TrustManager SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[]{trustManager}, null); OkHttpClient client = new OkHttpClient.Builder() .sslSocketFactory(sslContext.getSocketFactory(), trustManager) .build(); ``` 3. **过时的依赖版本冲突** **现象**:`The method did not exist: okhttp3.RequestBody.create(...)`[^4]。 **原因**:依赖的OkHttp版本与调用方法不兼容。 **解决方案**:统一依赖版本,例如强制使用OkHttp 4.x: ```xml <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.10.0</version> </dependency> ``` 4. **连接池参数优化** **现象**:`Idle connections will be closed`警告。 **原因**:默认连接池参数可能不适用于高并发场景。 **解决方案**:自定义连接池参数: ```java ConnectionPool pool = new ConnectionPool(50, 5, TimeUnit.MINUTES); OkHttpClient client = new OkHttpClient.Builder() .connectionPool(pool) .build(); ``` --- #### 三、最佳实践总结 1. **单例模式**:避免重复创建`OkHttpClient`实例[^1]。 2. **资源释放**:确保关闭`Response`和其关联的`ResponseBody`。 3. **版本对齐**:检查项目内所有依赖的OkHttp版本是否一致[^4]。 4. **日志调试**:通过添加拦截器打印详细请求日志: ```java client = client.newBuilder() .addInterceptor(new HttpLoggingInterceptor().setLevel(Level.BODY)) .build(); ``` 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值