关于 NIO 网络编程的一个问题

2020-12-12 21:00:43 +08:00
 Joker123456789

先上代码

ByteBuffer readBuffer = ByteBuffer.allocate(requestSize);// 这里是 1024*1024*1024 = 1G
readBuffer.clear();

SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

while (socketChannel.read(readBuffer) > 0) {}

/* 获取请求报文 */
readBuffer.flip();
byte[] bytes = new byte[readBuffer.limit()];
readBuffer.get(bytes);

如上面代码所示,缓冲区 ByteBuffer 的容量设置成了 1G,但是上传一个 30M 左右的文件,经常会丢包,导致文件不全,然后 formData 解析时报错。

这个缓冲区的容量如果比文件大很多,则一点问题都没,但是我又不能把缓冲区设置的太大,否则会堆溢出。

麻烦熟悉 NIO 的 大神们指点一下

2287 次点击
所在节点    Java
13 条回复
BBCCBB
2020-12-12 21:33:43 +08:00
这个 read(buf)方法返回 0 也是正常的现象, 各种情况会返回 0, 但是是正常现象, 所以这里这个 while 应该不对

要自己定一个协议, 数据缓存起来, 只要 read 不是返回-1, 就继续往 buf 里写数据, 直到达到你定义的协议的包的结束点..

比如定长的协议. |--- length-- | your data |, eg: 开头 4 个字节代表数据包长度, 读到这么长的数据才算完成读取.

可以直接用 netty, 看看里面的 LengthFieldBasedFrameDecoder 实现, 直接用 netty 吧. 看懂里面的实现就行.
BBCCBB
2020-12-12 21:36:01 +08:00
我百度的返回 0 的情况:

其实 read 返回 0 有 3 种情况,一是某一时刻 socketChannel 中当前(注意是当前)没有数据可以读,这时会返回 0,其次是 bytebuffer 的 position 等于 limit 了,即 bytebuffer 的 remaining 等于 0,这个时候也会返回 0,最后一种情况就是客户端的数据发送完毕了(注意看后面的程序里有这样子的代码),这个时候客户端想获取服务端的反馈调用了 recv 函数,若服务端继续 read,这个时候就会返回 0 。
Joker123456789
2020-12-12 21:59:05 +08:00
@BBCCBB 谢谢,我尝试了一下改成 -1,但是 这样一来就一直死循环了,只有强行取消本次请求 才会变成-1,否则到 0 就下不去了,有点头疼。
sagaxu
2020-12-12 23:04:57 +08:00
socket 是个 stream,你有两个选择,
1. 短连接,每次写入完 close,读的读到-1 结束
2. 长连接,自定义协议确定边界,支持复用

循环读,-1 就结束,0 继续等待可读通知,遇到边界就解出一个完整报文放入处理队列
Joker123456789
2020-12-12 23:24:51 +08:00
@sagaxu

我只能用第二种方案了,因为我是基于 NIO 在开发 http 服务,所以需要等待响应,客户端不能关闭的

但是第二种方案有个问题,不知道边界如何确定,因为客户端传来的数据是变化多端的。
laminux29
2020-12-13 01:38:17 +08:00
1.Buffer 只是个临时缓存,requestSize 没必要等于 file size,不然百度网盘的 SVIP 价格得翻几倍。你下载一个迅雷,以及其他 BT 软件,看看里面的硬盘缓存最大值是多少。

一般来说 requestSize 的 sizes 是 4k 的倍数,老旧服务端一般是 4/8/16 KB,再有钱的 bat 也最多 1/2MB 。

2.建议 debug,看看 ByteBuffer.allocate 之后里面到底是什么,有没有必要再做 clear()。

3.里面很多方法都会抛异常,建议阅读文档,该处理的一定要处理。

4.while (socketChannel.read(readBuffer) > 0) {} ,这也是没读文档造成的。先不说异常处理,read 方法有几种返回值,认真看一下文档。

5.重新改正后,先找个 100 字节文档测试一下,java 端收到后立马 write file,然后与 source file 对比一下内容。没问题后再测试 100KB file 、100MB file 甚至几十 GB 的 file 。先 md5 对比,不一样后再找一款基于 byte 对比的软件,比如 BeyondCompare 、TextDiff 甚至 Ultra Edit 。
zacharyjia
2020-12-13 09:42:25 +08:00
@Joker123456789 Http 的话,header 里面应该有当前请求的长度,可以根据它判断边界
cheng6563
2020-12-13 12:21:22 +08:00
http 一是看 content-length 判断长度,
如果没有 content-length 则是一行空行表示结束。
chenshun00
2020-12-13 13:02:05 +08:00
NIO 开发 http,你这样解析 http 的呀,可以先链接一下 netty 的 http 协议是怎么做的。 这样的读取肯定是不行的.
Joker123456789
2020-12-13 13:30:40 +08:00
@zacharyjia
@cheng6563
@laminux29
@BBCCBB

谢谢大佬们,已经解决了,通过查看读到的内容中有没有 \r\n\r\n 来判断头读完了没,如果读完了则获取到 Content-Length, 然后继续读 body,再判断已经读到的 body 的长度是否>= Content-Length 。
lei2j
2020-12-13 14:25:27 +08:00
为啥不用 netty 呢
Joker123456789
2020-12-13 14:33:46 +08:00
@lei2j 不想依赖第三方,netty 很好很强大,但是里面我用不到的东西也有很多,不够小。
shentar
2020-12-13 15:33:33 +08:00
@Joker123456789 看起来需要用 select 啊,监听读写事件。还是要读一下 netty,jetty 的网络处理部分: https://codefine.site/652.html

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/734848

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX