为 Swift 安排套接字通信

Scheduling socket communication for Swift

我对 Swift 编程还很陌生,请原谅我在下面要问的这些空问题。

在我的应用程序中,我试图安排调用一个函数,该函数将从我的服务器接收一些数据并每秒调用一次。通信需要通过 TCP 套接字实现。在做了一些研究之后,在我看来,我需要有一种方法来正确使用线程来调用该函数。所以我的问题来了:

  1. 我应该在哪里连接到服务器? (我应该在我的第一个视图控制器的 viewDidLoad() 函数中建立连接吗?)
  2. 我应该在哪里创建一个线程来安排函数调用?如果我在我的第一个视图控制器中创建线程,线程会在我切换到另一个视图控制器后死掉吗?
  3. 我应该为该线程使用什么 QoS 级别?该应用程序每秒渲染从服务器接收到的数据,因此我认为此任务的优先级很高。

我尝试查找有关线程和套接字通信的教程和示例,但找不到适用于我的应用程序的信息。因此,我们将不胜感激有关设计的任何帮助或见解!

提前致谢!

根据我的说法,对于问题中出现的场景可以做以下操作

  1. 如果您打算在启动应用程序后立即连接到您的服务器。我认为最好在 didFinishLaunchingWithOptions 中的 AppDelegate 中执行此操作

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    
    }
    
  2. 这取决于你打算如何切换到其他ViewController。在切换的过程中,如果你要释放你当前的ViewController,那么你的线程就会死掉。

  3. 考虑到您正在制作一个基于套接字的应用程序并且您将每秒从服务器接收数据。那么,NSURLSession可能对你帮助不大。对于套接字通信,通常使用 NSInputStreamNSOutputStream。来自 objective-C 的以下示例可能会帮助您入门:

     - (void)connectWithHostFromUrl: (NSURL *)hostUrl {
    
            CFReadStreamRef readStream;
            CFWriteStreamRef writeStream;
            CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)[hostUrl host], 80, &readStream, &writeStream);
    
           _inputStream = (__bridge_transfer NSInputStream *)readStream;
           [_inputStream setDelegate:self];
           [_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
           [_inputStream open];
    
           _outputStream = (__bridge_transfer NSOutputStream *)writeStream;
           [_outputStream setDelegate:self];
           [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
           [_outputStream open];
      }
    
     // Delegate methods
    
    - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
    
            switch (eventCode) {
                 case NSStreamEventNone:
                 {
                  // handle it according to your need
                 }
                      break;
                 case NSStreamEventOpenCompleted:
                 {
                  // handle it according to your need
                 }
                      break;
                 case NSStreamEventHasBytesAvailable:
                 {
                     if (_receivedData == nil) {
                          _receivedData = [NSMutableData new];
                     }
                     uint8_t buffer[1024];
                     NSInputStream *inputStream = (NSInputStream *)aStream;
                     NSInteger bytesReceived = [inputStream read:buffer maxLength:1024];
                    if (bytesReceived > 0) {
                        [_receivedData appendBytes:(const void *)buffer length:bytesReceived];
                    }
                 }
                      break;
                 case NSStreamEventHasSpaceAvailable:
                 {
                      if (_dataToSend != nil) {
                        // _dataToSend is NSMutableData/NSData object
                          NSOutputStream *outputStream = (NSOutputStream *)aStream;
                          const uint8_t *mutableBytes = (const uint8_t *)[_dataToSend mutableBytes];
                          NSInteger length = [_dataToSend length]/sizeof(uint8_t);
                          [outputStream write:(const uint8_t *)mutableBytes maxLength:length];
                      }
                  }
                      break;
                  case NSStreamEventErrorOccurred:
                  {
                    // handle it according to your need
                  }
                      break;
                  case NSStreamEventEndEncountered:
                  {
                    // handle it according to your need
                  }
                      break;
                  default:
                      break;
           }
    }
    

因为这里要处理的案件很多。大多数时候,我建议使用经过测试的第三方库,例如 SocketRocket.

请提出修改建议以使这个答案更好:)

经过一番挖掘,我找到了在 Swift 端实现流编程的方法(使用本机 Swift 功能)。原来Swift里面的方式和Objective C.

里面的方式很像

在Swift中启用流编程所需的两个函数是func connect(),这是我必须自己编写的,以及func stream(_ aStream: Stream, handle eventCode: Stream.Event),它是在[=]中作为方法提供的14=].

这是 func connect() 的样子:

func connect() {
    Stream.getStreamsToHost(withName: <serverIP>, port: <serverPort>, inputStream: &inputStream, outputStream: &)

    guard let inputStream = inputStream, let outputStream = outputStream else {
        print(" ->\tNetworkControllerError: Cannot open inputstream/outputstream.")
        return
    }

    // Set delegate
    inputStream.delegate = self
    outputStream.delegate = self

    let socketWorkQueue = DispatchQueue(label: "socketWorkQueue", attributes: .concurrent)
    CFReadStreamSetDispatchQueue(inputStream, socketWorkQueue)
    CFWriteStreamSetDispatchQueue(outputStream, socketWorkQueue)

    inputStream.open()
    outputStream.open()
}

下面是 func stream(_ aStream: Stream, handle eventCode: Stream.Event) 的样子:

func stream(_ aStream: Stream, handle eventCode: Stream.Event) {

    if aStream === inputStream {
        switch eventCode {
        case Stream.Event.errorOccurred:
            streamEventQueue.async {
                // do something here
            }
            break
        case Stream.Event.openCompleted:
            streamEventQueue.async {
                // do something here
            }
            break
        case Stream.Event.hasBytesAvailable:
            streamEventQueue.async {
                // do something here
                let output = read()
            }
            break
        case Stream.Event.endEncountered:
            streamEventQueue.async {
                // do something here
            }
            break
        default:
            break
        }
    }
    else if aStream === outputStream {
        switch eventCode {
        case Stream.Event.errorOccurred:
            streamEventQueue.async {
                // do something here
            }
            break
        case Stream.Event.openCompleted:
            streamEventQueue.async {
                // do something here
            }
            break
        default:
            break
        }
    }
}

所以 stream() 函数应该是我的应用程序中处理由网络 connection/disconnection 引起的状态转换的唯一地方。请注意,我还使用了串行事件队列来处理流事件。这是为了防止任何可能导致状态腐败的潜在竞争条件。

对于那些不太熟悉流编程的人来说,这是一种保持 connection/socket 活动的服务器-客户端通信模式。由于服务器和客户端之间存在持续通信,连接需要保持活动状态。 RESTful API 等通信模式会因客户端请求而显着加重服务器负担,因此不推荐使用。