2012-04-30 9 views
5

He configurado una red Bonjour entre un iPhone y una Mac.bytes Escrito, pero el otro dispositivo nunca recibe el evento NSStreamEventHasBytesAvailable

El usuario elige el servicio de red del iPhone en una tabla presentada en la Mac, y se crean un par de secuencias y se abren en ambos lados.

El iPhone comienza por enviar un código (un número entero) a la Mac. La Mac lo recibe con éxito.

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

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

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

¿Alguna idea de lo que podría estar mal?


Actualización: Me hizo una prueba en la que el Mac era el primero en enviar un entero en el iPhone. Una vez más, obtuve un bytes de 1 de la secuencia de salida de Mac, pero el iPhone nunca obtuvo un evento NSStreamEventHasBytesAvailable.

Entonces debe haber algo mal con la corriente de entrada del iPhone. Pero yo doublechecked: self.streamIn

  • de iPhone se escribe correctamente como NSInputStream en el archivo h
  • iPhone recibe 2 eventos NSStreamEventOpenCompleted, y puedo comprobar la clase de la corriente arg. Uno es KindOfClass: [clase NSOutputStream], el otro no.
  • iPhone nunca recibe NSStreamEventEndEncountered, NSStreamEventErrorOccurred o NSStreamEventNone.
  • Como se indicó anteriormente, después de la secuencia de escritura a salida de Mac, el estado de la secuencia de entrada del iPhone es 2, NSStreamStatusOpen.

Aquí está el código utilizado para crear la corriente de entrada del iPhone. Utiliza tipos CF porque se hace en la función de toma de devolución de llamada 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); 
} 

Update2: Información responde al comentario de Alastair:

Opciones Socket

La retener, liberación y las devoluciones de llamadas copyDescription se establecen en NULL. Los optionFlags están configurados para acceptCallback.

zócalo Creación

Este es el método utilizado para establecer la toma situada en el iPhone y el Mac, completo con mis intentos comentadas para averiguar lo que realmente está sucediendo en este código, que es una adaptación de varios tutoriales y experimentos (que funcionaba):

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

Runloop programación

Sí, los 4, transmite a programado en el runloop, todos usan un código equivalente al que publiqué en la primera actualización anterior.

Runloop Bloqueo:

no estoy haciendo algo de fantasía con la sincronización, múltiples hilos, NSLocks, o similares. Y si configuré una acción de botón para imprimir algo en la consola, funciona todo el tiempo: parece que Runloop se ejecuta normalmente.


Update4, Stream Puertos?

sugerencia de depuración de Noa me dio la idea de examinar las propiedades de la corriente más allá:

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

Yo había asumido que las corrientes estaban colgados en sus puertos, pero, sorprendentemente, nTest siempre es nula. Es nulo en mis aplicaciones, lo que parece indicar un problema, pero también es nulo en una aplicación tutorial que funciona. Si una secuencia no necesita mantener su asignación de puerto una vez creada, ¿cuál es el propósito de la propiedad del puerto?

¿Quizás la propiedad del puerto no se puede acceder directamente? Pero nTest siempre es nula en lo que sigue, también:

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) 
+2

Ha intentado llamar '' -hasBytesAvailable' y -léase: maxLength: 'directamente? – paulmelnikow

+0

Llamar [self.streamIn hasbytesdisponible] en el iPhone devuelve NO, aunque lo estoy llamando (mediante una acción de botón de prueba) * después * los registros de la aplicación Mac confirman que Mac ha escrito un byte. Excelente consejo de depuración, aunque con una devolución NO No estoy seguro de qué hacer a continuación ... – Wienke

+1

¿Tiene alguna opción de socket configurada? ¿Cómo se crearon los enchufes que está utilizando con estas transmisiones? ¿Están todas las transmisiones programadas en un ciclo de ejecución? ¿El ciclo de ejecución en cuestión se está ejecutando (es decir, no está bloqueando en ningún lugar?) – alastair

Respuesta

2

El problema era que esta línea:

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

Esto habría estado bien si sólo estaban recibiendo datos en el extremo iPhone. Pero yo estaba creando un par de corrientes, no sólo un flujo de entrada, por lo que a continuación el código que estaba creando una corriente de escritura:

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

El CFStream referencia dice: “Si pasa NULL [para ReadStream], esta función no creará una secuencia legible. "No dice que si pasas NULL, harás inoperable una secuencia previamente creada. Pero aparentemente eso es lo que sucede.

Un artefacto extraño de esta instalación era que si abría la streamin primero, me gustaría tener el problema opuesto: El iPhone llegaría eventos hasByteAvailable, pero nunca un evento hasSpaceAvailable. Y como se señala en la pregunta, si consulté las transmisiones para conocer su estado, ambas devolverían NSStreamStatusOpen. Por lo tanto, llevó mucho tiempo descubrir dónde estaba el verdadero error.

(Esta creación secuencia secuencial fue un artefacto de un proyecto de prueba que había configurado meses antes, en el que probé los datos moviéndose en una dirección u otra.)

SOLUCIÓN

Ambas corrientes deben ser creados como un par, en una línea:

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

Seleccione su respuesta como la respuesta correcta. Buen trabajo para descubrirlo. –

Cuestiones relacionadas