fix quicClient

This commit is contained in:
anlicheng 2026-04-28 20:44:22 +08:00
parent 8e761b0540
commit 6ac8ebf398
3 changed files with 107 additions and 228 deletions

View File

@ -80,7 +80,6 @@ actor SDLContextActor {
private var dnsLocalWorker: Task<Void, Never>? private var dnsLocalWorker: Task<Void, Never>?
private var quicClient: SDLQUICClient? private var quicClient: SDLQUICClient?
private var quicWorker: Task<Void, Never>?
nonisolated private let puncherActor: SDLPuncherActor nonisolated private let puncherActor: SDLPuncherActor
// //
@ -259,91 +258,60 @@ actor SDLContextActor {
private func startQUICClient() async throws { private func startQUICClient() async throws {
SDLLogger.log("[SDLContext] try start quicClient", for: .debug) SDLLogger.log("[SDLContext] try start quicClient", for: .debug)
self.quicWorker?.cancel()
await self.quicClient?.stop()
// monitor // monitor
let quicClient = SDLQUICClient(host: self.config.serverHost, port: 443) let quicClient = SDLQUICClient(host: self.config.serverHost, port: 443)
self.quicClient = quicClient self.quicClient = quicClient
quicClient.start()
await quicClient.start() defer {
quicClient.stop()
}
do { try await quicClient.waitReady(timeout: .seconds(3))
try await quicClient.waitReady(timeout: .seconds(3)) // quic
// quic try await Task.sleep(for: .seconds(0.3))
try await Task.sleep(for: .seconds(0.3)) SDLLogger.log("[SDLContext] start quic client: \(self.config.serverHost)")
SDLLogger.log("[SDLContext] start quic client: \(self.config.serverHost)")
try await withTaskCancellationHandler { try await withThrowingTaskGroup { group in
try await withThrowingTaskGroup { group in defer {
defer { group.cancelAll()
group.cancelAll() }
group.addTask {
for try await message in quicClient.messageStream() {
await self.handleQUICMessage(message: message)
}
}
group.addTask {
let timerStream = SDLAsyncTimerStream()
timerStream.start(interval: .seconds(5))
for await _ in timerStream.stream {
if Task.isCancelled {
break
} }
quicClient.send(type: .ping, data: Data())
}
SDLLogger.log("[SDLQUICClient] udp pingTask cancel", for: .debug)
}
group.addTask { group.addTask {
for await message in await quicClient.messageStream { for await event in quicClient.eventStream {
if Task.isCancelled { switch event {
return case .failed(let error):
} throw error
await self.handleQUICMessage(message: message) case .cancelled:
} throw SDLQUICEvent.cancelled
if Task.isCancelled { case .writeFailed(let error):
return
}
throw SDLQUICClientExit.transportClosed("messageStream finished")
}
group.addTask {
let exit = await quicClient.run()
switch exit {
case .normal:
return
case .cancelled:
if Task.isCancelled {
return
}
throw exit
case .transportClosed, .readFailed, .writeFailed:
throw exit
}
}
group.addTask {
for await event in await quicClient.eventStream {
switch event {
case .failed(let error):
throw error
case .cancelled:
throw SDLQUICClientExit.cancelled
case .writeFailed(let error):
throw error
}
}
if Task.isCancelled {
return
}
throw SDLQUICClientExit.transportClosed("eventStream finished")
}
do {
let _ = try await group.next()
await quicClient.stop()
} catch {
await quicClient.stop()
throw error throw error
} }
} }
} onCancel: {
Task {
await quicClient.stop()
}
} }
} catch {
await quicClient.stop() try await group.next()
throw error
} }
} }
private func handleQUICMessage(message: SDLQUICInboundMessage) async { private func handleQUICMessage(message: SDLQUICInboundMessage) async {
@ -533,8 +501,6 @@ actor SDLContextActor {
self.udpHoleV6 = nil self.udpHoleV6 = nil
self.udpHoleV6LocalAddress = nil self.udpHoleV6LocalAddress = nil
self.quicWorker?.cancel()
self.quicWorker = nil
await self.quicClient?.stop() await self.quicClient?.stop()
self.quicClient = nil self.quicClient = nil
@ -996,7 +962,7 @@ extension SDLContextActor {
if let registerSuperData = try? registerSuper.serializedData() { if let registerSuperData = try? registerSuper.serializedData() {
SDLLogger.log("[SDLContext] will send register super") SDLLogger.log("[SDLContext] will send register super")
await self.quicClient?.send(type: .registerSuper, data: registerSuperData) self.quicClient?.send(type: .registerSuper, data: registerSuperData)
} }
} }

View File

@ -26,57 +26,26 @@ enum SDLQUICEvent: Error {
case writeFailed(Error) case writeFailed(Error)
} }
enum SDLQUICClientExit: Error, Sendable, CustomStringConvertible { final class SDLQUICClient {
case normal
case cancelled
case transportClosed(String)
case readFailed(String)
case writeFailed(String)
var description: String {
switch self {
case .normal:
return "normal"
case .cancelled:
return "cancelled"
case .transportClosed(let reason):
return "transportClosed(\(reason))"
case .readFailed(let reason):
return "readFailed(\(reason))"
case .writeFailed(let reason):
return "writeFailed(\(reason))"
}
}
}
actor SDLQUICClient {
private let allocator = ByteBufferAllocator() private let allocator = ByteBufferAllocator()
// 64K
private let maxPacketSize: Int
// 2M // 2M
private let maxBufferSize: Int private let maxBufferSize: Int
private let readyLatch = AsyncOneShot<Void>() private static let maxPacketSize: Int = 64 * 1024
// private let readyLatch = AsyncOneShot<Void>()
public var messageStream: AsyncStream<SDLQUICInboundMessage>
private let messageCont: AsyncStream<SDLQUICInboundMessage>.Continuation
// //
public var eventStream: AsyncStream<SDLQUICEvent> public var eventStream: AsyncStream<SDLQUICEvent>
private let eventCont: AsyncStream<SDLQUICEvent>.Continuation private let eventCont: AsyncStream<SDLQUICEvent>.Continuation
private var didFinishStreams = false
private let connection: NWConnection private let connection: NWConnection
private let queue = DispatchQueue(label: "com.sdl.QUICClient.queue") // 线 private let queue = DispatchQueue(label: "com.sdl.QUICClient.queue") // 线
init(host: String, port: UInt16, maxPacketSize: Int = 64 * 1024, maxBufferSize: Int = 2 * 1024 * 1024) { init(host: String, port: UInt16, maxBufferSize: Int = 2 * 1024 * 1024) {
let options = NWProtocolQUIC.Options(alpn: ["punchnet/1.0"]) let options = NWProtocolQUIC.Options(alpn: ["punchnet/1.0"])
self.maxBufferSize = maxBufferSize self.maxBufferSize = maxBufferSize
self.maxPacketSize = maxPacketSize
(self.messageStream, self.messageCont) = AsyncStream.makeStream(of: SDLQUICInboundMessage.self)
(self.eventStream, self.eventCont) = AsyncStream.makeStream(of: SDLQUICEvent.self) (self.eventStream, self.eventCont) = AsyncStream.makeStream(of: SDLQUICEvent.self)
// //
@ -109,10 +78,10 @@ actor SDLQUICClient {
await self.readyLatch.succeed(()) await self.readyLatch.succeed(())
case .failed(let error): case .failed(let error):
await self.readyLatch.fail(error) await self.readyLatch.fail(error)
self.emitEvent(.failed(error)) self.eventCont.yield(.failed(error))
case .cancelled: case .cancelled:
await self.readyLatch.fail(SDLQUICError.connectionCancelled) await self.readyLatch.fail(SDLQUICError.connectionCancelled)
self.emitEvent(.cancelled) self.eventCont.yield(.cancelled)
default: default:
() ()
} }
@ -122,24 +91,6 @@ actor SDLQUICClient {
try await self.readyLatch.wait(timeout: timeout, timeoutError: SDLQUICError.timeout) try await self.readyLatch.wait(timeout: timeout, timeoutError: SDLQUICError.timeout)
} }
func run() async -> SDLQUICClientExit {
await withTaskGroup(of: SDLQUICClientExit.self) { group in
group.addTask {
await self.readLoop()
}
group.addTask {
await self.heartbeatLoop()
}
let exit = await group.next() ?? .normal
group.cancelAll()
self.finishStreams()
return exit
}
}
func send(type: SDLPacketType, data: Data) { func send(type: SDLPacketType, data: Data) {
var len = UInt16(data.count + 1).bigEndian var len = UInt16(data.count + 1).bigEndian
@ -150,49 +101,16 @@ actor SDLQUICClient {
connection.send(content: packet, completion: .contentProcessed { [weak self] error in connection.send(content: packet, completion: .contentProcessed { [weak self] error in
if let error { if let error {
SDLLogger.log("[SDLQUICClient] send data get error: \(error)", for: .debug) SDLLogger.log("[SDLQUICClient] send data get error: \(error)", for: .debug)
Task { self?.eventCont.yield(.writeFailed(error))
await self?.emitEvent(.writeFailed(error))
}
} }
}) })
} }
private func heartbeatLoop() async -> SDLQUICClientExit { func stop() {
let timerStream = SDLAsyncTimerStream()
timerStream.start(interval: .seconds(5))
for await _ in timerStream.stream {
if Task.isCancelled {
break
}
self.send(type: .ping, data: Data())
}
SDLLogger.log("[SDLQUICClient] udp pingTask cancel", for: .debug)
return .cancelled
}
func stop() async {
self.connection.cancel() self.connection.cancel()
await self.readyLatch.fail(SDLQUICError.connectionCancelled) Task {
self.finishStreams() await self.readyLatch.fail(SDLQUICError.connectionCancelled)
}
private func emitEvent(_ event: SDLQUICEvent) {
guard !self.didFinishStreams else {
return
} }
self.eventCont.yield(event)
}
private func finishStreams() {
guard !self.didFinishStreams else {
return
}
self.didFinishStreams = true
self.messageCont.finish()
self.eventCont.finish() self.eventCont.finish()
} }
@ -200,45 +118,51 @@ actor SDLQUICClient {
// --MARK: Reader // --MARK: Reader
extension SDLQUICClient { extension SDLQUICClient {
private func readLoop() async -> SDLQUICClientExit {
var buffer = allocator.buffer(capacity: self.maxBufferSize)
let threshold = self.maxBufferSize / 10 * 6
defer { func messageStream() -> AsyncThrowingStream<SDLQUICInboundMessage, Error> {
self.messageCont.finish() return AsyncThrowingStream { continuation in
} var buffer = allocator.buffer(capacity: self.maxBufferSize)
let threshold = self.maxBufferSize / 10 * 6
do { func readOnce() {
while !Task.isCancelled { self.connection.receive(minimumIncompleteLength: 1, maximumLength: Self.maxPacketSize) { data, _, isComplete, error in
let (isComplete, data) = try await self.readOnce() if let error {
if let data, !data.isEmpty { continuation.finish(throwing: error)
buffer.writeBytes(data) return
let frames = try parseFrames(buffer: &buffer)
if buffer.readerIndex > threshold {
buffer.discardReadBytes()
} }
for frame in frames { do {
if let message = decode(frame: frame) { if let data, !data.isEmpty {
self.messageCont.yield(message) buffer.writeBytes(data)
let frames = try Self.parseFrames(buffer: &buffer)
if buffer.readerIndex > threshold {
buffer.discardReadBytes()
}
for frame in frames {
if let message = Self.decode(frame: frame) {
continuation.yield(message)
}
}
} }
} catch let err {
continuation.finish(throwing: err)
return
} }
}
if isComplete { if isComplete {
return .transportClosed("receive complete") continuation.finish()
} else {
readOnce()
}
} }
} }
return .cancelled readOnce()
} catch is CancellationError {
return .cancelled
} catch {
return .readFailed("\(error)")
} }
} }
// //
private func parseFrames(buffer: inout ByteBuffer) throws -> [ByteBuffer] { private static func parseFrames(buffer: inout ByteBuffer) throws -> [ByteBuffer] {
guard buffer.readableBytes >= 2 else { guard buffer.readableBytes >= 2 else {
return [] return []
} }
@ -249,7 +173,7 @@ extension SDLQUICClient {
break break
} }
if len > self.maxPacketSize { if len > Self.maxPacketSize {
throw SDLQUICError.packetTooLarge throw SDLQUICError.packetTooLarge
} }
@ -266,23 +190,12 @@ extension SDLQUICClient {
return frames return frames
} }
//
private func readOnce() async throws -> (Bool, Data?) {
return try await withCheckedThrowingContinuation { cont in
self.connection.receive(minimumIncompleteLength: 1, maximumLength: maxPacketSize) { data, _, isComplete, error in
if let error {
cont.resume(throwing: error)
return
}
cont.resume(returning: (isComplete, data))
}
}
}
} }
// --MARK: // --MARK:
extension SDLQUICClient { extension SDLQUICClient {
private func decode(frame: ByteBuffer) -> SDLQUICInboundMessage? {
private static func decode(frame: ByteBuffer) -> SDLQUICInboundMessage? {
var buffer = frame var buffer = frame
guard let type = buffer.readInteger(as: UInt8.self), guard let type = buffer.readInteger(as: UInt8.self),
let packetType = SDLPacketType(rawValue: type) else { let packetType = SDLPacketType(rawValue: type) else {

View File

@ -71,7 +71,7 @@ actor AsyncOneShot<Value: Sendable> {
do { do {
try await Task.sleep(for: timeout) try await Task.sleep(for: timeout)
if !Task.isCancelled { if !Task.isCancelled {
await self.cancelWaiter(id: id, throwing: timeoutError) self.cancelWaiter(id: id, throwing: timeoutError)
} }
} catch { } catch {
return return