为了账号安全,请及时绑定邮箱和手机立即绑定

通过 SocketChannel 读取(写入)的正确方法

通过 SocketChannel 读取(写入)的正确方法

红糖糍粑 2024-01-17 17:13:54
我的问题比以下场景更通用,尽管这涵盖了所需的一切。这是针对Java和socket编程的正确做法。设想:一台服务器有多个客户端。非阻塞 I/O的使用该服务器是另一台服务器的客户端。阻塞 I/O的使用每种情况有两种情况:在一种情况下,所有数据都适合分配的字节缓冲区,在第二种情况下,它们不适合(仅适用于一次迭代,不适用于程序的生命周期)。我发现的所有非阻塞 I/O 的示例都是这样的:InetAddress host = InetAddress.getByName("localhost");Selector selector = Selector.open();ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);serverSocketChannel.bind(new InetSocketAddress(host, 1234));serverSocketChannel.register(selector, SelectionKey. OP_ACCEPT);while (true) {   if (selector.select() <= 0)       continue;   Set<SelectionKey> selectedKeys = selector.selectedKeys();   Iterator<SelectionKey> iterator = selectedKeys.iterator();   while (iterator.hasNext()) {       key = (SelectionKey) iterator.next();       iterator.remove();       if (key.isAcceptable()) {           SocketChannel socketChannel = serverSocketChannel.accept();           socketChannel.configureBlocking(false);           socketChannel.register(selector, SelectionKey.OP_READ);           // Do something or do nothing       }       if (key.isReadable()) {           SocketChannel socketChannel = (SocketChannel) key.channel();           ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);           socketChannel.read(buffer);           // Something something dark side           if (result.length() <= 0) {               sc.close();               // Something else           }        }    }read如果缓冲区足够大,这里是否读取来自该特定客户端和该特定请求的所有传入数据,或者我是否需要将其放在循环内while?如果缓冲区不够大?如果是 a write,我也可以这样做吗socketChannel.write(buffer)(至少从程序的角度来看)?这里的文档没有指定所有传入数据都适合缓冲区的情况。当我有阻塞 I/O 时,这也会让人有点困惑:然而,可以保证的是,如果通道处于阻塞模式并且缓冲区中至少剩余一个字节,则此方法将阻塞,直到至少读取一个字节。这是否意味着这里(阻塞 I/O)我需要以任何一种方式read通过while循环(我发现的大多数示例都是这样做的)?做手术怎么办write?所以,总而言之,我的问题是,从中间服务器(客户端到第二个服务器)的角度来看,在我的场景中读写数据的正确方法是什么?
查看完整描述

1 回答

?
白板的微信

TA贡献1883条经验 获得超3个赞

如果您没有调用configureBlocking(false),那么是的,您将使用循环来填充缓冲区。

然而……非阻塞套接字的要点是不要挂起等待任何一个套接字,因为这会延迟从所有剩余套接字(其选定的键尚未被迭代器处理)的读取。实际上,如果十个客户端连接,并且其中一个碰巧连接速度较慢,则其他部分或全部客户端可能会遇到同样的缓慢情况。

(未指定所选键集的确切顺序。查看 Selector 实现类的源代码是不明智的,因为缺乏任何顺序保证意味着 Java SE 的未来版本可以更改顺序。)

为了避免等待任何一个套接字,您不要尝试一次性填满缓冲区;相反,您可以在不阻塞的情况下读取套接字可以提供的任何内容,每次调用只读取一次select()

由于每个 ByteBuffer 可能保存部分数据序列,因此您需要记住每个 Socket 的每个 ByteBuffer 的进度。幸运的是,SelectionKey 有一个方便的方法来做到这一点:附件

您还想记住从每个套接字读取了多少字节。因此,现在您需要记住每个套接字的两件事:字节数和字节缓冲区。

class ReadState {

    final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);

    long count;

}


while (true) {


    // ...


        if (key.isAcceptable()) {

            SocketChannel socketChannel = serverSocketChannel.accept();

            socketChannel.configureBlocking(false);


            // Attach the read state for this socket

            // to its corresponding key.

            socketChannel.register(selector, SelectionKey.OP_READ,

                new ReadState());

        }


        if (key.isReadable()) {

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

            ReadState state = (ReadState) key.attachment();

            ByteBuffer buffer = state.buffer;

            state.count += socketChannel.read(buffer);


            if (state.count >= DATA_LENGTH) {

                socketChannel.close();

            }


            buffer.flip();


            // Caution: The speed of this connection will limit your ability

            // to process the remaining selected keys!

            anotherServerChannel.write(buffer);

        }

对于阻塞通道,您可以只使用一次write(buffer)调用,但是正如您所看到的,使用阻塞通道可能会限制主服务器使用非阻塞通道的优势。将与其他服务器的连接也设置为非阻塞通道可能是值得的。这会让事情变得更加复杂,所以除非你希望我这样做,否则我不会在这里解决这个问题。


查看完整回答
反对 回复 2024-01-17
  • 1 回答
  • 0 关注
  • 182 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信