Skip to content

Instantly share code, notes, and snippets.

@VaslD
Last active June 26, 2023 16:03
Show Gist options
  • Save VaslD/2a643081a40dd736da069fc6fb00d02c to your computer and use it in GitHub Desktop.
Save VaslD/2a643081a40dd736da069fc6fb00d02c to your computer and use it in GitHub Desktop.
import Foundation
import Network
/// TCP 监听(服务端)
public final class TCPServer: Sendable {
let queue: DispatchQueue
let listener: NWListener
public init(port: Int) throws {
guard let unsigned = UInt16(exactly: port), let port = NWEndpoint.Port(rawValue: unsigned) else {
throw NWError.posix(POSIXErrorCode.EADDRNOTAVAIL)
}
self.queue = DispatchQueue(label: "NWListener (:\(port))")
self.listener = try NWListener(using: .tcp, on: port)
}
public var state: NWListener.State {
self.listener.state
}
public var service: NWListener.Service? {
get { self.listener.service }
set { self.listener.service = newValue }
}
/// 开始监听
///
/// 此方法需要 `await`,将在监听配置成功或异常后返回。当监听正在配置时,继续(并发)调用此方法将立即导致 `CancellationError`
/// 错误。如果监听已开始,调用此方法无效。
public func start() async throws {
guard self.listener.stateUpdateHandler == nil else {
throw CancellationError()
}
guard self.listener.state != .ready else {
return
}
try await withUnsafeThrowingContinuation { continuation in
self.listener.stateUpdateHandler = {
switch $0 {
case .ready:
self.listener.stateUpdateHandler = nil
continuation.resume()
case let .failed(error):
self.listener.stateUpdateHandler = nil
continuation.resume(throwing: error)
case .cancelled:
self.listener.stateUpdateHandler = nil
continuation.resume(throwing: CancellationError())
case let .waiting(error):
self.listener.stateUpdateHandler = nil
self.listener.cancel()
continuation.resume(throwing: error)
default:
break
}
}
self.listener.start(queue: self.queue)
}
}
/// 停止监听
///
/// 此方法不会使得 ``clients`` 的异步流提前或正常终止。如果代码正在并发枚举客户端,请自行处理。
public func stop() {
self.listener.cancel()
}
/// 获取请求连接客户端的异步流
///
/// 由于 TCP 是全双工通讯,异步流中的客户端已连接到服务端 (SYN)、但服务端尚未回应 (ACK)。如果希望接受连接,请调用
/// ``TCPConnection/connect()`` 完成握手;如果希望拒绝连接,请调用 ``TCPConnection/disconnect()``
/// 取消握手;如果长时间不执行任何操作,客户端连接请求将会超时。
///
/// 每次枚举异步流只会获得新的连接请求对应的客户端。如需重复与某个客户端通讯,请另行留存 ``TCPClient``、不可期待多次枚举返回相同元素。
public var clients: AsyncStream<TCPClient>! {
guard self.listener.newConnectionHandler == nil else {
return nil
}
return AsyncStream { stream in
stream.onTermination = { _ in
self.listener.newConnectionHandler = nil
}
self.listener.newConnectionHandler = {
stream.yield(TCPClient($0))
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment