bytesWritten, pero el otro dispositivo nunca recibe el evento NSStreamEventHasBytesAvailable

He configurado una networking Bonjour entre un iPhone y una Mac.

El usuario elige el service de networking del iPhone en una tabla presentada en Mac, y se crean y abren un par de transmisiones en ambos lados.

El iPhone comienza enviando un código (un número integer) a la Mac. El Mac lo recibe con éxito.

Después de una pausa para la input y el procesamiento del usuario, la Mac inicia el envío de un código al iPhone:

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

Pero el iPhone nunca recibe un evento NSStreamEventHasBytesAvailable. Comprobé dos veces justo antes de este punto, y streamStatus en el iPhone NSInputStream es 2, que es NSStreamStatusOpen, como debería ser.

¿Alguna idea sobre que podría ir mal?


Actualización: ejecuté una testing en la que el Mac fue el primero en enviar un número integer al iPhone. De nuevo, obtuve una escritura de bytes de 1 desde el flujo de salida de Mac, pero el iPhone nunca obtuvo un evento NSStreamEventHasBytesAvailable.

Entonces, debe haber algo mal con el flujo de input del iPhone. Pero he comprobado dos veces:

  • El iPhone self.streamIn se escribe correctamente como NSInputStream en el file h
  • iPhone recibe 2 events NSStreamEventOpenCompleted y verifico la class del stream arg. One isKindOfClass: [NSOutputStream class], el otro no.
  • iPhone nunca recibe NSStreamEventEndEncountenetworking, NSStreamEventErrorOccurnetworking o NSStreamEventNone.
  • Como se indicó anteriormente, siguiendo el flujo de salida de escritura de la Mac, el estado del flujo de input de iPhone es 2, NSStreamStatusOpen.

Aquí está el código utilizado para crear el flujo de input del iPhone. Utiliza types de CF porque se realiza en la function de callback de socket de estilo 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); } 

Actualización2: Información en respuesta al comentario de alastair:

Opciones de sockets

Las devoluciones de llamada retener, liberar y copyr descripción están configuradas en NULL. Los optionFlags están configurados para acceptCallback.

Socket Creation

Aquí está el método utilizado para configurar el socket tanto en el iPhone como en el Mac, junto con mis bashs comentados de averiguar qué está sucediendo realmente en este código, que se adaptó de varios tutoriales y experimentos (que funcionaron):

 /** 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; } 

Progtwigción de Runloop

Sí, las 4 transmisiones están progtwigdas en el runloop, todas usan código equivalente al que publiqué en la primera actualización anterior.

Bloqueo Runloop:

No estoy haciendo nada elegante con la synchronization, varios subprocesss, NSLocks, o similar. Y si configuro una acción de button para imprimir algo en la console, funciona durante todo el time; el runloop parece estar ejecutándose normalmente.


¿Actualizar4, Puertos de transmisión?

La sugerencia de debugging de Noa me dio la idea de examinar más a background las properties de la stream:

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

Había asumido que las streams colgaban de sus puertos, pero, sorprendentemente, nTest es siempre nulo. Es nulo en mis aplicaciones, lo que parece apuntar a un problema, pero también es nulo en una aplicación tutorial que funciona . Si una transmisión no necesita colgarse de su asignación de puerto una vez creada, ¿cuál es el propósito de la propiedad de puerto?

Tal vez la propiedad del puerto no es accesible directamente? Pero nTest es nulo en lo siguiente:

  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) 

El problema era esta línea:

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

Esto estaría bien si solo recibiera datos en el iPhone. Pero estaba creando un par de secuencias, no solo una secuencia de input, por lo que debajo de este código estaba creando una secuencia de escritura:

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

La reference CFStream dice: "Si pasa NULL [para readStream], esta function no creará una transmisión legible". No dice que si pasa NULL, hará que una transmisión creada previamente no funcione. Pero eso es aparentemente lo que sucede.

Un artefacto extraño de esta configuration fue que si abría primero la transmisión, tendría el problema opuesto: el iPhone obtendría events hasByteAvailable, pero nunca un evento hasSpaceAvailable. Y como se señaló en la pregunta, si consulté las secuencias para su estado, ambos devolverían NSStreamStatusOpen. Entonces, tomó mucho time descubrir dónde estaba el verdadero error.

(Esta creación de secuencia secuencial fue un artefacto de un proyecto de testing que había establecido meses antes, en el que probé datos que se mueven en una sola dirección o en la otra).

SOLUCIÓN

Ambas secuencias deberían crearse como un par, en una línea:

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