me
me copied to clipboard
学习 rtmp 开发 (Part 9: http chunk and httpflv)
http chunk
Transfer-Encoding: chunked
表示本次http response的body长度不能确定,比如后面我们看到的httpflv正是利用这个特性。
文件下载: 如果是文件下载,比如2G的一个iso,这时候因为文件大小是确定的,所以content-length会直接给出,然后body不断输入bytes,所以chunked和内容的长度无关,只和是否知道长度有关。
app.js
require('net')
.createServer(function (sock) {
sock.on('data', function (data) {
sock.write('HTTP/1.1 200 OK\r\n');
sock.write('Transfer-Encoding: chunked\r\n\r\n');
sock.write('5\r\n');
sock.write('hello\r\n');
sock.write('6\r\n');
sock.write(' world\r\n');
sock.write('0\r\n\r\n');
});
})
.listen(9090);
通过curl和telnet验证:
% curl localhost:9090
hello world%
%
% telnet localhost 9090
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.1
HTTP/1.1 200 OK
Transfer-Encoding: chunked
5
hello
6
world
0
- 注: 这里的5,6是16进制,实际发送0x35,0x36,如果是10个字节,则发送字符a,即0x61
httpflv
httpflv本身并不是rfc, 所以讲的也不是很清楚,根据wireshark观察,需要构建一个完整的flv,即flv 9字节head,然后prev size,然后tag,再是prev size,但是没有规定每次分包的大小,我也是简单写了一下,通过ffplay能够播放,但是flv.js还是有问题,不深究了,简单拉通即可。
#include "flv.h"
#include "flvhttpd.h"
#include <arpa/inet.h>
#include <string.h>
#include <ctype.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#define kBLACK "\x1B[0m"
#define kRED "\x1B[31m"
#define kGREEN "\x1B[32m"
FLV flv;
void error_die(const char *);
void accept_request(void *arg);
static void print_hex(const uint8_t *data, unsigned long len);
int main(void) {
uint16_t port = 3000;
struct sockaddr_in name = {.sin_family = AF_INET, .sin_port = htons(port), .sin_addr.s_addr = htonl(INADDR_ANY)};
int on = 1;
int httpd = socket(PF_INET, SOCK_STREAM, 0);
if (httpd == -1) error_die("socket");
if (setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { error_die("setsocket failed"); }
if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) error_die("bind");
if (listen(httpd, 5) < 0) error_die("listen");
printf("server start at %d\n", port);
int client = -1;
struct sockaddr_in client_name;
socklen_t client_name_len = sizeof(client_name);
pthread_t newthread;
while (true) {
int client = accept(httpd, (struct sockaddr *)&client_name, &client_name_len);
if (client == -1) error_die("accept");
if (pthread_create(&newthread, NULL, (void *)accept_request, (void *)(intptr_t)client) != 0)
perror("pthread_create");
}
close(httpd);
return 0;
}
void error_die(const char *sc) {
perror("error: ");
exit(1);
}
void accept_request(void *arg) {
int client = (intptr_t)arg;
char request[1024], method[10], uri[255];
read(client, request, sizeof(request));
char *p = request;
while (*p++ != ' ') {}
memcpy(method, request, p - request - 1);
while (*p++ != ' ') {}
memcpy(uri, request + strlen(method) + 1, p - request - strlen(method) - 1 - 1);
printf("%s%s %s%s\n", kGREEN, method, kBLACK, uri);
// send header
char head[] = "HTTP/1.1 200 OK\nConnection: Keep-Alive\nContent-Type: video/x-flv\n\
Server: flvhttpd\nTransfer-Encoding: chunked\r\n\r\n";
write(client, head, strlen(head));
if (flv.file == NULL) {
FLV_Open(&flv, "sample.flv");
FLV_Parse(&flv);
printf("parse flv ok\n");
}
char chunkline[64];
uint8_t *buffer = malloc(1024 * 1024 * 10);
uint8_t *prev = malloc(4);
memset(prev, 0x00, 4);
for (int i = 0; i < flv.tagCount; ++i) {
FLVTag *t = flv.tags[i];
if (i == 0) {
// send FLV 9 bytes head
sprintf(chunkline, "%x\r\n", 9);
printf("%s", chunkline);
write(client, chunkline, strlen(chunkline));
fseek(flv.file, 0, SEEK_SET);
fread(buffer, 1, 9, flv.file);
print_hex(buffer, 9);
write(client, buffer, 9);
write(client, "\r\n", 2);
sprintf(chunkline, "%x\r\n", 4);
printf("%s", chunkline);
write(client, chunkline, strlen(chunkline));
print_hex(prev, 4);
write(client, prev, 4);
write(client, "\r\n", 2);
}
// write tag
sprintf(chunkline, "%lx\r\n", t->size);
printf("%s", chunkline);
write(client, chunkline, strlen(chunkline));
fseek(flv.file, t->offset, SEEK_SET);
fread(buffer, 1, t->size, flv.file);
print_hex(buffer, t->size);
write(client, buffer, t->size);
write(client, "\r\n", 2);
// write prev
sprintf(chunkline, "%x\r\n", 4);
printf("%s", chunkline);
write(client, chunkline, strlen(chunkline));
fread(prev, 1, 4, flv.file);
print_hex(prev, 4);
write(client, prev, 4);
write(client, "\r\n", 2);
usleep(1000000 / 30);
}
write(client, "0\r\n\r\n", 5);
close(client);
free(buffer);
}
static void print_hex(const uint8_t *data, unsigned long len) {
if (len <= 32) {
for (int i = 0; i < len; ++i) {
printf("%02x ", data[i]);
}
printf("\n");
} else {
for (int i = 0; i < 24; ++i) {
printf("%02x ", data[i]);
}
printf("...... ");
for (int i = 4; i > 0; --i) {
printf("%02x ", data[len - i]);
}
printf("\n");
}
}