从 Java NIO socketchannel 读取字节,直到到达标记

Posted

技术标签:

【中文标题】从 Java NIO socketchannel 读取字节,直到到达标记【英文标题】:Read bytes from Java NIO socketchannel until marker is reached 【发布时间】:2015-07-17 17:47:15 【问题描述】:

我正在寻找一种使用 Java NIO 从套接字通道读取字节的有效方法。任务很简单,我有一个解决方案,尽管我正在寻找一种更清洁、更有效的方法来解决这个问题。场景如下:

    从套接字通道读取数据 此数据是 UTF-8 编码的字符串 每一行都以\r\n结尾,前面长度未知 读完每一行后,我想对消息做点什么

我的解决方案按字节读取数据字节,并将每个字节与我的标记(在 UTF-8 代码页中的值为 10)进行比较。代码如下:

ByteBuffer res = ByteBuffer.allocate(512);
boolean completed = false;
try 
    while (true) 
        ByteBuffer tmp = ByteBuffer.allocate(1);
        if(soc.read(tmp) == -1) 
             break;
        

        // set marker back to index 0
        tmp.rewind();
        byte cur = tmp.get();
        res.put(cur);

        // have we read newline?
        if (cur == 10) 
            doSomething(res);
            res.clear();
        
    

 catch(Exception ex) 
     handle(ex);

即使这样做了,也可能有更好的方法,不需要在每次迭代后进行逐字节比较。

感谢您的帮助!

【问题讨论】:

【参考方案1】:

我这样做的方法是尽可能多地读取,例如 32 KB,一旦你读过这个,你就可以将数据逐字节复制到另一个缓冲区,例如一个字符串生成器。如果上次读取时缓冲区中还有数据,您可以继续使用缓冲区,直到它全部耗尽,此时您可以读取更多数据。

注意:每个系统调用都很昂贵。这可能需要 2-5 微秒。除非您调用它数百万次,否则这听起来并不多,它会增加读取 1 MB 的时间。

【讨论】:

好的,我已经更改了代码,以便它使用更大的缓冲区来减少系统调用的数量。我使用两个相同大小的缓冲区,在每个循环中,我从套接字读取到缓冲区 A。之后,我遍历该缓冲区并将所有字节复制到相同大小的缓冲区 B 中。如果我到达我的标记,我会处理缓冲区 B 并重新分配缓冲区 B 以确保较短的消息不会从较长的运行中命中字节。如果缓冲区大小和标记不匹配,下一次运行只是追加,这样我就不必关心余数。 如果你 write(byteBuffer) 从一个缓冲区到另一个缓冲区,它可以比逐字节执行快 8 倍或更多。【参考方案2】:

这是我最终解决方案的代码。

ByteBuffer res = ByteBuffer.allocate(maxByte);
while (true) 
    ByteBuffer tmp = ByteBuffer.allocate(maxByte);

    int bytesRead = clientSocket.read(tmp);
    if (bytesRead == -1) 
        break;
    

    // rewind ByteBuffer to get it back to start
    tmp.rewind();

    for (int i = 0; i < bytesRead; i++) 
        byte cur = tmp.get(i);
        res.put(cur);
        if (cur == marker) 
            processMessage(res);
            res = ByteBuffer.allocate(maxByte);
        
    

    // reached end of message, break loop
    if (bytesRead < tmpSize) 
        break;
    

【讨论】:

您不需要在每次循环时都分配一个新的tmp 缓冲区。您应该在get() 之前flip() 缓冲区,然后在compact() 之后,而不是rewind()。您无需在成功时重新分配 res:只需 clear() 即可。 什么是更好的选择,在每次 get() 之后或到达标记之后进行压缩?我对 clear() 的问题是,它只在逻辑上“清除”缓冲区。我遇到了我的消息长度可变的问题,如果以下消息比前一个消息短,我会处理来自先前迭代的“旧”数据而无法识别这一点。我还没有找到不重新分配它的方法。 1. compact() 在每个 flip() 之后。 2. 你的问题是你没有打电话给compact()。在具有相同容量的现有设备上分配新的ByteBufferclear() 之间的差异为零,只是clear() 的效率高出许多倍。 好的,我已将 tmp.rewind() 替换为 tmp.flip(); tmp.compact() 并将 res = ByteBuffer.allocate(maxByte); 替换为 res.clear()。我又回到了老问题……我知道我显然遗漏了一些东西,我可能也需要翻转另一个缓冲区……你能给我一个我的代码示例吗? 它必须是翻转、获取、压缩的顺序。

以上是关于从 Java NIO socketchannel 读取字节,直到到达标记的主要内容,如果未能解决你的问题,请参考以下文章

Java NIO系列教程 SocketChannel

Java NIO系列教程 SocketChannel

在 Java NIO 中,选择器对客户端 SocketChannel 有用吗?

Java NIO系列教程 SocketChannel

九Java NIO SocketChannel

Java NIO系列教程 SocketChannel