socket流读取read阻塞和readLine阻塞问题解决方案
场景:编写一个简单的httpserver,请求一直无响应。
分析:经排查,发现是在对socket的inputStream的最后一行读取时阻塞了。代码大概如下:
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); String line = ""; while ((line = br.readLine()) != null) { System.out.println(line); }
在网上搜索得出问题的根本原因:socket流没有结束符。我们对流的读取大概分两种,read()和readLine()。
阻塞场景:read() 没有读取到任何数据
readLine() 没有读取到结束符或者换行符
正是因为socket流没有结束符,而我们又不能强求请求体最后一定加上换行符,所以导致在readLine最后一行阻塞了。
1、换成read方法读取也不行,比如下面;结果read永远不会返回-1,因为没有结束符,最后在没有读取到数据的情况没有返回-1,而是选择了等待。
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); char[] bt = new char[1024]; while (br.read(bt) != -1) { System.out.println(bt); }
2、对于流数据比较小的情况,我们可以给bt初始化一个足够大的长度,一次将所有数据读取出来;http请求可以保证请求数据不会为空,继而保证了第一次读取不会阻塞。但这只是个妥协的办法,因为我们请求长度不可控。
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); char[] bt = new char[足够大]; br.read(bt); System.out.println(bt);
解决方案:在读取前,使用ready()方法判断是否还有数据没有读取。http请求不会为空,所以使用do-while保证能读取到数据。注意:这里如果使用while,可能会在第一次ready()时数据还没传输过来导致直接跳过。
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); char[] bt = new char[1024]; do { br.read(bt); System.out.println(bt); } while (br.ready());
最后再看看最终http请求读取、解析的代码。为了方便解析,请求头使用readLine(),请求体使用read。而且readLine()效率高于read()。
public void parse() throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); int lineNum = 0; String line = ""; while ((line = br.readLine()) != null) { if ("".equals(line)) { System.out.println(""); //请求体 parseContent(br); break; } if (lineNum++ == 0) { //首行 parseFirstLine(line); } else { //请求头 parseHeader(line); } } } private void parseFirstLine(String line) { String[] first = line.split("\\s"); method = first[0]; url = first[1]; agree = first[2]; System.out.println(line); } private void parseHeader(String line) { //TODO 待实现 System.out.println(line); } private void parseContent(BufferedReader br) throws IOException { StringBuilder sb = new StringBuilder(); char[] bt = new char[1024]; while (br.ready()) { br.read(bt); sb.append(bt); }; this.content = sb.toString(); System.out.println(content); }
结果与http请求格式: