【问题标题】:iOS: How to stream (live sending) data over HTTP?iOS:如何通过 HTTP 流式传输(实时发送)数据?
【发布时间】:2017-12-08 20:48:38
【问题描述】:

我知道,这个主题的标题很笼统,我认为这个问题很容易解决(我在 Android 上很容易做到)。但是在 iOS 上……嗯,这个问题是存在的……

我有一个设备,我想在它上面传输数据(准确地说是音频数据)。我必须使用的协议是 HTTP。实际上,我必须构建并发送一个在其正文中包含一些数据的 HTTP 请求,然后,当我的“生产者”(这里是重新采样/编码音频数据的过程)提供要传输的新数据时,通过它发送它请求正文。对于 HTTP 来说这听起来很奇怪,但它就像是没有边界的 HTTP 多部分(数据只是一个接一个地发送)。

例如,在 Java 中,我使用 URL 和标头初始化 HttpUrlConnection,我使用 /*my connection*/.setFixedLengthStreamingMode(9999999);Content-Length 标头精确到“9999999”(如目标设备文档中所写)。我不打电话给connect()method,但我得到了输出流(getOutputStream()),当我的生产者提供新数据时,我只打电话给/*my output stream*/.write(/*my data*/);然后/*my output stream*/.flush();

所以,我尝试使用 NSURLSession 和低级 API NSInputStreamNSOutputStream。但在每种情况下,我都无法做到:

  • 使用NSURLSession,我调用了uploadTaskWithStreamedRequest:,但回调needNewBodyStream: 只调用一次(或者在新身份验证的情况下调用两次)。所以我只在输入流中发送一个数据包。

  • 使用 NSInputStreamNSOutputStream 连接已被目标设备正确关闭,然后我可以发送任何以输入流结尾的标准 TCP 数据(HTTP 标头除外)。

我认为这是很多人都做过的事情,而且他们是我没有看到的一种方式,不是吗?请通过给我示例(这将是完美的)或建议我必须选择什么 API 或应用特定参数来帮助我?

谢谢!

编辑

我的问题类似于 this,但这是另一种(更短的)解释方式。

编辑 2

这是我使用 NSURLSession 时的代码(如果可能,我更喜欢继续使用它):

NSURLSessionConfiguration* defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSOperationQueue* operationQueue = [NSOperationQueue mainQueue];
NSURLSession* session = [NSURLSession sessionWithConfiguration:defaultConfiguration delegate:self delegateQueue:operationQueue];

NSInputStream* inputStream = [NSInputStream inputStreamWithData:/*data*/];

NSURL* url = /*init from string*/
NSMutableRequest* request : [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
request.HTTPBodyStream = inputStream;
/*Add some headers to request*/

NSURLSessionUploadTask* uploadTask = [session uploadTaskWithStreamedRequest:request];
[uploadTask resume];

确切地说,我没有在 URL 中输入身份验证信息。代表的方法按以下顺序调用:didSendBodyData -> didReceiveChallenge(这是我执行身份验证的地方,它有效,因为我在 401 之前收到,之后什么都没有) -> needNewBodyStream(这是我放置数据的地方在完成处理程序的输入流中)-> didSendBodyData -> didFinishCollectingMetrics -> didCompleteWithError(代码为 -1005:kCFErrorDomainCFNetwork)。奇怪的是数据传输良好(第一个数据包),因为我可以在目标设备中听到一些瞬间的声音。

再次准确地说,这是连接期间的网络流量:

  • 发送 TCP SYN、ECN、CWR
  • Rcv TCP SYN、ACK、ECN
  • 发送 TCP ACK
  • 发送重组 PDU 的 TCP 段(我的 HTTP 请求和数据没有身份验证)(x7,每个后跟一个 ACK​​ 响应)
  • Rcv HTTP 401(因为需要摘要)
  • ... TCP ACK、FIN/ACK、SYN/ECN/CWR 的连续...
  • 发送重组 PDU 的 TCP 段(我的 HTTP 请求和数据 with 身份验证)(x7,每个后跟一个 ACK​​ 响应)

然后(我不知道为什么):

  • Rcv TCP END, ACK
  • 发送 TCP ACK
  • 发送 TCP END、ACK
  • Rcv TCP ACK

【问题讨论】:

  • 您是使用共享的 URLSession 还是创建了新的默认会话或后台会话?在第二种情况下尝试设置sessionConfig.isDiscretionary = falsesessionConfig.networkServiceType 属性
  • @Roran 感谢您的评论,我用一些代码编辑了我的问题,显示了我的 NSURLSession 用法。我试过你说的,但行为没有区别。
  • 你可以试试看这个:link
  • @Roran 谢谢,我读过了,但它是用于“一次性”数据上传,我的问题是连续上传数据。

标签: ios http streaming


【解决方案1】:

好的,伙计们,我已经知道了答案,我认为分享它是个好主意。实际上这很容易:这个技巧根本不明确。回调needNewBodyStream: 被调用一次(或更多,参见文档),我们只需要将NSInputStream 对象与NSOutputStream 绑定。然后,当“生产者”提供新数据时,数据将写入NSOutputStream 的回调中。以前,我的连接被关闭了,因为在needNewBodyStream 调用之后没有任何东西可以发送。

NSURLSessionTaskDelegate 实现的一部分:

-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
    needNewBodyStream:(void (^)(NSInputStream * bodyStream))completionHandler
{
    NSInputStream* inputStream = nil;
    NSOutputStream* outputStream = nil;

    // Bind inputStream and outputStream

    self.inputStream = inputStream;
    self.outputStream = outputStream;
    [self.outputStream setDelegate:/*NSStreamDelegate implementation*/];
    [self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [self.outputStream open];

    completionHandler(self.inputStream);
}

NSStreamDelegate 实现的一部分:

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    switch (eventCode) {
        case NSStreamEventOpenCompleted:
            // to handle...
            break;
        case NSStreamEventHasSpaceAvailable:
            // Producer/Consumer's consumer here, and when data is available:
            [self.outputStream write:/*data*/ maxLength:/*data length*/];                
            break;
        case NSStreamEventErrorOccurred:
            // to handle...
            break;
        case NSStreamEventEndEncountered:
            // to handle...
            break;
        default:
            break;
    }
}

现在您可以通过 HTTP“实时”流式传输,尽情享受吧:)

【讨论】:

    猜你喜欢
    • 2018-03-06
    • 1970-01-01
    • 2010-09-25
    • 1970-01-01
    • 2013-10-17
    • 1970-01-01
    • 1970-01-01
    • 2012-03-30
    • 2015-10-08
    相关资源
    最近更新 更多