【问题标题】:iOS: Socket Networking Fundamentals using CFStreamCreatePairWithSocketToHostiOS:使用 CFStreamCreatePairWithSocketToHost 的套接字网络基础
【发布时间】:2012-08-13 19:24:09
【问题描述】:

用例

我正在使用套接字通过CFStreamCreatePairWithSocketToHost() 发送和接收数据,并且我试图弄清楚在发送多个数据(即不仅仅是1个请求)。

问题

目前我可以发送数据并接收响应(即 1 次往返)。但是,在我发送outputStream 中的所有数据后,流将关闭(即接收NSStreamEventEndEncountered)。

问题

那么问题来了,当我想发送多个数据请求时会发生什么?

  • 每次有新的数据对象要发送时,是否都要设置一个新的套接字?
  • 我是否必须重置 outputStream 并发送更多数据。

代码

大部分代码来自Cocoa Streams Documentation

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    _data = [[NSMutableData alloc] init];
    [self initNetworkCommunication];

    [self sendString:@"Hello World!"];
}

- (void)initNetworkCommunication {
    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;

    CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)@"123.456.0.0", 1234, &readStream, &writeStream);

    inputStream = (NSInputStream *)readStream; // ivar
    [inputStream setDelegate:self];
    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [inputStream open];

    outputStream = (NSOutputStream *)writeStream; // ivar
    [outputStream setDelegate:self];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream open];
}

- (void)sendString:(NSString *)string {
    NSData *data = [[NSData alloc] initWithData:[string dataUsingEncoding:NSASCIIStringEncoding]];
    [_data appendData:data];
    [data release];
}

- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
    NSLog(@"stream event %u", streamEvent);

    switch (streamEvent) {
        case NSStreamEventOpenCompleted:
            NSLog(@"Stream opened");
            break;
        case NSStreamEventHasSpaceAvailable: {
            uint8_t *readBytes = (uint8_t *)[_data mutableBytes];
            readBytes += byteIndex; // ivar
            int data_len = [_data length];
            unsigned int len = ((data_len - byteIndex >= 1024) ? 1024 : (data_len - byteIndex));
            uint8_t buf[len];
            (void)memcpy(buf, readBytes, len);
            len = [(NSOutputStream *)theStream write:(const uint8_t *)buf maxLength:len];
            NSLog(@"Sending buffer of len: %d", len);
            byteIndex += len;
            break;
        }
        case NSStreamEventHasBytesAvailable:
            if (theStream == inputStream) {
                uint8_t buffer[1024];
                int len;

                while ([inputStream hasBytesAvailable]) {
                    len = [inputStream read:buffer maxLength:sizeof(buffer)];
                    if (len > 0) {
                        NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];

                        if (nil != output) {
                            NSLog(@"server said: %@", output);
                        }
                    }
                }

                [self sendString:@"Another Test"];
            }
            break;
        case NSStreamEventErrorOccurred:
            NSLog(@"Can not connect to the host!");
            break;
        case NSStreamEventEndEncountered:
            NSLog(@"Closing stream...");
            [theStream close];
            [theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            [theStream release];
            theStream = nil;
            break;
        default:
            NSLog(@"Unknown event");
    }
}

回应:

2012-08-15 08:16:30.896 Sockets[34836:f803] Opened input stream.
2012-08-15 08:16:30.898 Sockets[34836:f803] Opened output stream.
2012-08-15 08:16:30.899 Sockets[34836:f803] Sending buffer of len: 12
2012-08-15 08:16:30.900 Sockets[34836:f803] Sending buffer of len: 0
2012-08-15 08:16:30.901 Sockets[34836:f803] Closing output stream.
2012-08-15 08:16:30.939 Sockets[34836:f803] server said: Hello World!

注意outputStream在我发送数据后关闭。我尝试在[self sendString:@"Another Test"]; 之前重新启动outputStream。我也试过idz的回答。

根据文档,我相信 len: 0 的发送缓冲区 是我的问题。

如果委托接收到一个 NSStreamEventHasSpaceAvailable 事件并且 不向流中写入任何内容,它不会进一步接收 从运行循环到 NSOutputStream 的可用空间事件 对象接收更多字节。发生这种情况时,运行循环是 为空间可用事件重新启动。

但是,文档没有说明在到达流末尾时关闭流。所以我很困惑……

【问题讨论】:

  • 网络流在有人(客户端/服务器)关闭它们之前不会关闭,即使这样,每个方向的流也可以独立关闭(除非一方专门关闭两个流)。根据您的描述,您的问题似乎出在您发布的代码之外。也许发布一些发送代码和一些关于客户端和服务器正在做什么的想法。
  • 我想这就是问题所在。客户端(iOS)正在关闭它们。我已经添加了您要求的代码。我尝试注释掉发布outputStream 的代码,但它似乎从未触发委托方法,即使我添加了更多数据。
  • iOS 不会关闭它们,除非你告诉它。我的猜测是问题出在createOutputStream 中,在NSStreamEventHasBytesAvailable 案例中被调用。您必须发布该代码以获得明确的答案。
  • 已发布。本质上,它与NSStreamEventEndEncountered 代码相反。我知道某些原因导致它关闭,因为我收到了Closing stream...。问题是什么...
  • 我删除了我的答案,因为它没有解决您的问题。您的最新更新显示了真正的问题。尝试写入零长度缓冲区(很可能)会导致问题。这是因为当套接字 readwrite 返回 0 时,它意味着流结束。您可能应该重新构建代码,使其始终尝试写入sendString 中的套接字,并且只使用_data 变量作为积压。

标签: ios sockets network-programming


【解决方案1】:

套接字是连接两个程序的双向流,可能在两台不同的计算机上。它只是将您在一端写入的数据传输到另一端读取。它在数据中不强制执行任何结构,并且对请求或响应一无所知。

CFStreamCreatePairWithSocketToHost API 将单个连接拆分为两个独立的流——一个可以读取,一个可以写入。这是一个很好的接触,底层的套接字 API 只使用一个文件描述符来读取和写入,这可能会让人很困惑。

连接保持打开状态,直到一侧关闭套接字。也有可能只在一个方向上关闭插座。也可以只在一个方向上关闭连接。如果远程关闭它的读取流,您的写入流将被关闭,反之亦然。

每次我有一个新的数据对象要发送时,我是否设置一个新的套接字?

您应该避免这样做。建立新连接需要一些时间,而且连接全速需要更多时间。所以你应该尽可能地重用相同的连接。

我是否必须重置 outputStream 并发送更多数据。

不,这不是必需的,只是发送更多数据。

根据文档,我相信 len: 0 的发送缓冲区是我的 问题。

什么都不写(即长度为 0 的缓冲区)应该不是问题。文档没有说明会发生什么。所以我今天写了我的测试程序,看看会发生什么,什么都不期待。事实证明,写入长度为 0 的缓冲区会关闭输出流。所以这真的是你的问题。我将在Apple Bug Reporter 上提交有关该文档问题的错误,您也应该这样做。

您引用的文档部分是关于不同的。如果您在收到可用空间通知后不写,则在您写完之前不会收到另一个通知。这很有用,因为这样系统就不会浪费 CPU 周期来一遍又一遍地告诉你的代码,如果你没有东西可写,你可以写一些东西。

【讨论】:

  • 我很欣赏基本答案。我仍然对 Sending buffer of len: 0 的问题感到好奇。
  • 我在回答中添加了更多相关信息。
  • 谢谢。我也会提交一张票。我从来没有想过这是一个bug,因为我不愿意去想这样的事情。
  • 我不会将此称为错误,它只是应该记录在案的东西。
猜你喜欢
  • 1970-01-01
  • 2015-11-01
  • 1970-01-01
  • 2011-01-23
  • 1970-01-01
  • 2013-03-18
  • 2017-02-07
  • 2019-02-15
  • 1970-01-01
相关资源
最近更新 更多