【问题标题】:Best practice for sending to connected clients发送到已连接客户端的最佳实践
【发布时间】:2022-01-21 15:46:35
【问题描述】:

我正在尝试设计一个 SwiftNIO 服务器,其中多个客户端(如 2 个或 3 个)可以连接到服务器,并且在连接后,它们都可以从服务器接收信息。

为此,我创建了一个ServerHandler 类,该类共享并添加到连接客户端的每个管道中。

let group = MultiThreadedEventLoopGroup(numberOfThreads: 2)
let handler = ServerHandler()
let bootstrap = ServerBootstrap(group: group)
    .serverChannelOption(ChannelOptions.backlog, value: 2)
    .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
    .childChannelInitializer { $0.pipeline.addHandler(handler) }
    .childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)

以上代码灵感来自https://github.com/apple/swift-nio/blob/main/Sources/NIOChatServer/main.swift

ServerHandler 类中,每当有新客户端连接时,该通道就会添加到数组中。然后,当我准备好向所有客户端发送数据时,我只需遍历ServerHandler 中的通道,然后调用writeAndFlush

这似乎工作得很好,但有几件事我担心:

  1. 似乎并不真正推荐创建共享处理程序,而是应该为每个客户端创建一个新处理程序。但是,我将如何访问需要向其发送数据的所有客户端通道? (我在由 UI 决定的时间发送数据)
  2. 为什么Channel.write 似乎什么也没做?如果我在服务器中使用Channel.write 而不是writeAndFlush,我的客户端将无法接收任何数据。

如果这些问题很愚蠢,我深表歉意,我最近才开始使用 SwiftNIO 和一般网络。

如果有人能给我一些见解,那就太棒了。

【问题讨论】:

    标签: swift networking swift-nio


    【解决方案1】:

    你的问题一点都不傻!

    1. 是的,分享ChannelHandler 可能算作“不推荐”。但不是因为它不起作用,更多的是它不寻常,可能不是其他 NIO 程序员所期望的。但是,如果您对此感到满意,那很好。如果你足够高性能以至于你担心每个Channel 的确切分配数量,那么你可以通过共享处理程序来节省一些。但是我真的不会过早地优化。

      如果您不想共享处理程序,那么您可以使用多个处理程序来共享对某种协调器 对象的引用。不要误会我的意思,它实际上仍然是同一件事:跨多个网络连接的一个共享引用。唯一真正的区别是测试可能更容易一些,并且对于其他 NIO 程序员来说可能感觉更自然。 (在任何情况下都要小心确保所有Channels 都在同一个EventLoop 上,或者使用外部同步(比如锁,从性能的角度来看这可能不是理想的)。

    2. write 只是 enqueues 一些要写入的数据。 flush 让 SwiftNIO 尝试发送所有之前写入的数据。 writeAndFlush 只需调用write 然后flush

      NIO 为什么要区分writeflush?在高性能网络应用程序中,最大的开销可能是系统调用开销。为了通过 TCP 发送数据,SwiftNIO 必须进行系统调用(writewritevsend,...)。

      如果您忽略 writeflush 并始终使用 writeAndFlush,任何 SwiftNIO 程序都可以工作。但是,如果网络跟上,那么每次writeAndFlush 调用都会花费你一个系统调用。然而,在许多情况下,使用 SwiftNIO 的库/应用程序已经知道它想要将多位数据排入队列以通过网络发送。在那种情况下,连续说三个writeAndFlush 会很浪费。如果累积三位数据然后使用“向量写入”(例如writev syscall)在一个系统调用中将它们全部发送会更好。如果你说writewritewriteflush,这正是 SwiftNIO 会做的事情。所以这三个写入都将使用一个writev 系统调用发送。 SwiftNIO 将简单地获取指向数据位的三个指针并将它们交给内核,然后内核尝试通过网络发送它们。

      你可以更进一步。假设您是一台高性能服务器,并且您想要响应大量传入请求。您将通过channelRead 收到客户的请求。如果您现在能够同步回复,您可以只 write 他们回复(这将使他们排队​​)。一旦你得到channelReadComplete(这标志着“读取突发”的结束)你就可以flush。这将允许您使用 one writev 系统调用在一次读取突发中响应尽可能多的请求。在某些情况下,这可能是一项非常重要的优化。

    【讨论】:

    • 非常感谢您的详细回复!这对我来说很清楚。节日快乐!
    • 谢谢您,祝您节日快乐!神奇的用户名顺便说一句;)
    • 仅供参考:在 Swift 论坛上的 SwiftNIO 类别中还包含很多 SwiftNIO 知识:forums.swift.org/c/related-projects/swiftnio/28。也欢迎你在那里提问。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-18
    • 1970-01-01
    • 1970-01-01
    • 2021-03-15
    相关资源
    最近更新 更多