Swift-NIO WebSocket-Kit: правильная настройка / очистка в приложении Mac

#swift #websocket #swift-nio

#swift #websocket #swift-nio

Вопрос:

Контекст

Я разрабатываю приложение для Mac. В этом приложении я хочу запустить сервер websocket. Для этого я использую Swift NIO и Websocket-Kit. Моя полная настройка приведена ниже.

Вопрос

Вся документация для Websocket-Kit и SwiftNIO направлена на создание единого серверного процесса, который запускается при запуске из командной строки, а затем выполняется бесконечно.

В моем приложении я должен иметь возможность запускать сервер websocket, а затем выключать его и перезапускать по требованию, без повторного запуска моего приложения. Приведенный ниже код делает это, но я хотел бы получить подтверждение двух вещей:

  1. В test() функции я отправляю некоторый текст всем подключенным клиентам. Я не уверен, что это потокобезопасно и правильно. Могу ли я сохранить WebSocket экземпляры, как я делаю здесь, и отправить их из основного потока моего приложения?

  2. Правильно ли я завершаю работу сервера websocket? Результат вызова serverBootstrap(group:)[...].bind(host:port:).wait() создает a Channel , а затем ожидает бесконечно. Когда я вызываю shutdownGracefully() связанный EventLoopGroup , правильно ли очищен этот сервер? (Я могу подтвердить, что порт 5759 снова свободен после этого выключения, поэтому я предполагаю, что все очищено?)

Спасибо за информацию; сложно найти примеры использования SwiftNIO и Websocket-Kit внутри приложения.

Код

 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
        })
    }
}
  

Ответ №1:

В функции test () я отправляю некоторый текст всем подключенным клиентам. Я не уверен, что это потокобезопасно и правильно. Могу ли я сохранить экземпляры WebSocket, как я делаю здесь, и отправить их из основного потока моего приложения?

Точно так же, как вы делаете здесь, да, это должно быть безопасно. ws.eventLoop.execute выполнит этот блок в потоке цикла событий, принадлежащем этому соединению с WebSocket. Это будет безопасно.

Когда я вызываю shutdownGracefully() в связанной EventLoopGroup, правильно ли очищен этот сервер? (Я могу подтвердить, что порт 5759 снова свободен после этого выключения, поэтому я предполагаю, что все очищено?)

Да. shutdownGracefully принудительно закрывает все соединения и прослушивающие сокеты.