之前使用的海康的前端开发包,利用插件进行视频预览,但是谷歌下面目前不支持插件,所以只能另寻他路。
平台为Ubuntu+nginx,利用nginx的代理将web通讯转发给webserver,通讯利用websocket通讯。server端以libevent为基础,构建reactor模式服务器,这样可以大量接入连接。前端采用streamedian,这是开源的,他采用init,join两个数据通道。
nginx配置转发如下
location / { proxy_pass http://127.0.0.1:9004; #proxy_pass http://172.16.10.4:49240/ws; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; #proxy_redirect off; #proxy_set_header Host $host; #proxy_set_header X-Real-IP $remote_addr; #proxy_read_timeout 3600s; #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
reactor模型
reactor模型主要是针对libevent的单线程单进程模式,所有的连接都设置成非租塞模式,通过不停地监听数据的写入,然后进行分类处理,监听结构配置如下:
flags = fcntl(rfd->fd, F_GETFL); flags |= O_NONBLOCK; fcntl(rfd->fd, F_SETFL, flags);
配置成非阻塞是因为libevent全部是轮询,效率高,阻塞会影响到整体效率,但会有个问题,导致connect的时候会出现连接不上,需要采用以下方式:
if(ret == -1 && (REACTOR_InProgress || REACTOR_WOULDBLOCK)) { debug("ACT:need check\r\n"); rfd = setfd(base,fd,type_CONNECT,app,arg);; if(rfd != NULL) { return rfd; } }
if( type != type_LISTEN){ evWrite = event_new(rfd->base, rfd->fd, EV_WRITE,_canWrite,rfd); timeout.tv_sec = 5; timeout.tv_usec = 0; flags = event_add(evWrite,&timeout );//&timeout//只要是可写的,write触发了的,就是正常连接 rfd->evWrite = evWrite; }
触发一个写的动作,并加上时间限制为5秒,这样写成功了,就连接成功了,如果返回超时信号,就没有连接成功。
websocket协议的解析
1.发送GET数据包,由client发起连接,其中会包含contrl命令,和sharekey(Sec-WebSocket-Protocol: control),建立INIT通道。
2.接收到包后添加control控制口令,和sharekey发送回client,
以上是websocket的套路,按规矩来就行了,接下来的就是streamedian的套路了
3.client发送cam的ip和port,由于用了掩码加密,需要解密
4.摄像头可以连接后,服务端发送wsp OK的数据包给服务端,同时还需要随机生成一个channel,此处我是用的SHA1加密的客户端和端口。
5.此时client会再次发起连接,此时建立数据JOIN通道。(Sec-WebSocket-Protocol: data),完成连接后发送wspOK。
6.建立连接完成后,client发送RTSP 指令,server收到后透明转发给摄像头,充当中间件的角色,这些交互都在INIT通道完成,最终摄像头发送rtp数据包给server,server通过join通道将rtp包发送给web页面。
rtp包的包头为“$”,接着为0x00,然后是两位数据的包的长度,可以根据包的长度来寻找下一个包的帧头,由于数据包可能会出现粘包,所以接收采用窗口接收的方式的来进行,保证每次接收的数据粘连在窗口的tail上,解析完了之后从head进行数据的释放。
源代码地址:
测试效果
搜狗浏览器,谷歌浏览器(版本 75.0.3770.142(正式版本) (64 位))均可流畅查看。测试时间1个小时。
测试方式:首先运行./wsServer ,他会监听9004端口,之前的nginx配置已经将视频请求信息转发给了9004端口