You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Objective C macOS应用如何持续监听stdin以支持Chrome原生消息长连接

问题:Objective C编写的MacOS原生应用如何支持Chrome connectNative长连接监听

我正尝试通过native messaging从浏览器扩展调用用Objective C编写的MacOS应用。
目前我已经实现了如下代码,可以处理通过chrome.runtime.sendNativeMessage发起的请求:

int main(int argc, char * argv[]) {
  @autoreleasepool {
    NSFileHandle *stdIn = [NSFileHandle fileHandleWithStandardInput];
    NSError * stdinError = nil;
    NSData * rawReqLen = [stdIn readDataUpToLength:4 error:&stdinError];
    if(rawReqLen == nil || stdinError != nil) return 1;
    NSUInteger reqLen;
    [rawReqLen getBytes:&reqLen length:4];

    NSData * req = [stdIn readDataUpToLength:reqLen error:&stdinError];
    if(stdinError != nil) return 1;

    handleRequest(req); // 该方法会根据接收到的请求执行对应操作

    return 0;
  }
}

这段代码可以正常运行。
我现在需要实现chrome.connectNative能力以维持连接常开,具体问题是如何让MacOS应用持续监听传入消息,直到浏览器扩展调用port.disconnect关闭端口为止。
我尝试了如下代码,但无法正常运行:

int main(int argc, char * argv[]) {
  @autoreleasepool {
    NSFileHandle *stdIn = [NSFileHandle fileHandleWithStandardInput];
  
    [[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification
                                                          object:stdIn
                                                         queue:[NSOperationQueue mainQueue]
                                                      usingBlock:^(NSNotification *note) {
      NSError * stdinError = nil;
      NSData * rawReqLen = [stdIn readDataUpToLength:4 error:&stdinError];
      if(rawReqLen == nil || stdinError != nil) exit(1);
      NSUInteger reqLen;
      [rawReqLen getBytes:&reqLen length:4];

      NSData * req = [stdIn readDataUpToLength:reqLen error:&stdinError];
      if(stdinError != nil) exit(1);

      handleRequest(req);
    }];
    [stdIn waitForDataInBackgroundAndNotify];
  }
}

请问有人知道该如何实现这一需求吗?


解答

你的实现无法正常运行主要有两个核心原因:

  1. 注册监听并调用waitForDataInBackgroundAndNotify后,主线程没有保持运行,autoreleasepool执行结束后进程直接退出,根本等不到消息通知触发
  2. NSFileHandleDataAvailableNotification默认只会触发一次,每次处理完一条消息后需要重新调用waitForDataInBackgroundAndNotify才能监听下一条消息

你可以参考下面的实现:

int main(int argc, char * argv[]) {
  @autoreleasepool {
    NSFileHandle *stdIn = [NSFileHandle fileHandleWithStandardInput];
    
    __weak NSFileHandle *weakStdIn = stdIn;
    [[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification
                                                          object:stdIn
                                                         queue:[NSOperationQueue mainQueue]
                                                      usingBlock:^(NSNotification *note) {
      NSError * stdinError = nil;
      // 先读4字节的消息长度
      NSData * rawReqLen = [weakStdIn readDataUpToLength:4 error:&stdinError];
      // 读取到空数据说明浏览器端已经断开端口,直接退出进程
      if(rawReqLen == nil || rawReqLen.length == 0 || stdinError != nil) {
        exit(0);
      }
      NSUInteger reqLen;
      [rawReqLen getBytes:&reqLen length:4];
      
      // 按长度读取完整消息
      NSData * req = [weakStdIn readDataUpToLength:reqLen error:&stdinError];
      if(stdinError != nil) {
        exit(1);
      }
      
      handleRequest(req);
      
      // 处理完当前消息后重新注册监听,等待下一条消息
      [weakStdIn waitForDataInBackgroundAndNotify];
    }];
    
    // 启动第一次消息监听
    [stdIn waitForDataInBackgroundAndNotify];
    
    // 启动主线程RunLoop保持进程存活,直到端口断开主动退出
    [[NSRunLoop mainRunLoop] run];
  }
  return 0;
}

另外要注意两个点:

  • 当浏览器端调用port.disconnect时,会主动关闭标准输入流,此时readDataUpToLength会返回空数据,你可以直接在这个分支做资源清理后退出进程
  • 如果需要给浏览器端回传消息,要按照Chrome原生消息的协议,先写4字节的消息长度,再写JSON格式的消息内容到标准输出

内容的提问来源于stack exchange,提问作者Goasmad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 10:24:04