flush方法何时有效何时无效

客户端解析问题导致flush方法无效

在一个下载需求中,为了优化用户体验,在服务端的数据处理前加入了以下代码:

response.getOutputStream().flush()
.....查询数据.......

flush方法会把已写入的数据头立即发送到客户端。

虽然以前就知道flush的作用,但毕竟没看过它的源码,突然想到一个测试:

@RequestMapping("hello")
@ResponseBody
public String hello(HttpServletRequest request,HttpServletResponse response) throws Exception {
		response.getOutputStream().write("h1".getBytes());
		response.getOutputStream().flush();
		Thread.sleep(20000);
		return "h2";
	}

在这段代码运行前,我预料中的结果应该是:客户端首先会收到“h1”,等待20s后,收到“h2”。

我启动服务端后,在浏览器中输入接口地址,回车…

结果大跌眼镜:屏幕一片空白,直到20s后,h1和h2竟然同时出现了。

难道我的认知是错的?flush不会把response中的内容发送到客户端?

我重新以debug模式启动服务,进入flush方法一步步的查看,没有看到什么奇怪的地方

为了对比,我又添加了close方法:

@RequestMapping("hello")
@ResponseBody
public String hello(HttpServletRequest request,HttpServletResponse response) throws Exception {
		response.getOutputStream().write("h1".getBytes());
		response.getOutputStream().flush();
		response.getOutputStream().close();
		Thread.sleep(20000);
		return "h2";
	}

在close方法的源码中也没有看到蹊跷的地方,倒是发现它和flush方法前面很多地方都是一致的,这就很奇怪了。

我只好求助网上,然后就看到了这个:

难道浏览器也是这样的吗??

我赶忙去验证,修改了下代码:

@RequestMapping("hello")
@ResponseBody
public String hello(HttpServletRequest request,HttpServletResponse response) throws Exception {
		response.getOutputStream().write("h1\r\n".getBytes());
		response.getOutputStream().flush();
		Thread.sleep(20000);
		return "h2";
	}

然后启动,访问,屏幕立刻出现了“h1”,20s后,出现了“h2”…

果然是这样。

最后的总结就是:

一开始的认知是对的,之所以浏览器出现这样的现象,是因为它是按行解析,所以虽然没有看到消息,但其实它已经收到了,只是没有显示出来而已。这个可能也跟返回的数据类型有关,如果是二进制流或者文件,可能也不会出现这样的问题。

服务端造成的flush方法无效

其实有一个令人很沮丧的事情:

在大部分的web开发场景中,如果你调用flush或者flushBuffer方法,其实是没有效果的…

一开始我也很苦恼这个问题是谁造成的,因为flush的API说明清楚地写着是会把消息强制发送到客户端的,网上查找了一些,发现大家的问题也千奇百怪:

  • 有的是客户端的解析问题,像我上面这种或者https://stackoverflow.com/questions/27739496/servlet-response-buffer-not-flushing-without-newlines
  • 有的是服务器配置的问题,像https://stackoverflow.com/questions/43453508/end-to-end-reactive-streaming-restful-service
  • 还有的是响应头设置的问题:setContentType、setStatus等等
  • https://bz.apache.org/bugzilla/show_bug.cgi?id=3941

有一点需要注意的是,在这些问题下面,很多人提到了:flush方法对他们是有效的

这就有意思了,而且我在自己测试过程中也发现了一个需要小心的干扰因素:

接口地址中使用‘localhost’和‘本机外网IP’测试结果是不同的,flush方法在localhost测试中的确是有效的,但IP不然,这可能和两者网络行为不同有关。

localhost一般只有开发者自己使用,所以我们最好还是用IP来测试。

我建议的测试环境:

  • 服务端使用springboot,方便更换server测试(tomcat、undertow最好都试一下)
  • 接口地址要使用IP,不要使用localhost或者127.0.0.1
  • 请求使用curl

后续我终于看到了可能对我有帮助的答案:

It turns out there is a limit on the underlying Apache/Windows IP stack that buffers data from a stream in an attempt to be efficient. Since most people have the problem of too much data, not the problem of too little data, this is right most of the time. What we ended up doing was requiring the user to request enough data that we'd hit the 1000 byte limit before timing out. Sorry for taking so long to answer the question.
https://stackoverflow.com/questions/6984825/servlet-buffering-response-despite-calls-to-flush?noredirect=1&lq=1

于是我修改了测试代码:

	@RequestMapping("streamTest")
	public void streamTest(HttpServletRequest request,HttpServletResponse response) throws Exception {
		 response.setContentType("text/html");
		  PrintWriter writer = response.getWriter();
		  int count=55000;
		  for(int i=0;i<count;i++) {
			  writer.println("Before sleep<br/>");
		  }		 
		  writer.flush();
		  response.flushBuffer();
		  try {
		    Thread.sleep(5000);
		  } catch (InterruptedException e) {
		  }
		  writer.print("After sleep<br/>");
	}

当count是10000的时候,客户端依旧是在5s后一起收到了响应,但是当count增大到55000,是否使用flush方法才有了区别,使用flush时,客户端先收到了一部分数据,5s后收到了另一部分。

这说明flush无效的问题应该是跟服务端的传输层有关的。

不过socket函数提供对缓冲区操作的api,所以如果自己实现一个应用层协议,应该可以避免这个问题。

还有一点就是:

如果设置了content-length

response.setContentLength(s.length());

调用flush后,客户端也可以马上收到响应(tomcat7可以,undertow不行),但这个依旧是服务端的行为。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值