简体   繁体   English

Swift-NIO + WebSocket-Kit:在 Mac 应用程序中正确设置/清理

[英]Swift-NIO + WebSocket-Kit: Proper Setup/Cleanup in a Mac App

Context语境

I'm developing a Mac app.我正在开发一个 Mac 应用程序。 In this app, I want to run a websocket server.在这个应用程序中,我想运行一个 websocket 服务器。 To do this, I'm using Swift NIO and Websocket-Kit.为此,我使用了 Swift NIO 和 Websocket-Kit。 My full setup is below.我的完整设置如下。

Question

All of the documentation for Websocket-Kit and SwiftNIO is geared towards a creating a single server-side process that starts up when you launch it from the command line and then runs infinitely. Websocket-Kit 和 SwiftNIO 的所有文档都旨在创建一个服务器端进程,该进程在您从命令行启动时启动,然后无限运行。

In my app, I must be able to start the websocket server and then shut it down and restart it on demand, without re-launching my application.在我的应用程序中,我必须能够启动 websocket 服务器,然后将其关闭并按需重新启动,而无需重新启动我的应用程序。 The code below does that, but I would like confirmation of two things:下面的代码可以做到这一点,但我想确认两件事:

  1. In the test() function, I send some text to all connected clients.test()函数中,我向所有连接的客户端发送一些文本。 I am unsure if this is thread-safe and correct.我不确定这是否是线程安全和正确的。 Can I store the WebSocket instances as I'm doing here and message them from the main thread of my application?我可以像在这里一样存储WebSocket实例并从应用程序的主线程向它们发送消息吗?

  2. Am I shutting down the websocket server correctly?我是否正确关闭了 websocket 服务器? The result of the call to serverBootstrap(group:)[...].bind(host:port:).wait() creates a Channel and then waits infinitely.调用serverBootstrap(group:)[...].bind(host:port:).wait()创建一个Channel ,然后无限等待。 When I call shutdownGracefully() on the associated EventLoopGroup , is that server cleaned up correctly?当我在关联的EventLoopGroup上调用shutdownGracefully()时,该服务器是否已正确清理? (I can confirm that port 5759 is free again after this shutdown, so I'm guessing everything is cleaned up?) (我可以确认在这次关闭后端口 5759 再次空闲,所以我一切都被清理了?)

Thanks for the input;感谢您的投入; it's tough to find examples of using SwiftNIO and Websocket-Kit inside an application.很难找到在应用程序中使用 SwiftNIO 和 Websocket-Kit 的示例。

Code代码

import Foundation
import NIO
import NIOHTTP1
import NIOWebSocket
import WebSocketKit


@objc class WebsocketServer: NSObject
{
    private var queue: DispatchQueue?
    private var eventLoopGroup: MultiThreadedEventLoopGroup?
    private var websocketClients: [WebSocket] = []
    
    
    @objc func startServer()
    {
        queue = DispatchQueue.init(label: "socketServer")
        queue?.async
        {
            let upgradePipelineHandler: (Channel, HTTPRequestHead) -> EventLoopFuture<Void> = { channel, req in
                
                WebSocket.server(on: channel) { ws in
                    ws.send("You have connected to WebSocket")
                    
                    DispatchQueue.main.async {
                        self.websocketClients.append(ws)
                        print("websocketClients after connection: \(self.websocketClients)")
                    }
                
                    ws.onText { ws, string in
                        print("received")
                        ws.send(string.trimmingCharacters(in: .whitespacesAndNewlines).reversed())
                    }
                
                    ws.onBinary { ws, buffer in
                        print(buffer)
                    }
                
                    ws.onClose.whenSuccess { value in
                        print("onClose")
                        
                        DispatchQueue.main.async
                        {
                            self.websocketClients.removeAll { (socketToTest) -> Bool in
                                return socketToTest === ws
                            }
                            
                            print("websocketClients after close: \(self.websocketClients)")
                        }
                    }
                }
            }

            self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2)
            let port: Int = 5759

            let promise = self.eventLoopGroup!.next().makePromise(of: String.self)
            
            let server = try? ServerBootstrap(group: self.eventLoopGroup!)
                
                // Specify backlog and enable SO_REUSEADDR for the server itself
                .serverChannelOption(ChannelOptions.backlog, value: 256)
                .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
                
                .childChannelInitializer { channel in
              
                let webSocket = NIOWebSocketServerUpgrader(
                    shouldUpgrade: { channel, req in
                        return channel.eventLoop.makeSucceededFuture([:])
                    },
                    upgradePipelineHandler: upgradePipelineHandler
                )
              
                return channel.pipeline.configureHTTPServerPipeline(
                    withServerUpgrade: (
                        upgraders: [webSocket],
                        completionHandler: { ctx in
                            // complete
                        })
                )
            }.bind(host: "0.0.0.0", port: port).wait()                  

            _ = try! promise.futureResult.wait()
        }
    }
    
    
    
    ///
    ///  Send a message to connected clients, then shut down the server.
    ///
    @objc func test()
    {
        self.websocketClients.forEach { (ws) in
            ws.eventLoop.execute {
                ws.send("This is a message being sent to all websockets.")
            }
        }
        
        stopServer()
    }
    
    
    
    @objc func stopServer()
    {
        self.websocketClients.forEach { (ws) in
            try? ws.eventLoop.submit { () -> Void in
                print("closing websocket: \(ws)")
                _ = ws.close()
            }.wait()                        // Block until complete so we don't shut down the eventLoop before all clients get closed.
        }
        
        eventLoopGroup?.shutdownGracefully(queue: .main, { (error: Error?) in

            print("Eventloop shutdown now complete.")
            self.eventLoopGroup = nil
            self.queue = nil
        })
    }
}

In the test() function, I send some text to all connected clients.在 test() 函数中,我向所有连接的客户端发送一些文本。 I am unsure if this is thread-safe and correct.我不确定这是否是线程安全和正确的。 Can I store the WebSocket instances as I'm doing here and message them from the main thread of my application?我可以像在这里一样存储 WebSocket 实例并从应用程序的主线程向它们发送消息吗?

Exactly as you're doing here, yes, that should be safe.就像你在这里做的那样,是的,那应该是安全的。 ws.eventLoop.execute will execute that block on the event loop thread belonging to that WebSocket connection. ws.eventLoop.execute将在属于该 WebSocket 连接的事件循环线程上执行该块。 This will be safe.这将是安全的。

When I call shutdownGracefully() on the associated EventLoopGroup, is that server cleaned up correctly?当我在关联的 EventLoopGroup 上调用 shutdownGracefully() 时,该服务器是否已正确清理? (I can confirm that port 5759 is free again after this shutdown, so I'm guessing everything is cleaned up?) (我可以确认在这次关闭后端口 5759 再次空闲,所以我猜一切都被清理了?)

Yes.是的。 shutdownGracefully forces all connections and listening sockets closed. shutdownGracefully强制shutdownGracefully所有连接和监听套接字。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM