Wie*_*nke 5 cocoa bonjour nsstream ios
我在iPhone和Mac之间建立了一个Bonjour网络.
用户在Mac中呈现的表格中选择iPhone的网络服务,并在两侧创建并打开一对流.
iPhone首先向Mac发送代码(整数).Mac成功收到它.
暂停用户输入和处理后,Mac会启动向iPhone发送代码:
NSInteger bytesWritten = [self.streamOut write:buffer maxLength:sizeof(uint8_t)];
// bytesWritten is 1.
Run Code Online (Sandbox Code Playgroud)
但iPhone永远不会获得NSStreamEventHasBytesAvailable事件.我在此之前仔细检查了一下,iPhone的NSInputStream上的streamStatus是2,它应该是NSStreamStatusOpen.
什么想法可能是错的?
更新:我运行了一个测试,其中Mac是第一个向iPhone发送整数的测试.再一次,我从Mac的输出流中获得了1的bytesWritten,但iPhone从未得到过NSStreamEventHasBytesAvailable事件.
所以iPhone的输入流肯定有问题.但我仔细检查:
这是用于创建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);
}
Run Code Online (Sandbox Code Playgroud)
Update2:响应alastair评论的信息:
套接字选项
retain,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;
}
Run Code Online (Sandbox Code Playgroud)
Runloop调度
是的,所有4个流都在runloop中安排,所有使用的代码都相当于我在上面的第一个更新中发布的代码.
Runloop阻止:
我没有做任何与同步,多线程,NSLocks等有关的事情.如果我设置一个按钮动作来打印到控制台的东西,它可以在整个过程中运行 - runloop似乎正常运行.
Update4,Stream Ports?
Noa的调试建议让我有了进一步检查流属性的想法:
NSNumber *nTest = [self.streamIn propertyForKey:NSStreamSOCKSProxyPortKey]; // always null!
Run Code Online (Sandbox Code Playgroud)
我曾假设流被挂在他们的端口上,但令人惊讶的nTest是,它始终是空的.这是我的应用程序,这似乎指向一个问题,空-但它也是空的教程应用程序,工作.如果流创建后不需要挂起其端口分配,那么端口属性的目的是什么?
也许港口物业不能直接进入?但是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)
Run Code Online (Sandbox Code Playgroud)
问题是这一行:
\n\nCFStreamCreatePairWithSocket(kCFAllocatorDefault, socketNativeHandle, &readStream, NULL);\nRun Code Online (Sandbox Code Playgroud)\n\n如果我只在 iPhone 端接收数据,那就没问题了。但我正在创建一对流,而不仅仅是一个输入流,因此在这段代码下面我正在创建一个写入流:
\n\nCFStreamCreatePairWithSocket(kCFAllocatorDefault, socketNativeHandle, NULL, &writeStream); \nRun Code Online (Sandbox Code Playgroud)\n\nCFStream Reference 说,\xe2\x80\x9c如果你传递 NULL [for readStream],这个函数将不会创建一个可读的流。\xe2\x80\x9d 它没有说\xe2\x80\x99t如果你传递 NULL,你\xe2\x80\x99 将使先前创建的流无法操作。但这显然就是发生的事情。
\n\n这种设置的一个奇怪的现象是,如果我首先打开streamIn,我会遇到相反的问题:iPhone会收到hasByteAvailable事件,但永远不会收到hasSpaceAvailable事件。正如问题中所指出的,如果我查询流的状态,两者都会返回 NSStreamStatusOpen。所以花了很长时间才弄清楚真正的错误在哪里。
\n\n(这种顺序流创建是我几个月前建立的一个测试项目的产物,在该项目中我测试了仅沿一个方向或另一个方向移动的数据。)
\n\n解决方案
\n\n两个流应该在一行中作为一对创建:
\n\nCFStreamCreatePairWithSocket(kCFAllocatorDefault, socketNativeHandle, &readStream, &writeStream);\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
1963 次 |
| 最近记录: |