如何使用 nghttp2 发送多块数据?

Posted

技术标签:

【中文标题】如何使用 nghttp2 发送多块数据?【英文标题】:How to send multi chunk data using nghttp2? 【发布时间】:2018-01-19 21:18:42 【问题描述】:

我正在使用 nghttp2 库来格式化多部分帧以与 Alexa 通信。我目前能够通过多部分消息获得音频响应。但是,目前只能发送最大 16KB 的数据,我想对我记录的数据进行流式传输,整体上可以大于 16KB。

谁能帮助我使用 nghttp2 将块中的音频数据发送到 AVS?

期待回复,请帮忙。

谢谢。

此外,我正在从代码中添加参考函数。请忽略名称约定和其他逻辑,因为我只是在尝试这个 ruffly 以发送超过 16KB 大小的数据。

    //Callback function for the data to be send to the server
ssize_t data_prd_read_callback_1(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length,uint32_t *data_flags, nghttp2_data_source *source, void *user_data)

    //uint8_t send_data[8000]; 
    char send_data[] = "\r\n\r\n--_____FINISH_HERE__________\r\nContent-Disposition: form-data; name=\"metadata\"\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n\"context\":[\"header\": \"namespace\": \"SpeechSynthesizer\",\"name\": \"SpeechState\",\"payload\": \"token\":\"\",\"offsetInMilliseconds\":0,\"playerActivity\":\"IDLE\"],\"event\":\"header\":\"namespace\":\"SpeechRecognizer\",\"name\":\"Recognize\",\"messageId\":\"MID-123456\",\"dialogRequestId\":\"DRID-123456\",\"payload\":\"profile\":\"CLOSE_TALK\",\"format\":\"AUDIO_L16_RATE_16000_CHANNELS_1\"\r\n\r\n--_____FINISH_HERE__________\r\nContent-Disposition: form-data; name=\"audio\"\r\nContent-Type: application/octet-stream\r\n\r\n";

    int fd = source->fd;
    uint8_t *audio_data;
    uint8_t *final_data_end, *temp_data;
    audio_data = malloc(AUDIO_FILE_SIZE);
    memset(audio_data, 0, AUDIO_FILE_SIZE);
    final_data_end = malloc(MAX_SIZE_TO_SEND);
    temp_data = final_data_end;
    memset(final_data_end, 0, MAX_SIZE_TO_SEND);
    int r;
    r = read(fd, audio_data, AUDIO_FILE_SIZE);
    if ( r == -1)
    
        printf("JOSHI error while reading audio file\n");
    
    int len, i;
    len = strlen(send_data);
    memcpy(final_data_end, send_data, strlen(send_data));
    final_data_end += strlen(send_data);
    memcpy(final_data_end, audio_data, AUDIO_FILE_SIZE);
    final_data_end += AUDIO_FILE_SIZE;
    memcpy(final_data_end, "\r\n--_____FINISH_HERE__________--", 32);
    memcpy(buf, temp_data, (strlen(send_data) + AUDIO_FILE_SIZE + 32));
    return (strlen(send_data) + AUDIO_FILE_SIZE + 32);



static int send_request(struct connection *conn, struct request *req) 
    nghttp2_nv nva[] =  
                MAKE_NV_LL(":method", "POST"),
                MAKE_NV_L(":scheme", "https"),
                MAKE_NV_LL(":path", "/v20160207/events" ),
        MAKE_NV_LL("authorization", "Bearer "ACCESS_TOKEN""),
        MAKE_NV_LL("content-type", "multipart/form-data; boundary=_____FINISH_HERE__________");
    int rv;

    nghttp2_data_provider data_prd;
    int file_descriptor;
    file_descriptor = open ("./audio.raw", O_RDONLY);
    if (file_descriptor == -1)
    
        printf("error while reading the audio file\n");
    
    data_prd.source.fd = file_descriptor;   // set the file descriptor 
    data_prd.read_callback = data_prd_read_callback_1;

    rv = nghttp2_submit_request(conn->ngh2, NULL, nva, ARRLEN(nva), &data_prd, req);
    temp_stream_id = rv;
    if (rv < 0) 
        fprintf(stderr, "In second error: (nghttp2_submit_requset) %s\n",nghttp2_strerror(rv));
        return -1; 
       
    return 0;

我正在使用“nghttp2_session_send”通过 send_callback 函数进行发送。 每当我尝试使用超过 16KB 大小的文件时,AVS 都会回复错误“没有状态为 400 的多部分数据”。如果整个数据小于 16KB,则 alexa 将在响应中附加音频数据。

【问题讨论】:

显示您的代码。你尝试了什么? 是的,我已经附上了相关代码。 【参考方案1】:

你需要指明你没有写完的 nghttp。 写在tutorial:

static ssize_t file_read_callback(nghttp2_session *session _U_,
                                  int32_t stream_id _U_, uint8_t *buf,
                                  size_t length, uint32_t *data_flags,
                                  nghttp2_data_source *source,
                                  void *user_data _U_) 
  int fd = source->fd;
  ssize_t r;
  while ((r = read(fd, buf, length)) == -1 && errno == EINTR)
    ;
  if (r == -1) 
    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
  
  if (r == 0) 
    *data_flags |= NGHTTP2_DATA_FLAG_EOF;
  
  return r;

您可以看到,当没有更多数据要读取时,他们将 *data_flags 设置为 NGHTTP2_DATA_FLAG_EOF。 因此,如果您有几个 MB 文件并且只有 16k 缓冲区,那么您必须保留一些偏移量,并且在每次调用 data_prd_read_callback_1 时写入最大 16k,然后在最后一次迭代中写入其余部分(比如 10k 或剩下的任何内容)。 我是这样用的:

ssize_t Message::Http2DataLoader( nghttp2_session *sess, int32_t sId, uint8_t *buf, size_t bufLen, uint32_t *dataFlags, nghttp2_data_source *src, void *hint )

   ssize_t ret = 0;
   Message* theRes = reinterpret_cast< Message* >( src->ptr );

   if ( bufLen <= theRes->getDataLen() - theRes->getWritten() )
   
      std::memcpy( buf, theRes->getData() + theRes->getWritten(), bufLen );
      ret = bufLen;
   
   else
   
      std::memcpy( buf, theRes->getData() + theRes->getWritten(), theRes->getDataLen() - theRes->getWritten() );
      ret = theRes->getDataLen() - theRes->getWritten();
   

   theRes->addWritten( ret );

   if( theRes->AllDataConsumed() )
   
      *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
   

   return ret;

Message 类是一个容器,它可以包含主体数据或多部分,所有多部分在添加时将其数据写入内部消息缓冲区,然后在数据加载器回调中我只是将其写入 16k 块(或者更好地说在 bufLen 块)。我使用 addWrittengetWritten 方法跟踪我在缓冲区中的位置。比赛由 AllDataConsumed 处理。 如果您正在从文件中读取音频多部分,那么您需要类似于教程中的 file_read_callback 之类的东西。如果你将它封装到一些 Message 类中,你可以添加一些 json multipart 和 audio multipart 并修改 dataLoader 回调以首先写入 json 部分然后开始读取音频文件并添加写入它以 16k(bufLen) 块的形式 buf

nghttp2_session_send 将调用您的 data_prd_read_callback_1,直到您写入所有数据或发生错误。这意味着您可以根据需要处理 JSON 写入缓冲区和音频文件写入缓冲区。如果一切顺利,它只会调用您的回调,直到您将 *data_flags 设置为 NGHTTP2_DATA_FLAG_EOF

希望对你有所帮助。

【讨论】:

以上是关于如何使用 nghttp2 发送多块数据?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 nghttp2 解决“空响应”错误?

如何优雅地停止使用 nghttp2 asio 构建的 HTTP2 服务器

nghttp2 多部分 POST 消息

如何在 Windows x64 上使用 openSSL 和 nghttp2 编译 cURL?

NgHttp2 调用请求数据处理程序两次,针对一个请求

nghttp2如何建立下载通道? (c/c++) (AVS)