Windows环境上的Java程序HttpClient请求超时时间无法超过22秒(左右)的问题记录

文章转载自:https://blog.51cto.com/u_15344989/3655501

聊一次历经4年才解决的问题,关于apache HttpClient的超时时间始终无法超过21秒的问题。

作者遇到的问题与原文作者遇到的问题一样,所以直接转载原文作者的文章。以下是原文

大约在四年前,笔者在工作中发现HttpClient的超时时间当你设置为一个大于21秒的值得时候比如60秒,你会发现它依然是21秒。而当你设置为一个小于21秒的时候比如5秒,你会发现超时时间就是5秒。可直接复制进ide运行,其中128.28.28.28是一个不存在的地址。

package com.dongbawen.hppa.biz.rulemstrecipe;

import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Send_Class {
    //最大连接数
    public static int MAX_CONNECTION_PERROUTE = 1;
    //超时时间
    public static int SOCKET_TIMEOUT = 60000;
    
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        try {
            String time1= sdf.format(new Date());
            System.out.println(time1);
            Send_Class sc=new Send_Class();
            sc.test();
            time1= sdf.format(new Date());
            System.out.println(time1);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void test(){
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setDefaultMaxPerRoute(MAX_CONNECTION_PERROUTE);
        RequestConfig.Builder builder = RequestConfig.custom();
        RequestConfig config = builder.setSocketTimeout(SOCKET_TIMEOUT)
                .setConnectTimeout(SOCKET_TIMEOUT)
                .setConnectionRequestTimeout(SOCKET_TIMEOUT)
                .setStaleConnectionCheckEnabled(true).build();
        CloseableHttpClient client = HttpClients.custom()
                .setMaxConnPerRoute(MAX_CONNECTION_PERROUTE).disableConnectionState()
                .setDefaultRequestConfig(config)
                .setConnectionManager(cm).build();
        String url="http://128.28.28.28";
        HttpPost post = new HttpPost(url);
        post.setProtocolVersion(HttpVersion.HTTP_1_1);
        post.setConfig(config);
        List<NameValuePair> formpair = new ArrayList<NameValuePair>();
        formpair.add(new BasicNameValuePair("name", "张三"));
        HttpEntity entity = new UrlEncodedFormEntity(formpair, Consts.UTF_8);
        if (entity != null) {
            post.setEntity(entity);
        }
        try {
            client.execute(post);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果

聊一次历经4年才解决的问题,关于apache HttpClient的超时时间始终无法超过21秒的问题。_java

聊一次历经4年才解决的问题,关于apache HttpClient的超时时间始终无法超过21秒的问题。_Httpclient超时时间设置_02

可以看到我设置的明明是60秒,但是这里21秒左右(22秒)

解决历程

我首先跟踪了源码,因为一开始我一直是觉得是不是由于JVM内部的某些限制,比如他会去判断这个值是否大于21,如果大于21就改成21,但是我一直跟到Open jdk的C源码,依然没发现这个值有变动,一直是你写多少就是多少。这就很奇怪了,但跟踪源码也不是完全没有收获,我看到这个21秒超时实际上是由于socket引起的超时,也就是如果你运行如下代码

public static void main(String[] args) {
	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
	String time1= sdf.format(new Date());
	System.out.println(time1);
	try {
		Socket sock = new Socket();
		SocketAddress socketAddress = new InetSocketAddress("128.28.28.28",80);
		sock.connect(socketAddress,60000);
	}catch (Exception ex){
		ex.printStackTrace();
	}finally {
		time1= sdf.format(new Date());
		System.out.println(time1);
	}
}

会得到如下结果

聊一次历经4年才解决的问题,关于apache HttpClient的超时时间始终无法超过21秒的问题。_apache_03

可以看到超时时间也是21秒左右
由于在一直跟踪到openjdk中都没有发现Jdk有对这个值改动,所以猜测着会不会是由于操作系统底层的限制呢??,我们知道linux内核会对某些参数有所限制,比如最大连接数等等。但由于笔者无法确定是否真的是这样,所以去stackoverflow提了一个问题,终于得到了一个相对满意的答复。原问题链接:
https://stackoverflow.com/questions/56146261/i-set-the-timeout-on-the-socket-and-i-found-that-this-value-cannot-be-greater-t
大体来说就是确实是linux内核对这个值进行了限制,而且可以修改这个值,怎么修改看这
http://willbryant.net/overriding_the_default_linux_kernel_20_second_tcp_socket_connect_timeout
于是笔者又搭了一个spring boot程序,放在我的linux虚拟机上去运行,我按照文中的方法进行了修改,但是duang,我发现超时时间居然还是21秒。。。这就很尴尬了,这到底是为什么呢??

就在一筹莫展的时候,我在想,这会不会是因为我的虚拟机linux的版本的问题,正好我有一台腾讯云,于是我在腾讯云上面试过了一下,没想到在21秒的超时没有超时,一直到127秒的时候才超时,而且我按照前面提到的方法确实可以随意控制这个超时时间。

到此,问题终于得到了解决。

猜想:之所以我在虚拟机上不行的原因是因为可能这个参数与底层系统有关,而虚拟机可能底层网络通信这块还是调用的windows上的,所以设置的这个值无效。
问题:那么问题来了,windows上怎么设置这个值呢???留给大家讨论吧

补充说明

其实上面那篇文章中提到的linux内核超时时间默认是20秒,经过测试是不对得,默认值应该127秒。
而我之所以是20秒,是因为我是在windows系统上

总结(解决方案)

如果你是要在windows系统上运行,暂时没有找到解决方案,而如果你是linux系统,你应该不会遇到默认21秒超时,因为linux系统默认时间是127秒超时,而如果你想修改这个值,你可以这么做

vim /etc/sysctl.conf
net.ipv4.tcp_syn_retries = 6
sysctl -p /etc/sysctl.conf

上面的这个6是代表等待127秒后超时,也是默认值。你可以随意修改这个值,1代表3秒,2代表7秒…7代表255秒,8代表511秒。假设这个值为n.则超时时间为2的n+1次方减1.

超时时间 = 2^(n+1)-1.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值