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]; } }
请问有人知道该如何实现这一需求吗?
解答
你的实现无法正常运行主要有两个核心原因:
- 注册监听并调用
waitForDataInBackgroundAndNotify后,主线程没有保持运行,autoreleasepool执行结束后进程直接退出,根本等不到消息通知触发 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
相关产品推荐
相关产品推荐

