SimpleNetworkStreams展示了如何基於Socket網路編程,實現了一個很典型的區域網路內網路資料轉送的情境,一個是client向server端發送本地的圖片檔案,另一個是client向server端下載圖片到本地檔案。抽取出來的一般流程:
此處IOS的一般做法是三步走:
第一步:建立系統級的socket,並綁定連接埠
port = 0; fd = socket(AF_INET, SOCK_STREAM, 0); success = (fd != -1); if (success) { memset(&addr, 0, sizeof(addr)); addr.sin_len = sizeof(addr); addr.sin_family = AF_INET; addr.sin_port = 0; addr.sin_addr.s_addr = INADDR_ANY; err = bind(fd, (const struct sockaddr *) &addr, sizeof(addr)); success = (err == 0); } if (success) { err = listen(fd, 5); success = (err == 0); } if (success) { socklen_t addrLen; addrLen = sizeof(addr); err = getsockname(fd, (struct sockaddr *) &addr, &addrLen); success = (err == 0); if (success) { assert(addrLen == sizeof(addr)); port = ntohs(addr.sin_port); } }
這裡用port=0是讓系統自動隨機找一個空閑連接埠。其他都是基於c風格對系統函數的直接調用。
第二步:用IOS的socket(CFSocket)封裝系統socket
CFSocketContext context = { 0, (__bridge void *) self, NULL, NULL, NULL };assert(self->_listeningSocket == NULL);self->_listeningSocket = CFSocketCreateWithNative( NULL, fd, kCFSocketAcceptCallBack, AcceptCallback, &context);success = (self->_listeningSocket != NULL);if (success) { CFRunLoopSourceRef rls; fd = -1; // listeningSocket is now responsible for closing fd rls = CFSocketCreateRunLoopSource(NULL, self.listeningSocket, 0); assert(rls != NULL); CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); CFRelease(rls);}
這裡封裝socket的目的是便於後面的事件偵聽和處理,把基於原生態socket的開發轉到IOS的層面上來,這裡accept事件偵聽函數是AcceptCallback,並在單獨thread中執行。
第三步:通過NSNetService發布socket
if (success) { self.netService = [[NSNetService alloc] initWithDomain:@"local." type:@"_x-SNSUpload._tcp." name:@"Test" port:port]; success = (self.netService != nil);}if (success) { self.netService.delegate = self; [self.netService publishWithOptions:NSNetServiceNoAutoRename]; // continues in -netServiceDidPublish: or -netService:didNotPublish: ...}
這裡是基於NSNetService把先前建立的socket發布出去,便於clienti串連和請求。
這裡是client通過前面server發布出來了netservice,發起對socket的串連:
netService = [[NSNetService alloc] initWithDomain:@"local." type:@"_x-SNSUpload._tcp." name:@"Test"];
server會在accept的事件偵聽的回呼函數裡對socket開啟stream,並偵聽stream上的各種IO事件:
CFStreamCreatePairWithSocket(NULL, fd, &readStream, NULL);assert(readStream != NULL);self.networkStream = (__bridge NSInputStream *) readStream;CFRelease(readStream);[self.networkStream setProperty:(id)kCFBooleanTrue forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];self.networkStream.delegate = self;[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];[self.networkStream open];
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode // An NSStream delegate callback that's called when events happen on our // network stream.{ assert(aStream == self.networkStream); #pragma unused(aStream) switch (eventCode) { case NSStreamEventOpenCompleted: { [self updateStatus:@"Opened connection"]; } break; case NSStreamEventHasBytesAvailable: { NSInteger bytesRead; uint8_t buffer[32768]; [self updateStatus:@"Receiving"]; // Pull some data off the network. bytesRead = [self.networkStream read:buffer maxLength:sizeof(buffer)]; if (bytesRead == -1) { [self stopReceiveWithStatus:@"Network read error"]; } else if (bytesRead == 0) { [self stopReceiveWithStatus:nil]; } else { NSInteger bytesWritten; NSInteger bytesWrittenSoFar; // Write to the file. bytesWrittenSoFar = 0; do { bytesWritten = [self.fileStream write:&buffer[bytesWrittenSoFar] maxLength:bytesRead - bytesWrittenSoFar]; assert(bytesWritten != 0); if (bytesWritten == -1) { [self stopReceiveWithStatus:@"File write error"]; break; } else { bytesWrittenSoFar += bytesWritten; } } while (bytesWrittenSoFar != bytesRead); } } break; case NSStreamEventHasSpaceAvailable: { assert(NO); // should never happen for the output stream } break; case NSStreamEventErrorOccurred: { [self stopReceiveWithStatus:@"Stream open error"]; } break; case NSStreamEventEndEncountered: { // ignore } break; default: { assert(NO); } break; }}
以上代碼是server監聽到有資料發過來時,把資料寫入本地檔案中,這裡實際上就是把網路inputstream寫入File的outputstream。這裡handleevent方法是當設定了self.networkStream.delegate = self後IO事件的回呼函數,handleevent裡就需要根據不同的事件類型進行不同的處理。
client的資料處理與server類似也是通過對網路stream的事件監聽來完成:
self.networkStream = output;self.networkStream.delegate = self;[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];[self.networkStream open];
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode // An NSStream delegate callback that's called when events happen on our // network stream.{ assert(aStream == self.networkStream); #pragma unused(aStream) switch (eventCode) { case NSStreamEventOpenCompleted: { [self updateStatus:@"Opened connection"]; } break; case NSStreamEventHasBytesAvailable: { assert(NO); // should never happen for the output stream } break; case NSStreamEventHasSpaceAvailable: { [self updateStatus:@"Sending"]; // If we don't have any data buffered, go read the next chunk of data. if (self.bufferOffset == self.bufferLimit) { NSInteger bytesRead; bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize]; if (bytesRead == -1) { [self stopSendWithStatus:@"File read error"]; } else if (bytesRead == 0) { [self stopSendWithStatus:nil]; } else { self.bufferOffset = 0; self.bufferLimit = bytesRead; } } // If we're not out of data completely, send the next chunk. if (self.bufferOffset != self.bufferLimit) { NSInteger bytesWritten; bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset]; assert(bytesWritten != 0); if (bytesWritten == -1) { [self stopSendWithStatus:@"Network write error"]; } else { self.bufferOffset += bytesWritten; } } } break; case NSStreamEventErrorOccurred: { [self stopSendWithStatus:@"Stream open error"]; } break; case NSStreamEventEndEncountered: { // ignore } break; default: { assert(NO); } break; }}
這裡的過程與server端正好相反,是從file的Inputstream中讀入資料,並寫入網路的outputsteam中。
以上就是我理解的IOS中Socket網路編程的一般模式。