上次写《connection reset by peer, socket write error问题排查》已经过去大半年,当时把问题“敷衍”过去了。

但是此后每隔一段时间就会又想起来,baidu、google一番,可能也会再拉周围的人小讨论一下,然后无果而终。淡忘,想起,淡忘,又想起,挥之不去。

这个周末它又在脑海中浮现,这次总算理解了这个问题,答案就在一本买了很久的新书《HTTP权威指南》中。如果懒得看下面的啰嗦,可以去直接看书中的《4.7.4 正常关闭连接》章节。实际上,我也只是为了找答案直接通过目录翻到了这一章,以后再找时间完整看一遍吧。

问题现象

再重新描述一下这个问题的现象和起因。

问题来源于一个http的文件上传接口,接口会先对一些参数签名进行校验,参数签名通过之后才会取出InputStream,将文件数据保存起来。如果参数校验失败或者检查到文件已经存在(参数上会带md5),则直接返回了错误信息。

实际上大多数情况挺正常的,但是偶尔在客户端会出现“connection reset by peer, socket write error”。这个错误通过搜索引擎找了答案,都不能解释遇到的现象,只有尝试着猜测和重现了。经过尝试发现,只有比较“大”的文件在参数校验失败或者属于重复上传的情况才能重现这个错误。

所以猜测应该是当客户端上传大文件时,服务端接收到了http header就拿到了接口参数,可以开始进行校验了,不符合条件时就直接返回了Response,关闭OutputStream的同时也把InputStream给close掉了。

基于此猜测,在服务端改动了一下,返回Response之前,先request.getInputStream().skip(request.getContentLength)。果然。问题不会出现了,虽然接口处理变慢了。

然后,我通过wireshark进行了抓包,实际上也抓到了服务端返回的错误码信息,也就是说服务端在这个情况下,Response已经输出了,而且很可能客户端是收到了的。

这个是令人比较矛盾的地方,并不是服务端数据没有输出啊,为什么客户端接收不到这个响应,而且是直接报了一个奇怪的错误呢?

翻了书之后,才弄清楚了其中的细节,细节是魔鬼啊。

关于连接的关闭

TCP连接是双向的,TCP连接的每一端都有一个输入队列和一个输出队列,用于数据的读或者写。

放入一端输出队列的数据会被传送到另一端的输入队列。

Recv-Q 输入<-------------------------------------------------输出 Send-Q
Client ------------------------------------------------------- Server
Send-Q 输出------------------------------------------------->输入 Recv-Q

连接的全关闭和半关闭

当应用程序的通过TCP通信时,Client端和Server端都可以关闭输入和输出信道中的某一个,或者两个都关闭。

如果只关闭其中的一个,称之为“半关闭”,如果两个都关闭,称之为“全关闭”。

这两种操作对应java里的Socket有相应的方法,shutdownInput()或者shutdownOutput()是半关闭操作,close()是全关闭操作。

connection reset错误的产生

可以看到不论是对于客户端还是服务端,发送数据(输出信道)总是主动的,而接受数据(输入信道)总是被动的。

  1. 当主动发送数据的一方完成数据发送,进行shutdownOutput之后,另一方的接受端在从缓冲区读出所有数据后会收到一条通知,说明数据流结束了,这样接受端就知道连接关闭了。
  2. 但是反过来,如果被动接收数据的一方想要停止接收数据,也就是shutdownInput时,它并不知道数据发送方是否还要发送数据;

    当接收端直接shutdownInput时,数据发送方却可能还在往缓冲区写数据呢,如果这个时候对方关闭连接的通知还没有到达这边,那么数据依然会被传送到已经shutdownInput另一端,这个时候另一端的操作系统会回复一条“连接被对方重置”的报文过去。

    当数据发送方出现这种情况时,大多数操作系统都会作为很严重的错误来处理,会删除掉对端还未读取的所有缓存数据。

所以我们可以看到关于连接关闭存在3种情况(从某一端的角度):

  1. 完全关闭:直接关闭输入和输出
  2. 半关闭(Output):关闭输出,
  3. 半关闭(Input):关闭输入

从上面的分析也可以看到,只有关闭输出是两端各自可以掌握主动权的,也就是相对安全的。

正常关闭

HTTP规范只是建议了在要关闭一条连接时应该正常的关闭传输连接,但是没有说明具体该如何去做。

由于只有输出端是自己可以掌握主动权的,所以要想正常的关闭连接首先是各自关闭自己的输出信道,同时等对方关闭输出信道,这样连接就完全关闭了,这样就不会出现“connecton reset”错误了。

但是,理想是美好的,现实中可能会比较无奈,无法确保双方都按照这个约定来操作。

所以除了做好自己这一方的关闭输出信道外,还需要周期性检查一下输入信道(对应于对方的输出)状态(是否还有数据,是否到了流的末尾),如果经过一定时间对方没有关闭还是需要强制结束以节省时间。

解决问题

问题的原因清楚了。回头看看文件上传接口的场景,就是服务端数据接收的一方在客户端方处于发送数据的时候强制关闭了连接,也就造成了客户端“connection reset”的错误。

那为什么小文件在同样的场景下没问题呢?因为小文件数据量小,在服务端关闭连接时就已经传输完成了。

那怎么解决大文件情况下的问题呢?貌似这个场景下没办法!因为服务端不应该在参数校验不通过的情况下等着客户端的数据流发送完,否则(实际上一开始说的临时解决办法skip真个content-length长度)就可能遇到可能安全问题(如果接口部署在局域网关系倒不大;如果部署在开放的互联网环境下,那就危险了,也就是如果不怀好意的人拿几个超大的文件少量的并发调用接口就可以把宝贵的带宽给占据了)。

既然技术角度无法解决了,只有从业务的角度来解决这个问题了。可以将这个文件上传接口拆分为两个接口,一个上传token生成接口,一个数据上传接口。token生成接口负责参数校验,如果校验成功则返回一个临时token,客户端用拿到的token再去上传数据。这样对于正常的调用方客户端应该不会再有问题,而对于非法的token不接收数据就很合理了。

举一反三

回头想想之前那篇文章中提到的找到的资料中说的服务端并发连接数达到上限、关掉浏览器等,都可以解释的通了。

其他

这也反映了一个问题,搜索引擎往往只能找到少部分问题的真正答案,要想能够举一反三,还是得从书中获取成体系的知识。只有全面系统的理解了一个知识体系,才能在遇到问题时具备以不变应万变的能力。

假如之前对HTTP或者TCP有一定的理解,那这个问题应该很容易就想通了。

重新分析connection reset by peer, socket write error错误原因的更多相关文章

  1. connection reset by peer, socket write error问题排查

    2018-03-15更新:弄明白connection reset产生的原因,见重新分析connection reset by peer, socket write error错误原因 在开发文件上传功 ...

  2. spring+ibatis问题1—— 程序报错:java.sql.SQLException: Io 异常: Connection reset by peer, socket write error; ”或“java.sql.SQLException 关闭的连接”异常

    转自:http://blog.sina.com.cn/s/blog_1549fb0710102whz2.html spring+ibatis程序测试时报错:java.sql.SQLException: ...

  3. jmeter测试文件上传接口报错:connection reset by peer: socket write error

    最近在对文件上传接口性能测试时,设置150线程数并发时,总会出现以下错误:connection reset by peer: socket write error 在网上搜索了一下,得到的原因有这些: ...

  4. Error -27780: Connection reset by peer: socket write error

    Problem Description: Error: "-27780: read to host failed: [10054] Connection reset by peer" ...

  5. Jmeter遇到线程链接被重置(Connection reset by peer: socket write error)的解决方法

    做性能测试的时候遇到一个很奇怪的问题,多线程的计划,有一个线程第一次能跑过,第二次确跑不过,单独跑这个线程跑多少次都没有问题,把思考时间改短也没有问题,唯独出现在特定的状态下,特定状态是啥,也不得而知 ...

  6. Connection reset by peer: socket write error 连数据库出现改错

    1.网络原因 2.从池中获取连接后没有释放到池中导致的

  7. Connection reset by peer的常见原因及解决办法 RST 大文件上传

    Connection reset by peer的常见原因及解决办法 Connection reset by peer的常见原因 - 简书 https://www.jianshu.com/p/263e ...

  8. Connection reset by peer问题分析

    extremetable导出excel,弹出一个下载窗口,这时不点下载而点取消,则报下面的异常: ClientAbortException Caused by: java.net.SocketExce ...

  9. atitit.故障排除------有时会错误com.microsoft.sqlserver.jdbc.SQLServerException: Connection reset by peer: soc

    atitit.故障排除------有时会错误com.microsoft.sqlserver.jdbc.SQLServerException: Connection reset by peer: soc ...

随机推荐

  1. C#多线程编程实战1.5检测线程状态

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...

  2. 【leetcode 138. 复制带随机指针的链表】解题报告

    方法一:递归 unordered_map<Node*,Node*> dict; Node* copyRandomList(Node* head) { if (!head) return h ...

  3. OOP3(继承中的类作用域/构造函数与拷贝控制/继承与容器)

    当存在继承关系时,派生类的作用域嵌套在其基类的作用域之内.如果一个名字在派生类的作用域内无法正确解析,则编译器将继续在外层的基类作用域中寻找该名字的定义 在编译时进行名字查找: 一个对象.引用或指针的 ...

  4. 【AOP】基于@Aspect的AOP配置

    基于spring cloud的aop配置 1,启动类MemberAppliaction增加注解 @Import({SwaggerConfiguraion.class, WebMvcAutoConfig ...

  5. Sample-Code:Translator

    <h2>My Spanish Translator</h2> <p> Enter your text in English:  </p> <p&g ...

  6. P4855 MloVtry的idea

    $ \color{#0066ff}{ 题目描述 }$ MloVtry是一个脑洞很大的人,它总会想出一些奇奇怪怪的idea. 可问题是,MloVtry作为一个蒟蒻,很多时候都没办法解决自己提出的问题,所 ...

  7. 大型php网站性能和并发访问优化方案(转载自php中文网)

               网站性能优化对于大型网站来说非常重要,一个网站的访问打开速度影响着用户体验度,网站访问速度慢会造成高跳出率,小网站很好解决,那对于大型网站由于栏目多,图片和图像都比较庞大,那该怎 ...

  8. 6、C++共用体

    6.共用体 共用体(union)是一种数据格式,他能够存储不同的数据类型,但只能同时存储其中的一种类型.也就是说,结构可以同时存储int.long和double,共用体只能存储ing.long.dou ...

  9. linux 下PATH环境变量

    环境变量简介 什么是环境变量呢?简要的说,就是指定一个目录,运行软件的时候,相关的程序将会按照该目录寻找相关文件. 在linux系统下,如果你下载并安装了应用程序,很有可能在键入它的名称时出现&quo ...

  10. flask简单了解

    Flask简介: Flask是一个Python编写的Web 微框架,让我们可以使用Python语言快速实现一个网站或Web服务,在介绍Flask之前首先来聊下它和Django的联系以及区别,djang ...