Java NIO实现HTTP服务器返回响应后浏览器一直加载如何解决?
问题原因和修复方案
1. HTTP响应缺少结束标识,客户端无法判断数据传输完成
HTTP/1.1默认启用Keep-Alive持久连接,如果你没有明确告知客户端响应的总长度,客户端会一直等待后续数据,直到连接超时或者被主动断开。你终止Java程序时连接被强制关闭,浏览器才会认为响应结束,所以此时才会渲染内容。
解决方案二选一即可:
- 在响应头增加
Content-Length字段,准确填写响应体的字节长度,客户端收到对应长度的数据就会自动结束请求 - 在响应头增加
Connection: close字段,告知客户端响应发送完成后会直接关闭连接,客户端收到完整响应就会终止加载
规范的响应示例(注意HTTP头的换行必须用\r\n,不能只用\n,否则会出现解析异常):
String response = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "Content-Length: 23\r\n" + "\r\n" + "<html><body>OK</body></html>";
上面示例中Content-Length的值为23,对应后面<html><body>OK</body></html>的UTF-8编码字节长度,需要根据实际响应内容准确计算。
2. 非阻塞SocketChannel写入逻辑存在缺陷
非阻塞模式下的SocketChannel.write()方法不保证一次性把缓冲区的所有数据写完,它只会写入内核发送缓冲区可容纳的字节数,返回实际写入的字节长度。你现在调用一次write就直接把感兴趣事件切回OP_READ,大概率会有部分数据残留在缓冲区没有发送,客户端收不到完整响应。
解决方案:写入后判断缓冲区是否还有剩余数据,如果还有就保留OP_WRITE事件,下次通道可写时继续发送,直到缓冲区所有数据写完再切换事件状态。
修改后的writeToKey参考代码:
private void writeToKey(SelectionKey key) throws IOException { SocketChannel clientSkt = (SocketChannel) key.channel(); ClientState state = (ClientState) key.attachment(); ByteBuffer buf = state.getWriteBuffer(); // 建议将待发送的缓冲区存在attach对象中,不要每次新建 int written = clientSkt.write(buf); if (written < 0) { // 客户端已经主动断开连接,直接回收资源 key.cancel(); clientSkt.close(); return; } if (buf.hasRemaining()) { // 还有数据未写完,继续监听可写事件 key.interestOps(SelectionKey.OP_WRITE); } else { // 数据全部写完,如果使用短连接逻辑直接关闭即可,长连接则切回监听读事件 key.interestOps(SelectionKey.OP_READ); // 短连接逻辑示例: // clientSkt.close(); // key.cancel(); } }
其他注意事项
- 你代码中的
Srtring是拼写错误,正确写法为String,属于低级笔误,编译阶段就能发现 - 读取请求的逻辑目前也存在问题:你只读一次缓冲区就认为请求读取完成,实际如果请求较大或者出现TCP分片,会出现一次读不完的情况,也需要和写入逻辑一样做累计读取处理,直到拿到完整的HTTP请求
内容的提问来源于stack exchange,提问作者Samuele B.
相关产品推荐
相关产品推荐

