Linux下基于HTTP網頁視頻監控
1.HTTP簡介
超文本傳輸協議(Hyper Text Transfer Protocol,HTTP)是一個簡單的請求-響應協議,它通常運行在TCP之上。它指定了客戶端可能發送給服務器什么樣的消息以及得到什么樣的響應。請求和響應消息的頭以ASCII形式給出;而消息內容則具有一個類似MIME的格式。這個簡單模型是早期Web成功的有功之臣,因為它使開發和部署非常地直截了當。
HTTP的發展是由蒂姆·伯納斯-李于1989年在歐洲核子研究組織(CERN)所發起。HTTP的標準制定由萬維網協會(World Wide Web Consortium,W3C)和互聯網工程任務組(Internet Engineering Task Force,IETF)進行協調,最終發布了一系列的RFC,其中最著名的是1999年6月公布的 RFC 2616,定義了HTTP協議中現今廣泛使用的一個版本——HTTP 1.1。
HTTP 是一個基于 TCP/IP 通信協議來傳遞數據( HTML 文件, 圖片文件, 查詢結果等)。工作于客戶端-服務端架構上,默認端口號為 80,但是你也可以改為 8080或其它端口號。HTTP協議永遠都是客戶端發起請求,服務器回送響應。
?HTTP是基于客戶/服務器模式,且面向連接的。
客戶與服務器之間的HTTP連接是一種一次性連接,它限制每次連接只處理一個請求,當服務器返回本次請求的應答后便立即關閉連接,下次請求再重新建立連接。當然HTTP也可以設置為長連接,在HTTP / 1.1中,引入了保持活動機制,其中連接可以重用于多個請求。這樣的持久性連接可以明顯減少請求延遲,因為在發送第一個請求之后,客戶端不需要重新協商TCP 3-Way-Handshake連接。
HTTP是一種無狀態協議,即服務器不保留與客戶交易時的任何狀態。這就大大減輕了服務器記憶負擔,從而保持較快的響應速度。但也意味著如果后續處理需要前面的信息則必須重傳。
2.HTTP報文格式
HTTP報文是面向文本的,報文中的每一個字段都是一些ASCII碼串,每個字段的長度是不確定的。
HTTP有兩種報文:請求報文和響應報文。
HTTP的請求報文由四部分組成:請求行(request line)、請求頭部(header)、空行和請求數據(request data)
??HTTP 響應也由四個部分組成,分別是:狀態行、消息報頭、空行和響應正文。
3.HTTP請求方式
HTTP/1.1協議中共定義了八種方法(有時也叫“動作”),來表明Request-URL指定的資源不同的操作方式
其中:
HTTP1.0定義了三種請求方法: GET, POST 和 HEAD方法。
HTTP1.1新增了五種請求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法
4.HTTP狀態碼
當瀏覽者訪問一個網頁時,瀏覽者的瀏覽器會向網頁所在服務器發出請求。當瀏覽器接收并顯示網頁前,此網頁所在的服務器會返回一個包含 HTTP 狀態碼的信息頭( server header)用以響應瀏覽器的請求。
HTTP 狀態碼的英文為 HTTP Status Code。
下面是常見的 HTTP 狀態碼:
200 - 請求成功
301 - 資源(網頁等)被永久轉移到其它 URL
404 - 請求的資源(網頁等)不存在
500 - 內部服務器錯誤
5.搭建HTTP服務器
HTTP 協議底層是 TCP 協議, HTTP 本身屬于上層協議, 協議格式都是以文本格式響應。 瀏覽器在訪問 HTTP服務器時,會發送請求報文, HTTP 服務器解析請求報文,根據解析結果,給瀏覽器進行回應。
瀏覽器第一次訪問 HTTP 服務器時,會請求空路徑, HTTP 服務器第一次回發的報文數據應該是 html 文件。瀏覽器收到 html 文件之后,再根據 html 文件描述符的內容與服務器進行后續交互。后續服務端需要根據瀏覽器請求響應對應數據即可。
5.1 搭建HTTP服務器響應瀏覽器圖片請求
- html文件
- http服務器
??服務器采用多線程方式處理客戶端請求,http是屬于短連接狀態,每次連接只處理一個請求,當服務器返回本次請求的應答后便立即關閉連接,下次請求再重新建立連接。
#include
#include /* See NOTES */
#include
#include
#include /* superset of previous */
#include
#include
#include
#include
#include
#include
#include
#define HTTP_PORT 8080 //HTTP服務器端口號
/*
服務端響應客戶端請求
"HTTP/1.1 200 OK\r\n"
"Content-type:image/jpeg\r\n"
"Content-Length:1234\r\n"
"\r\n"
形參:c_fd --客戶端套接字
type --文件類型
file --要發送的文件
返回值: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;//發送數據頭失敗
}
/*發送文件內容*/
int size;
while(1)
{
size=read(fd,buff,sizeof(buff));
if(write(c_fd,buff,size)!=size)break;//發送失敗
if(size!=sizeof(buff))break;//發送完成
}
close(fd);
return 0;
}
/*線程工作函數*/
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"))//請求網頁文件
{
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("創建網絡套接字失敗\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;
}
/*設置監聽數量*/
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);//設置為分離屬性
}
}
5.2 搭建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"
while(1)
{
//(1)響應報文頭
"Content-type:image/jpeg\r\n"
"Content-Length:666\r\n"
"\r\n"
write(“向瀏覽器發送報文頭數據”);
//(2)發送主體數據
write(“向瀏覽器發送報圖像主體數據”);
//(3)發送間隔字符串
"\r\n"
"--boundarydonotcross\r\n"
write(“間隔字符串數據”);
}
- 服務端處理流程
- 攝像頭圖像采集參考:https://blog.csdn.net/weixin_44453694/article/details/126488841
-
攝像頭采集圖像線程
?? 攝像頭采集線程負責采集圖像數據,當采集到一幀圖像數據后通過廣播喚醒所有的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));//初始化結構體
v4l2buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//視頻捕獲格式
v4l2buf.memory=V4L2_MEMORY_MMAP;//內存映射
int rgb_size=video_info.image_w*video_info.image_h*3;//rgb數據大小
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)
{
/*從采集隊列中取出圖像數據*/
if(ioctl(fd,VIDIOC_DQBUF,&v4l2buf))break;//取數據失敗
//printf("v4l2buff[%d]=%p\n",v4l2buf.index,video_info.video_buff[v4l2buf.index]);
//將yuv轉換為rgb
yuv_to_rgb(video_info.video_buff[v4l2buf.index],rgb_buffer,video_info.image_w,video_info.image_h);
//將rgb數據轉換為jpg數據格式
jpeg_buffer_size=rgb_to_jpeg(video_info.image_w,video_info.image_h,rgb_size,rgb_buffer,jpg_buffer, 80);
/*將數據拷貝給其它線程*/
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);
/*將緩沖區添加回采集隊列中*/
if(ioctl(fd,VIDIOC_QBUF,&v4l2buf))break;//添加到采集隊列失敗
}
pthread_cleanup_pop(1);
}
-
http客戶端處理線程
??http客戶端線程負責處理瀏覽器請求,當攝像頭線程采集到一幀數據后,http客戶端線程將數據下發給瀏覽器。
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"))//請求網頁文件
{
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建立長連接
??通過使用建立長連接的方式實現攝像頭網頁視頻監控,以圖片流的方式完成畫面監控。
/*
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;//發送報文頭失敗
int jpe_image_size;//保存jpeg圖像大小
char *image_jpeg=malloc(video_info.image_w*video_info.image_h*3);//保存jpeg圖像數據
while(1)
{
/*
(1)響應報文頭
"Content-type:image/jpeg\r\n"
"Content-Length:666\r\n"
"\r\n"
*/
pthread_mutex_lock(&mutex);//互斥鎖上鎖
pthread_cond_wait(&cond,&mutex);//等待條件變量產生
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;//響應報文頭失敗
}
/*發送jpg圖像數據*/
if(write(c_fd,image_jpeg,jpe_image_size)!=jpe_image_size)
{
free(image_jpeg);//釋放空間
return -3;//發送圖像數據失敗
}
/*
(3)發送間隔字符串
"\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;//發送間隔符失敗
}
}
return -4;//發送圖像數據失敗
}
- 運行效果
-
視頻監控
+關注
關注
17文章
1708瀏覽量
64915 -
Linux
+關注
關注
87文章
11230瀏覽量
208935 -
HTTP
+關注
關注
0文章
501瀏覽量
31065
發布評論請先 登錄
相關推薦
評論