Java SocketChannel 吃掉我的字节

Posted

技术标签:

【中文标题】Java SocketChannel 吃掉我的字节【英文标题】:Java SocketChannel Eating my bytes 【发布时间】:2009-11-16 16:01:15 【问题描述】:

我为远程服务器创建了一个 SocketChannel,以便在 Tomcat 上发送和接收消息。为了从远程计算机接收消息,我使用了一个专用于任务的线程(只有这个线程会从套接字读取,没有别的)。

当 SocketChannel 接收到一些字节时(我一直在非阻塞模式下轮询 SocketChannel 以获取新数据),我首先读取 4 个字节以获取下一条消息的长度,然后从 SocketChannel 分配并读取 x 个字节,然后将其解码并重构为消息。

下面是我的接收线程代码:

@Override
public void run() 

    while (true)  //Don't exit thread

        //Attempt to read the size of the incoming message
        ByteBuffer buf = ByteBuffer.allocate(4);

        int bytesread = 0;
        try 
            while (buf.remaining() > 0) 
                bytesread = schannel.read(buf);

                if (bytesread == -1)  //Socket was terminated

                 

                if (quitthread) break;
            

         catch (IOException ex) 

        

        if (buf.remaining() == 0) 
            //Read the header
            byte[] header = buf.array();
            int msgsize = (0xFF & (int)header[0]) + ((0xFF & (int)header[1]) << 8)
                    + ((0xFF & (int)header[2]) << 16) + ((0xFF & (int)header[3]) << 24);

            //Read the message coming from the pipeline
            buf = ByteBuffer.allocate(msgsize);
            try 
                while (buf.remaining() > 0) 
                    bytesread = schannel.read(buf);

                    if (bytesread == -1)  //Socket was terminated

                    

                    if (quitthread) break;
                
             catch (IOException ex) 

            

            parent.recvMessage(buf.array());
        

        if (quitthread) 
            break;
        
    


我从 SocketChannel 收到的第一个字节很好,我成功解码了消息。但是,下次我从 SocketChannel 读取时,套接字向前跳过了大约 100 个字节,这导致读取错误的字节并将其解释为长度,从而导致所有内容都损坏。

代码有什么问题?没有其他线程正在从 SocketChannel 读取数据。

【问题讨论】:

您可以简单地调用 getInt() 来读取长度,而不是将您的标头缓冲区转换为 4 个字节的数组。如果您想将其视为无符号整数,您始终可以使用 0xFFFFFFFF 转换为长按位与。还有, 我可能可以,但我不确定 java 是否会将 byte[] 的 arr[4] 读取为 little endian。无论如何,我怀疑这是问题的原因。第一条消息长度是正确的,我读取了那个字节数。检查读取的消息也表明没有错误。只有当我尝试读取标题的下 4 个字节时才会出现问题。 【参考方案1】:

你的括号去掉了,代码是:

(0xFF & ((int)header[1] << 8))

始终为 0(与

((0xFF & ((int)header[1])) << 8)

这会导致读取的消息字节不够,还会导致同步不匹配(而不是读取太多。)

编辑:现在你修复了上面的问题,我看不出有什么问题。你能告诉我们第一条消息的长度和被吃掉的确切字节数之间的关系吗?

根据显示的代码,我唯一的猜测是您从显示的示例中编辑了一些可能影响 schannel 的行为,schannel 是否在其他地方引用?

如果行:

ByteBuffer buf = ByteBuffer.allocate(4);

将在while 之外,这将导致您描述的行为,但在您的示例代码中不是。

【讨论】:

看起来这是解码标头的问题。现在可以正常使用了。【参考方案2】:

我想当你说你在非阻塞模式下轮询套接字时,你的意思是你正在使用“标准”Selector.select() 方法?

当 select 返回并表明有数据可从套接字读取时,您应该只读取在重新进入 select() 调用之前可用的字节。如果 read() 返回 -1,则表明缓冲区中没有更多字节可用于立即读取 - 这并不意味着套接字已关闭。因此,我怀疑您在返回之前尝试完全填充缓冲区是不正确的。即使它确实有效,您的 I/O 线程也会在数据到达时不断旋转。特别是,您似乎只是忽略了返回值 -1。

考虑重新构建您的代码以使用有限状态机方法。例如,我过去使用三态模型实现了这一点:IDLE、READ_MESSAGE_LENGTH 和 READ_MESSAGE。

【讨论】:

由于我每个线程只有一个套接字,我不知道是否需要使用 Selector.Select()。在任何情况下,-1 表示到达流的末尾 = 应该通知父级。我只是暂时忽略了处理。我将尝试使用普通的 Socket() 看看效果如何。

以上是关于Java SocketChannel 吃掉我的字节的主要内容,如果未能解决你的问题,请参考以下文章

SocketChannel - write 不写所有的 DATA

Java IO:SocketChannel和Selector在ZooKeeper中应用

Java NIO系列教程 SocketChannel

Java NIO系列教程 SocketChannel

Java NIO系列教程 SocketChannel

九Java NIO SocketChannel