bytesWritten,但其他设备从未收到 NSStreamEventHasBytesAvailable 事件

2023-12-25

我已经在 iPhone 和 Mac 之间建立了 Bonjour 网络。

用户在 Mac 中显示的表格中选择 iPhone 的网络服务,并在两侧创建并打开一对流。

iPhone 首先向 Mac 发送一个代码(整数)。 Mac成功接收。

用户输入和处理暂停后,Mac 开始向 iPhone 发送代码:

NSInteger bytesWritten = [self.streamOut write:buffer maxLength:sizeof(uint8_t)];
// bytesWritten is 1.

但 iPhone 永远不会收到 NSStreamEventHasBytesAvailable 事件。就在这之前,我仔细检查了一遍,iPhone 的 NSInputStream 上的streamStatus是2,即 NSStreamStatusOpen,它应该是这样。

有什么想法可能是错的吗?


更新:我进行了一项测试,其中 Mac 是第一个向 iPhone 发送整数的。同样,我从 Mac 的输出流中得到了 1 的 bytesWritten,但 iPhone 从未得到过 NSStreamEventHasBytesAvailable 事件。

所以iPhone的输入流肯定有问题。但我仔细检查了一下:

  • iPhone 的 self.streamIn 在 h 文件中正确键入为 NSInputStream
  • iPhone 收到 2 个 NSStreamEventOpenCompleted 事件,我检查流 arg 的类。一个是KindOfClass:[NSOutputStream class],另一个不是。
  • iPhone 永远不会收到 NSStreamEventEndEncountered、NSStreamEventErrorOccurred 或 NSStreamEventNone。
  • 如上所述,Mac 写入输出流后,iPhone 的输入流状态为 2,NSStreamStatusOpen。

以下是用于创建 iPhone 输入流的代码。它使用 CF 类型,因为它是在 C 风格的套接字回调函数中完成的:

CFReadStreamRef readStream = NULL;
CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketNativeHandle, &readStream, NULL);
if (readStream) {
    CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
    server.streamIn = (NSInputStream *)readStream;
    server.streamIn.delegate = server;
    [server.streamIn scheduleInRunLoop:[NSRunLoop currentRunLoop] 
                               forMode:NSDefaultRunLoopMode];
    if ([server.streamIn streamStatus] == NSStreamStatusNotOpen)
        [server.streamIn open];
    CFRelease(readStream);
}

更新2:响应alastair评论的信息:

插座选项

keep、release 和 copyDescription 回调设置为 NULL。 optionFlags 设置为acceptCallback。

套接字创建

以下是在 iPhone 和 Mac 上设置套接字的方法,以及我试图弄清楚这段代码中实际发生的情况的尝试,该代码改编自各种教程和实验(有效):

/**
 Socket creation, port assignment, socket scheduled in run loop.
 The socket represents the port on this app's end of the connection.
 */
- (BOOL) makeSocket {
    // Make a socket context, with which to configure the socket.
    // It's a struct, but doesn't require "struct" prefix -- because typedef'd?
CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL}; // 2nd arg is pointer for callback function   
    // Make socket.
    // Sock stream goes with TCP protocol, the safe method used for most data transmissions.
    // kCFSocketAcceptCallBack accepts connections automatically and presents them to the callback function supplied in this class ("acceptSocketCallback").
    // CFSocketCallBack, the callback function itself.
    // And note that the socket context is passed in at the end.
    self.socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)&acceptSocketCallback, &socketCtxt);

    // Do socket-creation error checking.
    if (self.socket == NULL) {
        // alert omitted
        return NO;
    }

    // Prepare an int to pass to setsockopt function, telling it whether to use the option specified in arg 3.
    int iSocketOption = 1; // 1 means, yes, use the option

    // Set socket options.
    // arg 1 is an int. C-style method returns native socket.
    // arg 2, int for "level." SOL_SOCKET is standard.
    // arg 3, int for "option name," which is "uninterpreted." SO_REUSEADDR enables local address reuse. This allows a new connection even when a port is in wait state.
    // arg 4, void (wildcard type) pointer to iSocketOption, which has been set to 1, meaning, yes, use the SO_REUSEADDR option specified in arg 3.
    // args 5, the size of iSocketOption, which can now be recycled as a buffer to report "the size of the value returned," whatever that is. 
    setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&iSocketOption, sizeof(iSocketOption));

    // Set up a struct to take the port assignment.
    // The identifier "addr4" is an allusion to IP version 4, the older protocol with fewer addresses, which is fine for a LAN.
    struct sockaddr_in addr4;
    memset(&addr4, 0, sizeof(addr4));
    addr4.sin_len = sizeof(addr4);
    addr4.sin_family = AF_INET;
    addr4.sin_port = 0; // this is where the socket will assign the port number
    addr4.sin_addr.s_addr = htonl(INADDR_ANY);
    // Convert to NSData so struct can be sent to CFSocketSetAddress.
    NSData *address4 = [NSData dataWithBytes:&addr4 length:sizeof(addr4)];

    // Set the port number.
    // Struct still needs more processing. CFDataRef is a pointer to CFData, which is toll-free-bridged to NSData.
    if (CFSocketSetAddress(socket, (CFDataRef)address4) != kCFSocketSuccess) {
        // If unsuccessful, advise user of error (omitted)…
        // ... and discard the useless socket.
        if (self.socket) 
            CFRelease(socket);
        self.socket = NULL;
        return NO;
    }

    // The socket now has the port address. Extract it.
    NSData *addr = [(NSData *)CFSocketCopyAddress(socket) autorelease];
    // Assign the extracted port address to the original struct.
    memcpy(&addr4, [addr bytes], [addr length]);
    // Use "network to host short" to convert port number to host computer's endian order, in case network's is reversed.
    self.port = ntohs(addr4.sin_port);
    printf("\nUpon makeSocket, the port is %d.", self.port);// !!!:testing - always prints a 5-digit number

    // Get reference to main run loop.
    CFRunLoopRef cfrl = CFRunLoopGetCurrent();
    // Schedule socket with run loop, by roundabout means.
    CFRunLoopSourceRef source4 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
    CFRunLoopAddSource(cfrl, source4, kCFRunLoopCommonModes);
    CFRelease(source4);

    // Socket made
    return YES;
}

运行循环调度

是的,所有 4 个流都安排在运行循环中,所有的代码都与我在上面的第一次更新中发布的代码相同。

运行循环阻塞:

我没有对同步、多线程、NSLocks 等做任何花哨的事情。如果我设置一个按钮操作来将某些内容打印到控制台,它就会自始至终起作用——运行循环似乎正在正常运行。


更新4,流端口?

Noa 的调试建议给了我进一步检查流属性的想法:

NSNumber *nTest = [self.streamIn propertyForKey:NSStreamSOCKSProxyPortKey]; // always null!

我原以为溪流挂在它们的端口上,但令人惊讶的是,nTest始终为空。它在我的应用程序中为空,这似乎指出了一个问题 - 但在教程应用程序中它也为空works。如果流在创建后不需要保留其端口分配,那么端口属性的用途是什么?

也许端口属性无法直接访问?但nTest在以下内容中也始终为 null:

    NSDictionary *dTest = [theInStream propertyForKey:NSStreamSOCKSProxyConfigurationKey];
    NSNumber *nTest = [dTest valueForKey:NSStreamSOCKSProxyPortKey];
    NSLog(@"\tInstream port is %@.", nTest); // (null)
    nTest = [dTest valueForKey:NSStreamSOCKSProxyPortKey];
    NSLog(@"\tOutstream port is %@.", nTest); // (null)

问题是这一行:

CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketNativeHandle, &readStream, NULL);

如果我只在 iPhone 端接收数据,那就没问题了。但我正在创建一个pair流,而不仅仅是输入流,因此在这段代码下面我创建了一个写入流:

CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketNativeHandle, NULL, &writeStream); 

CFStream Reference 说:“如果你为 readStream 传递 NULL,该函数将不会创建可读流。”它并不是说如果传递 NULL,就会使之前创建的流变得不可操作。但这显然就是发生的事情。

此设置的一个奇怪的工件是,如果我首先打开 StreamIn,我会遇到相反的问题:iPhone 会收到 hasByteAvailable 事件,但永远不会收到 hasSpaceAvailable 事件。正如问题中所指出的,如果我查询流的状态,两者都会返回 NSStreamStatusOpen。所以花了很长时间才弄清楚真正的错误在哪里。

(这种顺序流创建是我几个月前建立的一个测试项目的产物,在该项目中我测试了仅沿一个方向或另一个方向移动的数据。)

SOLUTION

两个流应该在一行中作为一对创建:

CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketNativeHandle, &readStream, &writeStream);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

bytesWritten,但其他设备从未收到 NSStreamEventHasBytesAvailable 事件 的相关文章

随机推荐

  • 寻找Python字典中最大的键

    General 我需要帮助在 python 中找到一种方法来获得最大值N多维Python字典中的项目 例如 things car weight 100 apple weight 1 spanner weight 10 在这种情况下 我想找到
  • JavaFX WebView 进度始终从 0.0 到 1.0。 (无中间值)

    我目前正在使用 GluonHQ JavaFXPorts 开发一个应用程序 其中我使用 WebView 加载一些 Internet 页面 我注意到当我在桌面上使用以下代码时 webEngine getLoadWorker progressPr
  • undefined 不是一个对象(评估 'ImagePickerManager.showImagePicker')

    我正在尝试使用react native image picker 但卡在这里 它总是显示错误 如下图所示 undefined 不是一个对象 评估 ImagePickerManager showImagePicker 有人遇到过这个问题吗 我
  • 使用 Python 的函数返回值为 shell 变量赋值

    我有一个 Python 函数 fooPy 它返回一些值 整数 双精度或字符串 我想使用这个值并在 shell 脚本中分配它 例如以下是 python 函数 def fooPy return some string return 10 alt
  • 使用 API 通过 Nodejs 使用 Drive.files.copy 将 Word 文档转换为 Google 文档 在 Google Drive API v3 中进行转换

    我正在尝试通过 Node js 使用 API 将 Word 文档转换为 Google 文档 单词文档已经在一个文件夹中 我只想将它们转换为谷歌文档 我正在使用v3 The v3 docs https developers google co
  • PHP EOF 仅显示循环的一个结果

    我在 PHP 中使用 EOF 问题是它只显示来自 mySQL 循环的一项 它仅显示最后的结果 这在EOF中有必要吗 或者我可以避免这个问题吗 Thanks function getYiBAdminBanner global site glo
  • R try catch 块

    我正在尝试在循环中评估树的多个输出参数 但有时树功能会中止 这些行如何被 try catch 块包围 我很抱歉没有 真正的 代码 但我没有非工作树的示例 这是pseddo代码来说明当前的实现 for icol in seq 1 ncol c
  • 如何从纬度和经度找出地图瓦片坐标?

    我正在使用 Mapbox 矢量切片从后端进程收集特定数据 在示例中 他们提供了曼哈顿图块的链接 http a tiles mapbox com v3 examples map zr0njcqy 14 4823 6160 png http a
  • 如何在管道中使用导管下降功能?

    我有一个简单的任务 从文件中读取一堆行并对每一行执行一些操作 除了第一个 这是一些需要忽略的标题 所以我想我应该尝试一下管道 printFile src runResourceT CB sourceFile src CT decode CT
  • 有没有办法获得 dask 中每组最大的项目?

    我有以下数据集 location category percent A 5 100 0 B 3 100 0 C 2 50 0 4 13 0 D 2 75 0 3 59 0 4 13 0 5 4 0 我正在尝试获取数据框中按位置分组的最大类别
  • 使用别名覆盖内置命令

    我正在尝试创建一个覆盖的别名cd命令 这将在 真实 之前和之后执行一个脚本cd 这是我到目前为止所拥有的 alias cd echo before cd 1 echo after 这将执行echo before and echo after
  • 识别通过蓝牙与 PixelSense 配对的移动设备

    我希望能够通过蓝牙将 Microsoft PixelSense 硬件与多个移动设备配对 并且我希望 PixelSense 知道哪个设备是哪个 因此 如果我将两部手机放在桌子上 PixelSense 应该能够通过设备名称来标记它们 我最初的想
  • html 模板保存在哪里?

    我有一个单页应用程序 目前我的模板存储在index html中 例如 以这种方式存储它们是最佳实践吗 我发现了jQuery 模板 我应该把它们放在哪里 https stackoverflow com questions 4719828 jq
  • Redis 作为独特的原子 ID 生成器 - Web 应用程序避免竞争条件的线程安全方式

    我计划使用 Redis 作为唯一的原子 id 生成器 但是 我担心多个浏览器可能会同时发出 Web 请求 我想知道 使以下操作原子化的常见做法是什么 get id from redis if id is not found insert i
  • 如何从环境变量将动态主题名称传递给@KafkaListener(topics)

    我正在写一个卡夫卡消费者 我需要将环境变量主题名称传递给 KafkaListener topics 这是我到目前为止所尝试过的 import org springframework beans factory annotation Auto
  • 使用 itextsharp 根据大小将 pdf 拆分为更小的 pdf

    因此 我们有一些非常低效的代码 可以根据允许的最大大小将 pdf 分成更小的块 又名 如果最大大小为 10megs 则将跳过 8 meg 文件 而将根据页数拆分 16 meg 文件 这是我继承的代码 我觉得必须有一种更有效的方法来做到这一点
  • numpy 中的数组切片

    今天我使用numpy数组进行一些计算 发现一个奇怪的问题 例如 假设我已经在Ipython中导入了numpy arange 并且我运行了一些脚本 如下所示 In 5 foo arange 10 In 8 foo1 foo arange 3
  • 如何通过 AJAX POST 将“数据”发送到 ASMX Web 服务?

    我可以成功地从我的网络服务接收值 因此在这方面脚本工作正常 不过 我现在尝试使用下面的 数据 字段将数据发送到网络服务 我不知道如何将一个简单的字符串 例如 test 发送到网络服务 这是我的网络方法期望的参数 任何帮助深表感谢 例如 fu
  • 将表单传递给服务层与原始输入

    验证表单并将其过滤后的输入传递到服务层更好 还是将原始输入传递到服务层并让服务验证输入 有或没有表单实例 更好 显然 如果是后者 控制器仍然需要访问表单 以便将其发送到视图进行渲染 如果是这样 您是否只需通过服务 service gt ge
  • bytesWritten,但其他设备从未收到 NSStreamEventHasBytesAvailable 事件

    我已经在 iPhone 和 Mac 之间建立了 Bonjour 网络 用户在 Mac 中显示的表格中选择 iPhone 的网络服务 并在两侧创建并打开一对流 iPhone 首先向 Mac 发送一个代码 整数 Mac成功接收 用户输入和处理暂