Linux下基于HTTP網(wǎng)頁視頻監(jiān)控
1.HTTP簡介
超文本傳輸協(xié)議(Hyper Text Transfer Protocol,HTTP)是一個簡單的請求-響應(yīng)協(xié)議,它通常運行在TCP之上。它指定了客戶端可能發(fā)送給服務(wù)器什么樣的消息以及得到什么樣的響應(yīng)。請求和響應(yīng)消息的頭以ASCII形式給出;而消息內(nèi)容則具有一個類似MIME的格式。這個簡單模型是早期Web成功的有功之臣,因為它使開發(fā)和部署非常地直截了當(dāng)。
HTTP的發(fā)展是由蒂姆·伯納斯-李于1989年在歐洲核子研究組織(CERN)所發(fā)起。HTTP的標(biāo)準(zhǔn)制定由萬維網(wǎng)協(xié)會(World Wide Web Consortium,W3C)和互聯(lián)網(wǎng)工程任務(wù)組(Internet Engineering Task Force,IETF)進(jìn)行協(xié)調(diào),最終發(fā)布了一系列的RFC,其中最著名的是1999年6月公布的 RFC 2616,定義了HTTP協(xié)議中現(xiàn)今廣泛使用的一個版本——HTTP 1.1。
HTTP 是一個基于 TCP/IP 通信協(xié)議來傳遞數(shù)據(jù)( HTML 文件, 圖片文件, 查詢結(jié)果等)。工作于客戶端-服務(wù)端架構(gòu)上,默認(rèn)端口號為 80,但是你也可以改為 8080或其它端口號。HTTP協(xié)議永遠(yuǎn)都是客戶端發(fā)起請求,服務(wù)器回送響應(yīng)。
?HTTP是基于客戶/服務(wù)器模式,且面向連接的。
客戶與服務(wù)器之間的HTTP連接是一種一次性連接,它限制每次連接只處理一個請求,當(dāng)服務(wù)器返回本次請求的應(yīng)答后便立即關(guān)閉連接,下次請求再重新建立連接。當(dāng)然HTTP也可以設(shè)置為長連接,在HTTP / 1.1中,引入了保持活動機(jī)制,其中連接可以重用于多個請求。這樣的持久性連接可以明顯減少請求延遲,因為在發(fā)送第一個請求之后,客戶端不需要重新協(xié)商TCP 3-Way-Handshake連接。
HTTP是一種無狀態(tài)協(xié)議,即服務(wù)器不保留與客戶交易時的任何狀態(tài)。這就大大減輕了服務(wù)器記憶負(fù)擔(dān),從而保持較快的響應(yīng)速度。但也意味著如果后續(xù)處理需要前面的信息則必須重傳。
2.HTTP報文格式
HTTP報文是面向文本的,報文中的每一個字段都是一些ASCII碼串,每個字段的長度是不確定的。
HTTP有兩種報文:請求報文和響應(yīng)報文。
HTTP的請求報文由四部分組成:請求行(request line)、請求頭部(header)、空行和請求數(shù)據(jù)(request data)
??HTTP 響應(yīng)也由四個部分組成,分別是:狀態(tài)行、消息報頭、空行和響應(yīng)正文。
3.HTTP請求方式
HTTP/1.1協(xié)議中共定義了八種方法(有時也叫“動作”),來表明Request-URL指定的資源不同的操作方式
其中:
HTTP1.0定義了三種請求方法: GET, POST 和 HEAD方法。
HTTP1.1新增了五種請求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法
4.HTTP狀態(tài)碼
當(dāng)瀏覽者訪問一個網(wǎng)頁時,瀏覽者的瀏覽器會向網(wǎng)頁所在服務(wù)器發(fā)出請求。當(dāng)瀏覽器接收并顯示網(wǎng)頁前,此網(wǎng)頁所在的服務(wù)器會返回一個包含 HTTP 狀態(tài)碼的信息頭( server header)用以響應(yīng)瀏覽器的請求。
HTTP 狀態(tài)碼的英文為 HTTP Status Code。
下面是常見的 HTTP 狀態(tài)碼:
200 - 請求成功
301 - 資源(網(wǎng)頁等)被永久轉(zhuǎn)移到其它 URL
404 - 請求的資源(網(wǎng)頁等)不存在
500 - 內(nèi)部服務(wù)器錯誤
5.搭建HTTP服務(wù)器
HTTP 協(xié)議底層是 TCP 協(xié)議, HTTP 本身屬于上層協(xié)議, 協(xié)議格式都是以文本格式響應(yīng)。 瀏覽器在訪問 HTTP服務(wù)器時,會發(fā)送請求報文, HTTP 服務(wù)器解析請求報文,根據(jù)解析結(jié)果,給瀏覽器進(jìn)行回應(yīng)。
瀏覽器第一次訪問 HTTP 服務(wù)器時,會請求空路徑, HTTP 服務(wù)器第一次回發(fā)的報文數(shù)據(jù)應(yīng)該是 html 文件。瀏覽器收到 html 文件之后,再根據(jù) html 文件描述符的內(nèi)容與服務(wù)器進(jìn)行后續(xù)交互。后續(xù)服務(wù)端需要根據(jù)瀏覽器請求響應(yīng)對應(yīng)數(shù)據(jù)即可。
5.1 搭建HTTP服務(wù)器響應(yīng)瀏覽器圖片請求
- html文件
- http服務(wù)器
??服務(wù)器采用多線程方式處理客戶端請求,http是屬于短連接狀態(tài),每次連接只處理一個請求,當(dāng)服務(wù)器返回本次請求的應(yīng)答后便立即關(guān)閉連接,下次請求再重新建立連接。
#include
#include /* See NOTES */
#include
#include
#include /* superset of previous */
#include
#include
#include
#include
#include
#include
#include
#define HTTP_PORT 8080 //HTTP服務(wù)器端口號
/*
服務(wù)端響應(yīng)客戶端請求
"HTTP/1.1 200 OK\r\n"
"Content-type:image/jpeg\r\n"
"Content-Length:1234\r\n"
"\r\n"
形參:c_fd --客戶端套接字
type --文件類型
file --要發(fā)送的文件
返回值:0成功,其它失敗
*/
int Http_SendData(int c_fd,const char *type,const char *file)
{
int fd=open(file,O_RDONLY);//打開文件
if(fd<0)return -1;//打開文件失敗
struct stat statbuf;
fstat(fd,&statbuf);
if(statbuf.st_size<=0)
{
close(fd);
return -2;
}
char buff[1024]={0};
snprintf(buff,sizeof(buff),"HTTP/1.1 200 OK\r\n"
"Content-type:%s\r\n"
"Content-Length:%ld\r\n"
"\r\n",type,statbuf.st_size);
if(write(c_fd,buff,strlen(buff))!=strlen(buff))
{
close(fd);
return -3;//發(fā)送數(shù)據(jù)頭失敗
}
/*發(fā)送文件內(nèi)容*/
int size;
while(1)
{
size=read(fd,buff,sizeof(buff));
if(write(c_fd,buff,size)!=size)break;//發(fā)送失敗
if(size!=sizeof(buff))break;//發(fā)送完成
}
close(fd);
return 0;
}
/*線程工作函數(shù)*/
void *pth_work(void *arg)
{
int c_fd=*(int *)arg;
free(arg);
char buff[1024]={0};
int size;
size=read(c_fd,buff,sizeof(buff)-1);
if(size<=0)
{
close(c_fd);
pthread_exit(NULL);
}
buff[size]='\0';
printf("buff=%s\n",buff);
if(strstr(buff,"GET / HTTP/1.1"))//請求網(wǎng)頁文件
{
Http_SendData(c_fd,"text/html","./html/image.html");
}
else if(strstr(buff,"GET /1.bmp HTTP/1.1"))
{
Http_SendData(c_fd,"application/x-bmp","./html/1.bmp");
}
else if(strstr(buff,"GET /favicon.ico HTTP/1.1"))
{
Http_SendData(c_fd,"image/x-icon","./html/wmp.ico");
}
close(c_fd);
pthread_exit(NULL);
}
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
printf("創(chuàng)建網(wǎng)絡(luò)套接字失敗\n");
return 0;
}
/*允許綁定已使用的端口號*/
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
/*綁定端口號*/
struct sockaddr_in s_addr=
{
.sin_family=AF_INET,//IPV4
.sin_port=htons(HTTP_PORT),
.sin_addr.s_addr=INADDR_ANY
};
if(bind(sockfd,(const struct sockaddr *)&s_addr,sizeof(s_addr)))
{
printf("綁定端口號失敗\n");
return 0;
}
/*設(shè)置監(jiān)聽數(shù)量*/
listen(sockfd,100);
/*等待客戶端連接*/
struct sockaddr_in c_addr;
socklen_t len=sizeof(c_addr);
int c_fd;
pthread_t pthid;
int *p=NULL;
while(1)
{
c_fd=accept(sockfd,(struct sockaddr *)&c_addr,&len);
if(c_fd==-1)
{
printf("客戶端連接失敗\n");
continue;
}
printf("%d連接成功,%s:%d\n",c_fd,inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));
p=malloc(4);
*p=c_fd;
pthread_create(&pthid,NULL,pth_work,p);
pthread_detach(pthid);//設(shè)置為分離屬性
}
}
5.2 搭建HTTP服務(wù)器實現(xiàn)攝像頭網(wǎng)頁監(jiān)控
??要實現(xiàn)網(wǎng)頁攝像頭監(jiān)控,就是服務(wù)端采集攝像頭數(shù)據(jù),通過一幀一幀圖片流方式響應(yīng)客戶端,因此客戶端和服務(wù)器直接需要建立長連接,保持活動機(jī)制。
- 長連接響應(yīng)格式
//建立長連接格式
"HTTP/1.0 200 OK\r\n"
"Server: wbyq\r\n"
"Content-Type:multipart/x-mixed-replace;boundary=boundarydonotcross\r\n"
"\r\n"
"--boundarydonotcross\r\n"
while(1)
{
//(1)響應(yīng)報文頭
"Content-type:image/jpeg\r\n"
"Content-Length:666\r\n"
"\r\n"
write(“向瀏覽器發(fā)送報文頭數(shù)據(jù)”);
//(2)發(fā)送主體數(shù)據(jù)
write(“向瀏覽器發(fā)送報圖像主體數(shù)據(jù)”);
//(3)發(fā)送間隔字符串
"\r\n"
"--boundarydonotcross\r\n"
write(“間隔字符串?dāng)?shù)據(jù)”);
}
- 服務(wù)端處理流程
- 攝像頭圖像采集參考:https://blog.csdn.net/weixin_44453694/article/details/126488841
-
攝像頭采集圖像線程
?? 攝像頭采集線程負(fù)責(zé)采集圖像數(shù)據(jù),當(dāng)采集到一幀圖像數(shù)據(jù)后通過廣播喚醒所有的http客戶端處理線程。
void *Camera_work(void *arg)
{
int fd=video_info.video_fd;//攝攝像頭描述符
printf("攝像頭采集線程,fd=%d\n",fd);
struct v4l2_buffer v4l2buf;
memset(&v4l2buf,0,sizeof(v4l2buf));//初始化結(jié)構(gòu)體
v4l2buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//視頻捕獲格式
v4l2buf.memory=V4L2_MEMORY_MMAP;//內(nèi)存映射
int rgb_size=video_info.image_w*video_info.image_h*3;//rgb數(shù)據(jù)大小
char *rgb_buffer=malloc(rgb_size);
char *jpg_buffer=malloc(rgb_size);
char *buff[]={rgb_buffer,jpg_buffer};
int jpeg_buffer_size;
pthread_cleanup_push(pth_clean,(void *)buff);
while(1)
{
/*從采集隊列中取出圖像數(shù)據(jù)*/
if(ioctl(fd,VIDIOC_DQBUF,&v4l2buf))break;//取數(shù)據(jù)失敗
//printf("v4l2buff[%d]=%p\n",v4l2buf.index,video_info.video_buff[v4l2buf.index]);
//將yuv轉(zhuǎn)換為rgb
yuv_to_rgb(video_info.video_buff[v4l2buf.index],rgb_buffer,video_info.image_w,video_info.image_h);
//將rgb數(shù)據(jù)轉(zhuǎn)換為jpg數(shù)據(jù)格式
jpeg_buffer_size=rgb_to_jpeg(video_info.image_w,video_info.image_h,rgb_size,rgb_buffer,jpg_buffer, 80);
/*將數(shù)據(jù)拷貝給其它線程*/
pthread_mutex_lock(&mutex);
jpg_size=jpeg_buffer_size;
memcpy(jpeg_image_buffer,jpg_buffer,jpeg_buffer_size);
pthread_cond_broadcast(&cond);//廣播喚醒所有線程
pthread_mutex_unlock(&mutex);
/*將緩沖區(qū)添加回采集隊列中*/
if(ioctl(fd,VIDIOC_QBUF,&v4l2buf))break;//添加到采集隊列失敗
}
pthread_cleanup_pop(1);
}
-
http客戶端處理線程
??http客戶端線程負(fù)責(zé)處理瀏覽器請求,當(dāng)攝像頭線程采集到一幀數(shù)據(jù)后,http客戶端線程將數(shù)據(jù)下發(fā)給瀏覽器。
void *pth_work(void *arg)
{
int c_fd=*(int *)arg;
free(arg);
char buff[1024]={0};
int size;
size=read(c_fd,buff,sizeof(buff)-1);
if(size<=0)
{
close(c_fd);
pthread_exit(NULL);
}
buff[size]='\0';
printf("buff=%s\n",buff);
if(strstr(buff,"GET / HTTP/1.1"))//請求網(wǎng)頁文件
{
Http_SendData(c_fd,"text/html","./html/image.html");
}
else if(strstr(buff,"GET /1.jpg HTTP/1.1"))
{
Http_Content(c_fd);
}
else if(strstr(buff,"GET /favicon.ico HTTP/1.1"))
{
Http_SendData(c_fd,"image/x-icon","./html/wmp.ico");
}
close(c_fd);
pthread_exit(NULL);
}
-
http建立長連接
??通過使用建立長連接的方式實現(xiàn)攝像頭網(wǎng)頁視頻監(jiān)控,以圖片流的方式完成畫面監(jiān)控。
/*
HTTP長連接處理客戶端請求
"HTTP/1.0 200 OK\r\n"
"Server: wbyq\r\n"
"Content-Type:multipart/x-mixed-replace;boundary=boundarydonotcross\r\n"
"\r\n"
"--boundarydonotcross\r\n"
*/
int Http_Content(int c_fd)
{
char buff[1024]={0};
/*建立長連接*/
snprintf(buff,sizeof(buff),"HTTP/1.0 200 OK\r\n"
"Server: wbyq\r\n"
"Content-Type:multipart/x-mixed-replace;boundary=boundarydonotcross\r\n"
"\r\n"
"--boundarydonotcross\r\n");
if(write(c_fd,buff,strlen(buff))!=strlen(buff))return -1;//發(fā)送報文頭失敗
int jpe_image_size;//保存jpeg圖像大小
char *image_jpeg=malloc(video_info.image_w*video_info.image_h*3);//保存jpeg圖像數(shù)據(jù)
while(1)
{
/*
(1)響應(yīng)報文頭
"Content-type:image/jpeg\r\n"
"Content-Length:666\r\n"
"\r\n"
*/
pthread_mutex_lock(&mutex);//互斥鎖上鎖
pthread_cond_wait(&cond,&mutex);//等待條件變量產(chǎn)生
jpe_image_size=jpg_size;
memcpy(image_jpeg,jpeg_image_buffer,jpe_image_size);
pthread_mutex_unlock(&mutex);//互斥鎖上鎖
snprintf(buff,sizeof(buff), "Content-type:image/jpeg\r\n"
"Content-Length:%d\r\n"
"\r\n",jpe_image_size);
if(write(c_fd,buff,strlen(buff))!=strlen(buff))
{
free(image_jpeg);//釋放空間
return -2;//響應(yīng)報文頭失敗
}
/*發(fā)送jpg圖像數(shù)據(jù)*/
if(write(c_fd,image_jpeg,jpe_image_size)!=jpe_image_size)
{
free(image_jpeg);//釋放空間
return -3;//發(fā)送圖像數(shù)據(jù)失敗
}
/*
(3)發(fā)送間隔字符串
"\r\n"
"--boundarydonotcross\r\n"
*/
strcpy(buff,"\r\n--boundarydonotcross\r\n");
if(write(c_fd,buff,strlen(buff))!=strlen(buff))
{
free(image_jpeg);//釋放空間
break;//發(fā)送間隔符失敗
}
}
return -4;//發(fā)送圖像數(shù)據(jù)失敗
}
- 運行效果
-
視頻監(jiān)控
+關(guān)注
關(guān)注
17文章
1710瀏覽量
64945 -
Linux
+關(guān)注
關(guān)注
87文章
11292瀏覽量
209318 -
HTTP
+關(guān)注
關(guān)注
0文章
504瀏覽量
31194
發(fā)布評論請先 登錄
相關(guān)推薦
評論