《从零开始编写一个直播服务器》 C++ 实现一个最简单的HTTP-FLV流媒体服务器

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

流媒体服务系列文章


文章目录


前言

HTTP FLV通过http传输时延可控制在2秒以内浏览器可基于bilibili开源的flv.js(采用h5 mse技术)开发比起rtsp、rtmp等免插件播放因此被广泛应用于直播场景。


一、http flv

httpflv是什么
FLV (Flash Video) 是 Adobe 公司推出的另一种视频格式是一种在网络上传输的流媒体数据存储容器格式。其格式相对简单轻量不需要很大的媒体头部信息。整个FLV由 The FLV Header, The FLV Body 以及其它 Tag 组成。因此加载速度极快。采用 FLV 格式封装的文件后缀为.flv。而HTTP-FLV 即将流媒体数据封装成 FLV 格式然后通过 HTTP 协议传输给客户端。
在这里插入图片描述

httpflv传输特点
分块传输编码Chunked transfer encoding是超文本传输协议HTTP中的一种数据传输机制允许HTTP由网页服务器发送给客户端应用 通常是网页浏览器的数据可以分成多个部分。分块传输编码只在HTTP协议1.1版本HTTP/1.1中提供。

HTTP协议中有个约定content-length字段http的body部分的长度服务器回复http请求的时候如果有这个字段客户端就接收这个长度的数据然后就认为数据传输完成了如果服务器回复http请求中没有这个字段客户端就一直接收数据直到服务器跟客户端的socket连接断开。
http-flv直播就是利用第二个原理服务器回复客户端请求的时候不加content-length字段或者content-length为-1在回复了http内容之后紧接着发送flv数据客户端就一直接收数据了。

httpflv传输格式
如果一个HTTP消息请求消息或应答消息的Transfer-Encoding消息头的值为chunked那么消息体由数量未定的块组成并以最后一个大小为0的块为结束。
每一个非空的块都以该块包含数据的字节数字节数以十六进制表示开始跟随一个CRLF 回车及换行然后是数据本身最后块CRLF结束。在一些实现中块大小和CRLF之间填充有白空格0x20。
最后一块是单行由块大小0一些可选的填充白空格以及CRLF。最后一块不再包含任何数据但是可以发送可选的尾部包括消息头字段。
消息最后以CRLF结尾。

HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
 
25
This is the data in the first chunk
 
1C
and this is the second one
 
3
con
 
8
sequence
 
0

httpflv优缺点
优点
HTTP-FLV 依靠 MIME 的特性根据协议中的 Content-Type 来选择相应的程序去处理相应的内容使得流媒体可以通过 HTTP 传输。相较于 RTMP 协议HTTP-FLV 能够较好的穿透防火墙它是基于 HTTP/80 传输有效避免被防火墙拦截。除此之外它可以通过 HTTP 302 跳转灵活调度/负载均衡支持使用 HTTPS 加密传输也能够兼容支持 AndroidiOS 的移动端。

缺点
由于HTTP-FLV的传输特性会让流媒体资源缓存在本地客户端在保密性方面不够好。因为网络流量较大它也不适合做拉流协议。

二、使用步骤

服务器代码

#include <stdint.h>
#include <iostream>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <cstring>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cassert>
#include <string>
#include <iostream>
#include <memory>
#include <functional>
#include <thread>

#define MAXCONN 1024

int main() {
	int port = 8888;
	const char* filename = "/home/Mycode/data/test.flv";
	printf("httpflvServer http://10.20.39.168:%d/test.flv \n", port);

	int serverFd = -1;
	struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(port);


	serverFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (serverFd < 0) {
		printf("socket create error\n");
		return -1;
	}
	printf("socket create sucess %d",serverFd);
	auto ret = bind(serverFd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if (ret < 0) {
		fprintf(stderr, "bind socket error %s errno: %d\n", strerror(errno), errno);
		printf("socket bind error\n");
		return -1;
	}

	if (listen(serverFd, MAXCONN) < 0) {
		printf("socket listen error\n");
		return -1;
	}

	constexpr char http_headers[] = \
		"HTTP/1.1 200 OK\r\n" \
		"Access-Control-Allow-Origin: * \r\n" \
		"Content-Type: video/x-flv\r\n" \
		"Content-Length: -1\r\n" \
		"Connection: Keep-Alive\r\n" \
		"Expires: -1\r\n" \
		"Pragma: no-cache\r\n" \
		"\r\n"
		;
	int http_headers_len = strlen(http_headers);

/*	
constexpr char http_headers[] = \
"HTTP/1.1 200 OK\r\n" \
"Access-Control-Allow-Origin: * \r\n" \
"Cache-Control: no-cache\r\n" \
"Content-Type: video/x-flv\r\n" \
"Connection: close\r\n" \
"Expires: -1\r\n" \       //设置资源的有效期来控制http的缓存
"Pragma: no-cache\r\n" \  //用于客户端发送的请求中。客户端会要求所有的中间服务器不返回缓存的资源
"\r\n"
;
*/

	while (true)
	{
		printf("等待新连接...\n");
		int len = sizeof(struct sockaddr);
		struct sockaddr_in accept_addr;
		int clientFd = accept(serverFd, (struct sockaddr*)&accept_addr, (socklen_t*)&len);

		if (clientFd < 0) {
			printf("accept connection error \n");
			break;
		}
		printf("发现新连接 clientFd=%d \n", clientFd);
		unsigned char buf[5000];
		char bufRecv[2000] = { 0 };

		FILE* fp;
		fp = fopen(filename, "rb");
		if (!fp) {
			printf("fopen %s fail!\n", filename);
		}
		else {
			int times = 0;
			while (true) {
				times++;
				if (times == 1) {
					int bufRecvSize = recv(clientFd, bufRecv, 2000, 0);
					printf("bufRecvSize=%d,bufRecv=%s \n", bufRecvSize, bufRecv);
					send(clientFd, http_headers, http_headers_len, 0);
				}
				else {
					int bufLen = fread(buf, 1, sizeof(buf), fp);
					int ret = send(clientFd, (char*)buf, bufLen, 0);
					if (ret <= 0) {
						break;
					}
					else {
						//printf("send bufLen=%d,ret=%d \n", bufLen, ret);
					}
				}
			}
		}

		if (fp) {
			fclose(fp);
		}
		close(clientFd);
		printf("关闭连接 clientFd=%d \n", clientFd);
	}

	close(serverFd);
	return 0;
}

/*
g++ main.cpp  -o httpflv.server -std=c++11 -lpthread
*/

运行与用VLC播放

./httpflvServer 
httpflvServer http://ip:8888/test.flv 
socket create sucess 3
等待新连接...

播放地址为http://ipaddr:8888/test.flv
在这里插入图片描述
VLC播放
1、申请httpflv视频流
2、收到http封装的flv数据消息
3、vlc进行解码播放
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


总结

通过本文的学习你应该对http-flv视频流有了一定的认识希望对你后面的学习有所帮助。

授之以鱼不如授之以渔

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: c++服务器
返回列表

上一篇:Linux时钟配置

下一篇:Base64

“《从零开始编写一个直播服务器》 C++ 实现一个最简单的HTTP-FLV流媒体服务器” 的相关文章