2012-04-30 16 views
5

Ustanowiłem sieć Bonjour między iPhonem a komputerem Mac.bytesOpis, ale inne urządzenie nigdy nie odbiera zdarzenia NSStreamEventHasBytesAvailable

Użytkownik wybiera serwis internetowy iPhone'a w tabeli prezentowanej na Macu, a para strumieni jest tworzona i otwierana po obu stronach.

iPhone zaczyna się od wysłania kodu (liczby całkowitej) na komputer Mac. Komputer Mac z powodzeniem go odbiera.

Po chwili do wejścia i przetwarzania użytkownika, Mac inicjuje wysłanie kodu do telefonu iPhone:

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

ale iPhone nie dostaje NSStreamEventHasBytesAvailable wydarzenie. Sprawdziłem dokładnie przed tym punktem, a streamStatus na NSInputStream iPhone'a to 2, czyli NSStreamStatusOpen, tak jak powinno być.

Wszelkie pomysły, co może być nie tak?


Aktualizacja: Przeprowadziłem test, w którym komputer Mac był pierwszym, który wyśle ​​liczbę całkowitą do iPhone'a. Znów dostałem bajty napisane jako 1 ze strumienia wyjściowego Maca, ale iPhone nigdy nie otrzymał zdarzenia NSStreamEventHasBytesAvailable.

Tak więc coś musi być nie tak ze strumieniem wejściowym iPhone'a. Ale doublechecked: self.streamIn

  • iPhone jest prawidłowo wpisany jako NSInputStream w pliku h
  • iPhone otrzymuje 2 zdarzenia NSStreamEventOpenCompleted i sprawdzić klasę arg strumienia. Jeden toKindOfClass: [klasa NSOutputStream], a drugi nie.
  • iPhone nigdy nie otrzymuje wartości NSStreamEventEndEncountered, NSStreamEventErrorOccurred lub NSStreamEventNone.
  • Jak wspomniano powyżej, po zapisie do strumienia wyjściowego komputera Mac status strumienia wejściowego iPhone'a to 2, NSStreamStatusOpen.

Oto kod służący do tworzenia strumienia wejściowego iPhone'a. Wykorzystuje rodzaje CF ponieważ jest to zrobione w stylu C Funkcja gniazdo callback:

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: Informacje reaguje na komentarz Alastair za:

Opcje Socket

Zachować, zwolnij , a wywołania zwrotne copyDescription mają wartość NULL. OpcjiFlags ustawiono na AcceptCallback.

Socket Creation

Oto metoda stosowana założyć gniazdo zarówno na iPhone i Mac, wraz z moimi skomentował prób, aby dowiedzieć się, co faktycznie dzieje się w tym kodzie, który został dostosowany od różne ćwiczenia i eksperymenty (który pracował):

/** 
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 Scheduling

Tak, wszystkie 4 strumienie zaplanowane w runloop, wszystkie używają kodu równoważnego z tym, co napisałem w pierwszej aktualizacji powyżej.

Runloop Blokowanie:

Nie robię niczego wyjątkowego, z synchronizacją wielu wątków, NSLocks, lub tym podobne. A jeśli ustawię akcję przycisku, aby wydrukować coś na konsoli, działa ona przez cały czas - runloop wydaje się działać normalnie.


Update4, Stream Porty?

Noa za debugowanie sugestia dał mi pomysł, aby zbadać właściwości strumieniowych dalej:

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

Przypuszczałem, że strumienie wisiały na swoich portach, ale zaskakująco, nTest zawsze ma wartość null. W moich aplikacjach jest pusta, co wydaje się wskazywać na problem - ale jest również zerowa w aplikacji samouczka, która działa. Jeśli po utworzeniu strumienia nie ma potrzeby zawieszania się jego przypisania do portu, jaki jest cel właściwości portu?

Być może właściwość portu nie jest dostępna bezpośrednio? Ale nTest zawsze ma wartość null w dalszej części, zbyt:

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

próbowałeś dzwoniąc '-hasBytesAvailable' i' -read: maxLength: 'bezpośrednio? – paulmelnikow

+0

Wywołanie [self.streamIn hasBytesAvailable] na iPhonie zwraca NO, mimo że wywołuję go (poprzez akcję przycisku testowego) * po * dziennikach aplikacji Maca potwierdzam, że Mac napisał bajt. Doskonałe porady dotyczące debugowania, ale przy zerowym zwrocie nie jestem pewien, co dalej ... – Wienke

+1

Czy masz ustawione opcje gniazd? Jak stworzyłeś gniazda, których używasz w tych strumieniach? Czy wszystkie strumienie są zaplanowane w pętli uruchamiania? Czy faktycznie działa uruchomiona pętla uruchamiania (tzn. Nie blokujesz nigdzie?) – alastair

Odpowiedz

2

Kłopot polegał ten wiersz:

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

To byłoby OK gdybym odbierania danych wyłącznie na końcu iPhone. Ale było stworzenie parę strumieni, a nie tylko strumień wejściowy, więc pod tym kodem byłem tworząc strumień napisać:

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

CFStream referencyjny mówi: „Jeśli przekazać NULL [dla readStream] ta funkcja nie utworzy strumienia czytelnego. "Nie oznacza to, że jeśli prześlesz NULL, wyrenderujesz wcześniej utworzony strumień, który będzie niedziałający. Ale najwyraźniej tak się dzieje.

Jednym z dziwnych artefaktów tej instalacji było to, że jeśli najpierw otworzę streamIn, będę miał przeciwny problem: iPhone będzie miał zdarzenia hasByteAvailable, ale nigdy nie ma zdarzenia hasSpaceAvailable. I jak wspomniano w pytaniu, jeśli zapytałem strumienie o ich status, oba zwrócą NSStreamStatusOpen. Tak więc zajęło dużo czasu, aby dowiedzieć się, gdzie był prawdziwy błąd.

(To sekwencyjne tworzenie strumienia było artefaktem projektu testowego, który ustawiłem kilka miesięcy wcześniej, w którym przetestowałem dane poruszające się tylko w jednym kierunku lub w drugim.)

ROZWIĄZANIE

Oba strumienie powinny być tworzone w postaci pary, w jednym wierszu:

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

Prosimy o oznaczenie własnej odpowiedzi jako poprawnej odpowiedzi. Dobra robota na zastanawianie się. –

Powiązane problemy