• Linux下基于HTTP网页视频监控


    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 服务器解析请求报文,根据解析结果,给浏览器进行回应。
     &emsp浏览器第一次访问 HTTP 服务器时,会请求空路径, HTTP 服务器第一次回发的报文数据应该是 html 文件。浏览器收到 html 文件之后,再根据 html 文件描述符的内容与服务器进行后续交互。后续服务端需要根据浏览器请求响应对应数据即可。

    5.1 搭建HTTP服务器响应浏览器图片请求

    • html文件
    <! DOCTYPE HTML>
    <html>
    	<head>
    	<title>wbyqtitle>
    	<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
    	head>
    	<body>
    	<center><img src="1.bmp" width="3000px" height="2121px" />
    	center>
    	body>
    html>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 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);//设置为分离属性
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

    在这里插入图片描述

    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(“间隔字符串数据”);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 服务端处理流程
      在这里插入图片描述
        摄像头图像采集参考: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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 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;//发送图像数据失败
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 运行效果
      在这里插入图片描述
  • 相关阅读:
    PerfView专题 (第十五篇): 如何洞察 C# 中的慢速方法
    【Prometheus】mysqld_exporter采集+Grafana出图+AlertManager预警
    【数据开发】数据全栈知识架构,数据(平台、开发、管理、分析)
    eslint 警告处理合集
    2022年最新《谷粒学院开发教程》:6 - 整合SpringCloud
    HTTP1.x协议详解和HTTP2.0
    Vue面试题
    【数据结构与算法】杨辉三角,相同字符的截取以及扑克牌
    101道算法JavaScript描述【二叉树】5
    TCP/IP 协议
  • 原文地址:https://blog.csdn.net/weixin_44453694/article/details/126542022