2014-04-07 13 views
10

Tôi chỉ mới bắt đầu với lập trình socket trên iOS và tôi đang cố gắng xác định việc sử dụng sự kiện NSStreamEventHasSpaceAvailable cho NSOutputStreams.Cách chính xác để gửi dữ liệu qua ổ cắm với NSOutputStream

Một mặt, Apple's official documentation (Listing 2) cho thấy rằng trong phương pháp -stream:handleEvent: đại biểu, dữ liệu cần được ghi vào bộ đệm đầu ra với -write:maxLength: tin nhắn, thông qua dữ liệu liên tục từ một bộ đệm, bất cứ khi nào sự kiện NSStreamEventHasSpaceAvailable nhận được.

Mặt khác, this tutorial from Ray Wenderlichthis iOS TCP socket example on GitHub bỏ qua sự kiện NSStreamEventHasSpaceAvailable hoàn toàn, và chỉ cần đi trước và -write:maxLength: vào bộ đệm bất cứ khi nào họ cần (thậm chí phớt lờ -hasSpaceAvailable).

Thứ ba, có this example code mà dường như làm cả ...

Câu hỏi của tôi do đó, cách chính xác (s) để xử lý ghi dữ liệu vào một NSOutputStream được gắn vào một ổ cắm là gì? Và những gì sử dụng là mã sự kiện NSStreamEventHasSpaceAvailable nếu nó có thể (rõ ràng) bị bỏ qua? Dường như với tôi rằng có một hoặc rất may mắn UB xảy ra (trong ví dụ 2 và 3), hoặc có một số cách để gửi dữ liệu thông qua một socket dựa trên NSOutputStream ...

Trả lời

13

Bạn có thể ghi vào luồng bất cứ lúc nào , nhưng đối với luồng mạng, -write:maxLength: chỉ trả lại cho đến khi ít nhất một byte đã được ghi vào bộ đệm ghi ổ cắm. Do đó, nếu bộ đệm ghi ổ cắm đầy (ví dụ: vì đầu kia của kết nối không đọc dữ liệu đủ nhanh), điều này sẽ chặn chuỗi hiện tại. Nếu bạn viết từ chủ đề chính, điều này sẽ chặn giao diện người dùng.

Sự kiện NSStreamEventHasSpaceAvailable được báo hiệu khi bạn có thể ghi vào luồng mà không bị chặn. Chỉ viết để phản hồi sự kiện đó tránh được luồng hiện tại và có thể giao diện người dùng bị chặn.

Hoặc, bạn có thể ghi vào luồng mạng từ một "chuỗi ghi" riêng biệt.

12

Sau khi xem câu trả lời của @ MartinR, tôi đọc lại Tài liệu Apple và đã đọc một số sự kiện trên NSRunLoop. Các giải pháp không phải là tầm thường như tôi lần đầu tiên nghĩ và đòi hỏi một số đệm thêm.

Kết luận

Trong khi ví dụ Ray Wenderlich hoạt động, nó không phải là tối ưu - như đã nêu bởi @MartinR, nếu không có chỗ trong cửa sổ TCP gửi đi, cuộc gọi đến write:maxLength sẽ chặn. Lý do ví dụ của Ray Wenderlich không hoạt động là vì các tin nhắn được gửi nhỏ và không thường xuyên, và có kết nối internet băng thông rộng và không có lỗi, nó sẽ 'có thể' hoạt động. Khi bạn bắt đầu xử lý (nhiều) lượng dữ liệu lớn hơn đang được gửi (nhiều) thường xuyên hơn, tuy nhiên, các cuộc gọi write:maxLength: có thể bắt đầu chặn và Ứng dụng sẽ bắt đầu dừng ...

Đối với trường hợp NSStreamEventHasSpaceAvailable, tài liệu của Apple có những lời khuyên sau đây:

Nếu các đại biểu nhận một sự kiện NSStreamEventHasSpaceAvailable và không ghi bất cứ điều gì cho luồng dữ liệu, nó không nhận các sự kiện còn chỗ trống thêm từ chạy lặp lại cho đến khi đối tượng NSOutputStream nhận được nhiều byte hơn. ... ... Bạn có thể yêu cầu đại biểu đặt cờ khi nó không ghi vào luồng khi nhận được sự kiện NSStreamEventHasSpaceAvailable. Sau đó, khi chương trình của bạn có nhiều byte để ghi, nó có thể kiểm tra cờ này và, nếu được đặt, hãy ghi trực tiếp vào thể hiện luồng đầu ra.

Nó được do đó chỉ 'đảm bảo an toàn' để gọi write:maxLength: trong hai kịch bản:

  1. Bên trong callback (trên nhận được sự kiện NSStreamEventHasSpaceAvailable).
  2. Bên ngoài cuộc gọi lại nếu và chỉ khi chúng tôi đã nhận được NSStreamEventHasSpaceAvailable nhưng đã chọn không gọi write:maxLength: bên trong chính cuộc gọi lại (ví dụ: chúng tôi không có dữ liệu để thực sự viết).

Đối với trường hợp (2), chúng tôi sẽ không nhận được cuộc gọi lại cho đến khi write:maxLength thực sự được gọi trực tiếp - Apple đề xuất đặt cờ bên trong cuộc gọi đại biểu (xem bên trên) để cho biết thời điểm chúng tôi được phép thực hiện việc này.

Giải pháp của tôi là sử dụng mức đệm bổ sung - thêm NSMutableArray làm hàng đợi dữ liệu. Mã của tôi để viết dữ liệu vào một ổ cắm trông như thế này (ý kiến ​​và kiểm tra lỗi bỏ qua cho ngắn gọn, biến currentDataOffset chỉ ra chúng tôi đã gửi bao nhiêu NSData đối tượng 'hiện tại'):

// Public interface for sending data. 
- (void)sendData:(NSData *)data { 
    [_dataWriteQueue insertObject:data atIndex:0]; 
    if (flag_canSendDirectly) [self _sendData]; 
} 

// NSStreamDelegate message 
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { 
    // ... 
    case NSStreamEventHasSpaceAvailable: { 
     [self _sendData]; 
     break; 
    } 
} 

// Private 
- (void)_sendData { 
    flag_canSendDirectly = NO; 
    NSData *data = [_dataWriteQueue lastObject]; 
    if (data == nil) { 
     flag_canSendDirectly = YES; 
     return; 
    } 
    uint8_t *readBytes = (uint8_t *)[data bytes]; 
    readBytes += currentDataOffset; 
    NSUInteger dataLength = [data length]; 
    NSUInteger lengthOfDataToWrite = (dataLength - currentDataOffset >= 1024) ? 1024 : (dataLength - currentDataOffset); 
    NSInteger bytesWritten = [_outputStream write:readBytes maxLength:lengthOfDataToWrite]; 
    currentDataOffset += bytesWritten; 
    if (bytesWritten > 0) { 
     self.currentDataOffset += bytesWritten; 
     if (self.currentDataOffset == dataLength) { 
      [self.dataWriteQueue removeLastObject]; 
      self.currentDataOffset = 0; 
     } 
    } 
} 
+0

Cảm ơn vì sự hiểu biết, con thiêu thân , có vẻ như tôi đã có cùng một mức độ lẫn lộn như bạn. Cảm ơn một lần nữa! – Patricia

+0

lưu ý rằng byteWritten có thể âm, cho biết lỗi, trong trường hợp có lỗi, mã trên sẽ bị kẹt, vì currentDataOffset không khớp ... chỉnh sửa được đề xuất –

+0

Bạn có thể giúp tôi hiểu cách người nhận biết liệu nó có nhận được một lỗi khác không toàn bộ dữ liệu hoặc dữ liệu? Có lẽ bộ đệm của tôi có dung lượng 2 byte và tôi chỉ có thể viết hai ký tự ASCII cùng một lúc. Làm thế nào người nhận cho biết nếu tôi gửi "làm" hoặc "dope" từ với nó? –

Các vấn đề liên quan