之前使用的海康的前端開發包,利用插件進行視頻預覽,但是谷歌下面目前不支持插件,所以只能另尋他路。
平台為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端口