我的编程空间,编程开发者的网络收藏夹
学习永远不晚

java.net.SocketTimeoutException: Read timed out,tcp连接心跳[TCP Keep-Alive],socket模拟http

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

java.net.SocketTimeoutException: Read timed out,tcp连接心跳[TCP Keep-Alive],socket模拟http

            logger.info("接口开始");                        con.setConnectTimeout(5000);//建立连接的超时时间,单位毫秒            con.setReadTimeout(3 * 60 * 60 * 1000); //接口最长处理3小时            // 开启连接            con.connect();            //请求参数            if (params != null && params.length() > 0) {                os = con.getOutputStream();                os.write(params.getBytes(StandardCharsets.UTF_8));                os.flush();            }            //响应            logger.info("接口结果:" + con.getResponseCode()); // 这里抛出异常 java.net.SocketTimeoutException: Read timed out

 读超时con.setReadTimeout(3 * 60 * 60 * 1000);已设置为3小时。

日志

2023-06-02 05:16:44.341 logback [main] INFO  com.ustc.transfer.TransferService - 接口开始2023-06-02 08:16:44.389 logback [main] ERROR com.ustc.transfer.TransferService - Read timed outjava.net.SocketTimeoutException: Read timed outat java.net.SocketInputStream.socketRead0(Native Method)at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)at java.net.SocketInputStream.read(SocketInputStream.java:171)at java.net.SocketInputStream.read(SocketInputStream.java:141)at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)at java.io.BufferedInputStream.read(BufferedInputStream.java:345)at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:735)at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:678)at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1607)at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1512)at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)at com.ustc.transfer.TransferService.sendPost(TransferService.java:489)

日志发现 等待了 3小时,抛出了异常

经过排查,是因为 后端防火墙,连接空闲20分钟,连接就会被丢弃。

解决办法是,使用 socket.setKeepAlive(true);
注意HttpURLConnection的connection.setRequestProperty("Connection", "keep-alive");是不能达到到效果的,这个作用是是复用连接。

注意与HTTP中请求头”Connection: keep-alive“的区别

socket.setKeepAlive(true)这个参数与TCP KeepAliveTime参数配合使用,默认2小时,空闲2小时的连接发送一次心跳防止连接被杀掉。
 

我这里的场景是,这个接口后台要处理3小时左右,而后端防火墙会丢弃空闲20分钟的连接

客户端是windows系统,TCP KeepAliveTime默认是连接空闲2小时才发送一次[TCP Keep-Alive]报文。根据我的实际情况改成15分钟。

在 cmd 中输入 regedit 打开注册表,复制下面的路径回车

计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters

右键 Parameters > 新建 > DWORD(32位)> KeepAliveTime > 右键修改

 

修改后,需要重启电脑,这样连接空闲15分钟后,就会发送一次[TCP Keep-Alive]报文。

socket.setSoTimeout(3 * 60 * 60 * 1000);//读超时,单位毫秒
socket.setKeepAlive(true);

package com.study;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.Socket;import java.net.URI;import java.net.URLEncoder;import java.nio.charset.StandardCharsets;import java.util.HashMap;import java.util.Map;public class SocketHttpClient {    private static final Logger logger = LoggerFactory.getLogger(SocketHttpClient.class);    public static void main(String[] args) throws Exception {        String url = "http://localhost:5033/temp?t=21";        // 请求参数:name=vaue        Map params = new HashMap<>();        params.put("name", "张三");        params.put("age", "18");        String sendPost = sendPost(url, params);        logger.info(sendPost);    }        public static String sendPost(String url, Map params) {        Socket socket = null;        InputStream inputStream = null;        OutputStream outputStream = null;        try {            URI uri = new URI(url);            // 要连接的服务端IP地址和端口            String host = uri.getHost();            int port = uri.getPort();            // 与服务端建立连接            socket = new Socket(host, port);            // socket.setSoTimeout(5000);//读超时,单位毫秒            socket.setSoTimeout(3 * 60 * 60 * 1000);//读超时,单位毫秒            socket.setTcpNoDelay(true);//数据不作缓冲,立即发送            // 这个参数与TCP KeepAliveTime参数配合使用,默认2小时,空闲2小时的连接发送一次心跳防止连接被杀掉。            // 注意与HTTP中请求头”Connection: keep-alive“的区别            // 修改注册表”计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters“新建”KeepAliveTime“值为900000即15分钟。            socket.setKeepAlive(true);            // 建立好连接后,从socket中获取输入流            inputStream = socket.getInputStream();            // 建立好连接后,从socket中获取输出流            outputStream = socket.getOutputStream();            // 获取http模拟报文            byte[] http = getHttp(url, params);            outputStream.write(http);            outputStream.flush();            // 测试发现,HTTP报文的终止符应该是"\r\n\r\n"即连续两个换行符,这部分内容刚好是请求头中的内容,            // 假如请求头中没有"Content-Length",则请求体中的内容会被丢弃,            // 如果有"Content-Length"请求头,后端继续读取"Content-Length"请求头指定大小的内容。            // 在模拟HTTP请求时不要主动关闭连接,HTTP通过"Content-Length"请求头指定的大小来接收请求体中的数据。            // 所以模拟HTTP报文时,是不需要shutdownOutput()关闭(单向)连接的,            // 若shutdownOutput()关闭了连接,在120秒后,客户端会发送一个RST(连接重置)信号,并抛出异常java.net.SocketException: Connection reset                        // 请求报文发送完成后,发送终止符            // socket.shutdownOutput();// 单向关闭输出流,发送流的终止符-1。            // 获取响应内容            byte[] buf = new byte[1024];            int len;            ByteArrayOutputStream out = new ByteArrayOutputStream();            //只有当客户端关闭它的输出流的时候,服务端才能取得结尾的-1            while ((len = inputStream.read(buf)) != -1) {                out.write(buf, 0, len);            }            // 注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8            String result = out.toString(StandardCharsets.UTF_8.name());            // 响应头和响应体之间有一个空行,这里只返回响应体中的内容            if (result != null && result.indexOf("\r\n\r\n") != -1) {                return result.substring(result.indexOf("\r\n\r\n") + 4);            }        } catch (Exception e) {            logger.error(e.getMessage(), e);        } finally {            try {                if (inputStream != null) {                    inputStream.close();                }            } catch (IOException e) {                e.printStackTrace();            }            try {                if (outputStream != null) {                    outputStream.close();                }            } catch (IOException e) {                e.printStackTrace();            }            try {                if (socket != null) {                    socket.close();                }            } catch (IOException e) {                e.printStackTrace();            }        }        return null;    }        private static byte[] getHttp(String url, Map params) throws Exception {        URI uri = new URI(url);        // 要连接的服务端IP地址和端口        int port = uri.getPort();        String host = uri.getHost() + ":" + port;        // 拼接http请求体        StringBuilder bodyBuilder = new StringBuilder();        if (params != null && params.size() > 0) {            for (Map.Entry entry : params.entrySet()) {                // 参数名                bodyBuilder.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name()));                bodyBuilder.append("=");                // 参数值                bodyBuilder.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.name()));                bodyBuilder.append("&");            }            bodyBuilder.deleteCharAt(bodyBuilder.length() - 1);        }        byte[] body = bodyBuilder.toString().getBytes(StandardCharsets.UTF_8);        // 定义存放 http 报文的缓冲区        ByteArrayOutputStream http = new ByteArrayOutputStream();        http.write(("POST " + url + " HTTP/1.1\r\n").getBytes(StandardCharsets.UTF_8));        http.write(("Host: " + host + "\r\n").getBytes(StandardCharsets.UTF_8));        // 请求参数为key1=value1&key2=value2形式        http.write(("Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n").getBytes(StandardCharsets.UTF_8));        // 请求参数为json格式        // http.write(("Content-Type: application/json; charset=UTF-8\r\n").getBytes(StandardCharsets.UTF_8));        http.write(("Content-Length: " + body.length + "\r\n").getBytes(StandardCharsets.UTF_8));        // http.write(("Cookie: " + cookie + "\r\n").getBytes(StandardCharsets.UTF_8));        // 请求头和请求体之间有一个空行        http.write("\r\n".getBytes(StandardCharsets.UTF_8));        http.write(body);        return http.toByteArray();    }}

// 请求报文发送完成后,发送终止符
// socket.shutdownOutput();// 单向关闭输出流,发送流的终止符-1。 

测试发现,HTTP报文的终止符应该是"\r\n\r\n"即连续两个换行符,这部分内容刚好是请求头中的内容,
假如请求头中没有"Content-Length",则请求体中的内容会被丢弃,
如果有"Content-Length"请求头,后端继续读取"Content-Length"请求头指定大小的内容。

在模拟HTTP请求时不要主动关闭连接shutdownOutput(),HTTP后端通过"Content-Length"请求头指定的大小来接收请求体中的数据。
所以模拟HTTP报文时,是不需要shutdownOutput()关闭(单向)连接的,
若shutdownOutput()关闭了连接,在120秒后,客户端会发送一个RST(连接重置)信号,并抛出异常java.net.SocketException: Connection reset

现象1:
请求报文发送完成后,如果关闭shutdownOutput单向关闭输出流,如果后台处理时间超过120秒,客户端会发送一个RST(连接重置)信号,并抛出异常Connection reset。

现象2:
请求报文发送完成后,如果不关闭shutdownOutput单向关闭输出流,会延迟20秒才能接收到后台的响应结果。

问:不关闭shutdownOutput响应结果为什么会延迟20秒?

这是因为在使用Socket发送HTTP报文时,服务器端会根据Content-Length或Transfer-Encoding字段来判断报文是否已经发送完毕。如果客户端没有主动关闭输出流,服务器端会一直等待接收数据,直到超时。因此,如果不调用shutdownOutput方法关闭输出流,可能会导致数据发送不完整,服务器端一直等待直到超时。而shutdownOutput方法会显式地告知服务器端数据已经发送完毕,服务器端不必等待超时,从而提高响应速度。

问:为什么关闭shutdownOutput单向关闭输出流,在请求时长超过120秒时会报错Connection resetd?

如果调用shutdownOutput方法关闭输出流,但请求时长超过了120秒,就会因为TCP连接超时而导致连接被重置,从而导致Connection reset错误。这是因为TCP连接有一个默认的超时时间,如果在超时时间内没有收到对方的回复,就会认为连接已经失效,从而重置连接。

问:请求报文发送完成后,到底该不该shutdownOutput()关闭连接?

1、如果请求在120秒内就会有响应结果,强烈建议关闭shutdownOutput连接。

2、如果请求不知道什么时候返回响应结果,有两种方案:

方案一:
关闭shutdownOutput连接,并修改
KeepAliveTime=60000毫秒,即1分钟就会发送一次[TCP Keep-Alive]报文,连接就不会超时被重置。

方案二:
不关闭shutdownOutput连接,这样做有个缺点,接收响应结果会晚20秒。

来源地址:https://blog.csdn.net/u014644574/article/details/131001026

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

java.net.SocketTimeoutException: Read timed out,tcp连接心跳[TCP Keep-Alive],socket模拟http

下载Word文档到电脑,方便收藏和打印~

下载Word文档

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录