反序列化问题处理Unexpected EOF read on the socket

8 篇文章 0 订阅

最近负责的SpringBoot项目日志里面老是出现“Unexpected EOF read on the socket”的错误,但是测试时,测来测去又没发现什么问题,但是看到日志里面有错误日志又不知道原因,这个怎么能忍,所以花点时间好好看看。

首先得出我的结论:在客户端上传请求体的期间,客户端关闭了网络连接,导致服务器端的输入流异常中断,最终导致Jackson反序列化出现异常。下面是具体的分析过程。

首先我们看看错误日志:

11:00:13.792 [http-nio-9090-exec-1] ERROR com.app.component.ControllerExceptionHandler - JSON parse error: Unexpected EOF read on the socket; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unexpected EOF read on the socket (through reference chain: com.app.entity.AppServiceEntity["installList"]->java.util.HashSet[2])
 org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unexpected EOF read on the socket; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unexpected EOF read on the socket (through reference chain: com.app.entity.AppServiceEntity["installList"]->java.util.HashSet[2])
	at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:243) ~[spring-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
	at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:225) ~[spring-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
 

错误日志的调用栈其实很长,这里只截取了一小部分。从日志里面我们可以看出,错误发生的阶段应该是在服务器把请求体里面的JSON字符串反序列化成JavaBean的过程中,错误产生的原因是这个Unexpected EOF read on the socket,既然有socket肯定就跟网络有关;这里的EOF又是什么呢,EOF就是End Of File的缩写,简单理解就是文件结束标志,Unexpected EOF read on the socket直译就是在socket中出现了不该出现的EOF,意思就是在不该结束的地方就结束了,字面意思是这样,但是实际上又是什么原因导致的呢。

先百度了一下,很多帖子都说是请求超时导致的,延长服务器的连接超时时间就可以了,但是我感觉这个不科学,因为超时异常一般都是报什么SocketTimeoutException,而不是这里的Unexpected EOF,我也延长了服务器的连接超时时间,结果无济于事,说明这个说法是错误的,那导致这个问题的原因是什么呢?我初步的猜想是客户端在传输请求体的过程中,因为网络超时或者关闭,导致服务器接受到的JSON不全,进而导致JSON反序列化异常。接下来为了重现这个异常,需要写一个简单的发送HTTP请求的代码,代码如下:

public class Main {
 
    public static void main(String[] args) throws Exception {
        try (Socket client = new Socket()) {
            InetSocketAddress inetSocketAddress = new InetSocketAddress("localhost", 9090);
            client.connect(inetSocketAddress, 1000);
 
            PrintWriter writer = new PrintWriter(client.getOutputStream());
 
            String body = "{\"appId\":\"com.promqueen\",\"channel\":\"VIVO\"," +
                    "\"language\":\"CN\",\"deviceType\":\"ANDR\"," +
                    "\"udid\":\"21312dfhdfh44sdasd\",\"landOrPort\":\"PORT\"," +
                    "\"installList\":[\"21312dfhdfh44sdasdd\",\"21312dfhdsd\"]}";
            int bodyLength = body.getBytes(StandardCharsets.UTF_8).length;
 
            String header = "POST /promote_app/accom/ad_info/v1 HTTP/1.1\n" +
                    "Host: localhost:9090\n" +
                    "Content-Type: application/json;charset=UTF-8\n" +
                    "Content-Length: " + bodyLength + "\n";
            //请求头
            writer.print(header);
            //空行
            writer.println();
            //请求体
            writer.print(body);
            writer.flush();
 
            BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
            String line;
            do {
                line = reader.readLine();
                System.out.println(line);
            } while (!line.equals(""));
        }
    }
}

这个代码就是向服务器发送一个POST请求,并提交JSON字符串,正常情况下会打印出响应的响应头,如下:

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Vary: Accept-Encoding
Date: Tue, 11 Aug 2020 06:38:23 GMT

响应的响应码是200,说明这个代码是正常的。我们怎么让它变得不正常呢,我最容易想到的情况就是在发送了一部分的JSON后,网络突然就阻塞或者直接断了,导致网络连接超时了,虽然我上面说了这个异常很可能不是超时导致的,我们好歹先试一试。我们把上面的发送JSON的代码改成这样:

            //先发送一部分JSON
            writer.print("{\"appId\":\"com.promqueen\",\"channel\":\"VIVO\"," +
                    "\"language\":\"CN\",\"deviceType\":\"ANDR\"," +
                    "\"udid\":\"21312dfhdfh44sdasd\",\"landOrPort\":\"PORT\"," +
                    "\"installList\":[\"21312dfhdfh44sdasdd\",\"21312dfhdsd\"");
            writer.flush();
 
            //模拟网络超时,在spring boot配置文件中设置超时时间为10s(server.connectionTimeout = 10000)
            Thread.sleep(15000);
 
            writer.print("]}");
            writer.flush();

果然服务器报错了:

15:40:51.727 [http-nio-9090-exec-1] ERROR com.app.component.ControllerExceptionHandler - JSON parse error: (was java.net.SocketTimeoutException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.net.SocketTimeoutException) (through reference chain: com.app.entity.AppServiceEntity["installList"]->java.util.HashSet[1])
 org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: (was java.net.SocketTimeoutException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.net.SocketTimeoutException) (through reference chain: com.app.entity.AppServiceEntity["installList"]->java.util.HashSet[1])

但是报的是SocketTimeoutException,不是Unexpected EOF,说明Unexpected EOF不是由于超时导致的,那它又是由于什么导致的呢,一时就没得头绪了,想不到还有什么其他的原因会导致问题的出现。

我们先分析一下这个问题。在错误日志的调用栈里面,抛出异常的根源在这里:

 /**
     * Attempts to read some data into the input buffer.
     *
     * @return <code>true</code> if more data was added to the input buffer
     *         otherwise <code>false</code>
     */
    private boolean fill(boolean block) throws IOException {
 
        if (parsingHeader) {
            if (byteBuffer.limit() >= headerBufferSize) {
                if (parsingRequestLine) {
                    // Avoid unknown protocol triggering an additional error
                    request.protocol().setString(Constants.HTTP_11);
                }
                throw new IllegalArgumentException(sm.getString("iib.requestheadertoolarge.error"));
            }
        } else {
            byteBuffer.limit(end).position(end);
        }
 
        byteBuffer.mark();
        if (byteBuffer.position() < byteBuffer.limit()) {
            byteBuffer.position(byteBuffer.limit());
        }
        byteBuffer.limit(byteBuffer.capacity());
        int nRead = wrapper.read(block, byteBuffer);
        byteBuffer.limit(byteBuffer.position()).reset();
        if (nRead > 0) {
            return true;
        } else if (nRead == -1) {
            throw new EOFException(sm.getString("iib.eof.error"));
        } else {
            return false;
        }
    }

这个方法的全称是org.apache.coyote.http11.Http11InputBuffer.fill,如果nRead == -1 就会抛出EOFException。结合以前学过的IO流的知识,当从输入流读取数据时,返回的读取字节数如果是-1的话,对于文件IO就表示文件读完了,也就是到了"end of file";如果是一个网络IO就表示输入流被关闭了。再想想HTTP的知识,对于使用长连接的情况下,服务器通过网络输入流读取数据时,是通过请求头Content-Length来判断请求体是否被读取完的,意思就是服务器先会去解析请求头,通过Content-Length就会知道请求体数据量的大小,然后再从输入流当中读取相应字节数的数据作为这个请求的请求体。通过上面的代码我们可以分析得出在正常情况下这个nRead是不应该为-1的,就是还有数据可以读出来,也就是还没有达到Content-Length所指定的数据量,然后输入流就关闭了。所有可以猜测出原因可能是在客户端发送JSON的过程中,在数据还没传完的情况下,连接被关闭了。我们把代码改成这样再验证一下:

            //先发送一部分JSON
            writer.print("{\"appId\":\"com.promqueen\",\"channel\":\"VIVO\"," +
                    "\"language\":\"CN\",\"deviceType\":\"ANDR\"," +
                    "\"udid\":\"21312dfhdfh44sdasd\",\"landOrPort\":\"PORT\"," +
                    "\"installList\":[\"21312dfhdfh44sdasdd\",\"21312dfhdsd\"");
            writer.flush();
 
            //模拟网络超时,在spring boot配置文件中设置超时时间为10s(server.connectionTimeout = 10000)
            //Thread.sleep(15000);
 
            //关闭输出流
            writer.close();
 
            //输出流关闭后再发送数据其实是没有意义的,这个代码可以注释掉
            writer.print("]}");
            writer.flush();

果然服务端抛出了Unexpected EOF read on the socket的异常信息,说明我们找到问题的原因了,就是客户端把连接给关闭了,然后服务器还没有读取到Content-Length所指定的数据量,输入流就关闭了,所以叫Unexpected EOF,就是在不该结束的时候结束了。我们还可以把代码稍微改改再来验证一下这个问题,我们把Content-Length改成先发送的部分JSON字符串的大小,看看服务器会报什么错。

//后面发送的"]}"占用了两个字节
int bodyLength = body.getBytes(StandardCharsets.UTF_8).length - 2;
 
·····
 
//不关闭输出流
//writer.close();

抛出异常:

16:51:46.866 [http-nio-9090-exec-7] ERROR com.app.component.ControllerExceptionHandler - JSON parse error: Unexpected end-of-input: expected close marker for Array (start marker at [Source: (PushbackInputStream); line: 1, column: 141])
 at [Source: (PushbackInputStream); line: 1, column: 353]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unexpected end-of-input: expected close marker for Array (start marker at [Source: (PushbackInputStream); line: 1, column: 141])

这时服务器抛出的异常没有再与socket相关的异常信息了,说明我们通过修改Content-Length骗过了服务器,让它觉得接受完了请求体,但是因为接受到的JSON是不完整的,所有反序列化时会显示JSON格式不正确。

最后我们可以看出这个问题其实客户端的问题,这个接口是给app使用的,所以我觉得应该是app退出前台后,网络被关闭导致的。

原文:Unexpected EOF read on the socket

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值