V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Joker123456789
V2EX  ›  Java

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

  •  
  •   Joker123456789 · 2020-12-12 21:00:43 +08:00 · 2276 次点击
    这是一个创建于 1223 天前的主题,其中的信息可能已经有所发展或是发生改变。

    先上代码

    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 的 大神们指点一下

    BBCCBB
        1
    BBCCBB  
       2020-12-12 21:33:43 +08:00   ❤️ 1
    这个 read(buf)方法返回 0 也是正常的现象, 各种情况会返回 0, 但是是正常现象, 所以这里这个 while 应该不对

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

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

    可以直接用 netty, 看看里面的 LengthFieldBasedFrameDecoder 实现, 直接用 netty 吧. 看懂里面的实现就行.
    BBCCBB
        2
    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
        3
    Joker123456789  
    OP
       2020-12-12 21:59:05 +08:00
    @BBCCBB 谢谢,我尝试了一下改成 -1,但是 这样一来就一直死循环了,只有强行取消本次请求 才会变成-1,否则到 0 就下不去了,有点头疼。
    sagaxu
        4
    sagaxu  
       2020-12-12 23:04:57 +08:00 via Android
    socket 是个 stream,你有两个选择,
    1. 短连接,每次写入完 close,读的读到-1 结束
    2. 长连接,自定义协议确定边界,支持复用

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

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

    但是第二种方案有个问题,不知道边界如何确定,因为客户端传来的数据是变化多端的。
    laminux29
        6
    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
        7
    zacharyjia  
       2020-12-13 09:42:25 +08:00
    @Joker123456789 Http 的话,header 里面应该有当前请求的长度,可以根据它判断边界
    cheng6563
        8
    cheng6563  
       2020-12-13 12:21:22 +08:00 via Android
    http 一是看 content-length 判断长度,
    如果没有 content-length 则是一行空行表示结束。
    chenshun00
        9
    chenshun00  
       2020-12-13 13:02:05 +08:00
    NIO 开发 http,你这样解析 http 的呀,可以先链接一下 netty 的 http 协议是怎么做的。 这样的读取肯定是不行的.
    Joker123456789
        10
    Joker123456789  
    OP
       2020-12-13 13:30:40 +08:00
    @zacharyjia
    @cheng6563
    @laminux29
    @BBCCBB

    谢谢大佬们,已经解决了,通过查看读到的内容中有没有 \r\n\r\n 来判断头读完了没,如果读完了则获取到 Content-Length, 然后继续读 body,再判断已经读到的 body 的长度是否>= Content-Length 。
    lei2j
        11
    lei2j  
       2020-12-13 14:25:27 +08:00
    为啥不用 netty 呢
    Joker123456789
        12
    Joker123456789  
    OP
       2020-12-13 14:33:46 +08:00
    @lei2j 不想依赖第三方,netty 很好很强大,但是里面我用不到的东西也有很多,不够小。
    shentar
        13
    shentar  
       2020-12-13 15:33:33 +08:00
    @Joker123456789 看起来需要用 select 啊,监听读写事件。还是要读一下 netty,jetty 的网络处理部分: https://codefine.site/652.html
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3619 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 04:34 · PVG 12:34 · LAX 21:34 · JFK 00:34
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.