上次写《connection reset by peer, socket write error问题排查》已通过去大半年,当时把问题“敷衍”过去了。
可是此后每隔一段时间就会又想起来,baidu、google一番,可能也会再拉周围的人小讨论一下,而后无果而终。淡忘,想起,淡忘,又想起,挥之不去。html
这个周末它又在脑海中浮现,此次总算理解了这个问题,答案就在一本买了好久的新书《HTTP权威指南》中。若是懒得看下面的啰嗦,能够去直接看书中的《4.7.4 正常关闭链接》章节。实际上,我也只是为了找答案直接经过目录翻到了这一章,之后再找时间完整看一遍吧。java
再从新描述一下这个问题的现象和原由。
问题来源于一个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()
是全关闭操做。并发
能够看到不管是对于客户端仍是服务端,发送数据(输出信道)老是主动的,而接受数据(输入信道)老是被动的。socket
因此咱们能够看到关于链接关闭存在3种状况(从某一端的角度):搜索引擎
从上面的分析也能够看到,只有关闭输出是两端各自能够掌握主动权的,也就是相对安全的。google
HTTP规范只是建议了在要关闭一条链接时应该正常的关闭传输链接,可是没有说明具体该如何去作。
因为只有输出端是本身能够掌握主动权的,因此要想正常的关闭链接首先是各自关闭本身的输出信道,同时等对方关闭输出信道,这样链接就彻底关闭了,这样就不会出现“connecton reset”错误了。
可是,理想是美好的,现实中可能会比较无奈,没法确保双方都按照这个约定来操做。
因此除了作好本身这一方的关闭输出信道外,还须要周期性检查一下输入信道(对应于对方的输出)状态(是否还有数据,是否到了流的末尾),若是通过必定时间对方没有关闭仍是须要强制结束以节省时间。操作系统
问题的缘由清楚了。回头看看文件上传接口的场景,就是服务端数据接收的一方在客户端方处于发送数据的时候强制关闭了链接,也就形成了客户端“connection reset”的错误。
那为何小文件在一样的场景下没问题呢?由于小文件数据量小,在服务端关闭链接时就已经传输完成了。
那怎么解决大文件状况下的问题呢?貌似这个场景下没办法!由于服务端不该该在参数校验不经过的状况下等着客户端的数据流发送完,不然(实际上一开始说的临时解决办法skip真个content-length长度)就可能遇到可能安全问题(若是接口部署在局域网关系倒不大;若是部署在开放的互联网环境下,那就危险了,也就是若是不怀好意的人拿几个超大的文件少许的并发调用接口就能够把宝贵的带宽给占据了)。
既然技术角度没法解决了,只有从业务的角度来解决这个问题了。能够将这个文件上传接口拆分为两个接口,一个上传token生成接口,一个数据上传接口。token生成接口负责参数校验,若是校验成功则返回一个临时token,客户端用拿到的token再去上传数据。这样对于正常的调用方客户端应该不会再有问题,而对于非法的token不接收数据就很合理了。
回头想一想以前那篇文章中提到的找到的资料中说的服务端并发链接数达到上限、关掉浏览器等,均可以解释的通了。
这也反映了一个问题,搜索引擎每每只能找到少部分问题的真正答案,要想可以触类旁通,仍是得从书中获取成体系的知识。只有全面系统的理解了一个知识体系,才能在遇到问题时具有以不变应万变的能力。 假如以前对HTTP或者TCP有必定的理解,那这个问题应该很容易就想通了。