【摘要】 介绍Linux下HTTP服务器搭建,完成网页图片显示,网页视频显示。
任务1: 网页视频监控项目
目的: 使用浏览器访问开发板的USB摄像头图像数据,实时刷新到达视频的效果。
1.HTTP协议: 如何传输数据,让浏览器显示?
2.线程的并发执行: 多个浏览器同时访问摄像头数据。
3.USB摄像头编程: 如果获取摄像头的数据。
1.1 如何显示一张静态的图片
HTTP协议: 文本协议-----报文: 字符串。
HTTP服务器基本的交互的步骤:
1.先创建HTTP服务器
2.使用浏览器(HTTP客户端)访问HTTP服务器:
(1)第一次请求的路径是: / :表示询问: 你需要我做什么?
(2)HTTP服务器收到请求之后,先向HTTP客户端发送应答报文。
再发送需要浏览器处理的数据: 数据类型、数据长度。 :表示分配给浏览器需要做的任务
如果需要浏览器显示一张图片,浏览器在收到任务之后,会解析任务,再次向服务器发送请求:
请求图片(图片的资源路径):
HTTP服务器收到请求之后,先向HTTP客户端发送应答报文。
再发送需要浏览器处理的数据: 数据类型、数据长度。
1.2 采集摄像头数据、显示动态图片
1. 采集摄像头数据: 开一个新的线程
2. 需要将摄像头的数据编码为JPG格式—jpglib只能将RGB数据压缩成JPG格式保存到文件。
需要使用改进的算法,将JPG图像压缩存放到内存里。
3.需要考虑资源共享: 线程互斥锁+条件变量
(1)线程1: 负责采集摄像头的数据,并进行编码压缩jpg图像
(2)线程2(主线程): 负责等待HTTP客户端连接(浏览器),处理与浏览器之间的交互过程。
云服务器: 本身就是一个虚拟电脑。
1.登录: 使用ssh远程登录。
2.买云服务器: 送一个公网IP地址。
3.也可以购买一个域名。www.1234.com
今天的代码基础之上实现:
跨网段网页视频监控。
int on = 1; if(setsockopt(http_server_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { printf("setsockopt(SO_REUSEADDR) 设置错误!\n"); exit(-1); } //这样可以保证: 端口关闭之后,立即可以再次使用 |
1.3 解决TCP服务器退出时,产生退出信号终止进程
signal.h中的宏定义SIG_DFL及SIG_IGN
SIG_DFL,SIG_IGN 分别表示无返回值的函数指针,指针值分别是0和1,
这两个指针值逻辑上讲是实际程序中不可能出现的函数地址值。
SIG_DFL:默认信号处理程序
SIG_IGN:忽略信号的处理程序
/*
往一个已经接收到FIN的套接中写是允许的,接收到的FIN仅仅代表对方不再发送数据。
并不能代表我不能发送数据给对方。
往一个FIN结束的进程中写(write),对方会发送一个RST字段过来,TCP重置。
如果再调用write就会产生SIGPIPE信号
*/
signal(SIGPIPE,SIG_IGN);
1.4 HTTP服务器搭建_显示一静态JPG图片
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define HTTP_SERVER_PORT 1237 /*HTTP服务器端口号*/
int http_server_fd; /*HTTP服务器套接字*/
/*
函数功能: 处理退出的信号
*/
void exit_sighandler(int sig)
{
/*关闭服务器套接字*/
close(http_server_fd);
sleep(2);
//退出进程
exit(1);
}
/*
函数功能: 向HTTP客户端发送文件数据
*/
void HTTPClient_SendFileData(int client_fd,char *type,char *file)
{
int file_fd;
int read_len;
struct stat file_buf;
unsigned char buffer[1024];
file_fd=open(file,O_RDONLY);
if(file_fd<0)
{
printf("%s文件打开失败!\n",file);
return;
}
/*1. 获取文件的状态信息*/
stat(file,&file_buf);
//printf("%d\n",file_buf.st_size);
/*2. 构造报文头*/
sprintf(buffer,"HTTP/1.1 200 OK\r\n" \
"Content-type:%s\r\n" \
"Content-Length:%d\r\n"
"Server: wbyq\r\n" \
"\r\n",type,file_buf.st_size);
read_len=strlen(buffer);
/*2. 发送数据*/
do
{
if(write(client_fd,buffer,read_len)<=0)break;
}while((read_len=read(file_fd,buffer,sizeof(buffer)))>0);
}
/*
函数功能: 处理HTTP客户端的线程
*/
void *pthread_Handler_HTTP_Client(void *dev)
{
int Clientfd;
unsigned char buffer[1024];
unsigned char *p=buffer;
struct pollfd fds;
int poll_state; /*poll函数的状态值*/
int recv_len; /*接收的数据长度*/
if(dev==NULL)
{
pthread_exit(NULL); /*终止线程*/
}
Clientfd=*(int*)dev; /*保存客户端套接字描述符*/
free(dev); /*释放空间*/
/*1. 接收客户端的请求报文*/
fds.fd=Clientfd;
fds.events=POLLIN;
while(1)
{
/*等待数据*/
poll_state=poll(&fds,1,100);
if(poll_state<=0)break; /*数据接收完毕就退出*/
recv_len=read(Clientfd,p,1024);
p+=recv_len;
if(p-buffer>1024)break;
}
//printf("buffer=%s\n",buffer);
/*1. 判断请求的路径*/
if(strstr(buffer,"GET / HTTP/1.1"))
{
HTTPClient_SendFileData(Clientfd,"text/html","index.html");
}
else if(strstr(buffer,"GET /image.jpg HTTP/1.1"))
{
HTTPClient_SendFileData(Clientfd,"image/jpeg","123.jpg");
}
else if(strstr(buffer,"GET /favicon.ico HTTP/1.1"))
{
HTTPClient_SendFileData(Clientfd,"image/x-icon","123.ico");
}
close(Clientfd);
}
/*
HTTP服务器创建:
1. 创建socket套接字
2. 绑定端口号: 服务器创建
3. 设置监听端口的数量: 服务器最大等待连接的客户端总数量
4. 等待客户端连接
*/
int main(int argc,char **argv)
{
/*1. 绑定将要捕获的信号*/
signal(SIGINT,exit_sighandler);
signal(SIGSEGV,exit_sighandler);
/*2. 创建套接字*/
http_server_fd=socket(AF_INET,SOCK_STREAM,0);
if(http_server_fd<0)
{
printf("HTTP服务器:创建套接字创建失败!\n");
return -1;
}
/*3. 绑定端口号*/
struct sockaddr_in server_addr;
memset(&server_addr,0,sizeof(struct sockaddr_in));
server_addr.sin_family=AF_INET; //IPV4
server_addr.sin_port=htons(HTTP_SERVER_PORT); //需要填大端格式的端口号数据
server_addr.sin_addr.s_addr=0;//inet_addr("192.168.18.3");
/*0=inet_addr("0.0.0.0") ---表示本地所有IP地址*/
if(bind(http_server_fd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr_in))!=0)
{
printf("HTTP服务器:绑定端口号失败!\n");
return -2;
}
/*4. 设置监听客户端连接的数量*/
listen(http_server_fd,50);
/*5. 等待客户端连接:阻塞*/
struct sockaddr_in client_addr;
int addrlen=sizeof(struct sockaddr_in);
pthread_t thread_id; /*线程的ID*/
int *client_fd=NULL; /*保存客户端的套接字描述符*/
while(1)
{
client_fd=(int*)malloc(sizeof(int));
if(client_fd==NULL)
{
printf("存放客户端的套接字描述符,空间申请失败!\n");
break;
}
*client_fd=accept(http_server_fd,(struct sockaddr *)&client_addr,&addrlen);
if(*client_fd<0)
{
break;
}
/*6. 创建新的线程*/
if(pthread_create(&thread_id,NULL,pthread_Handler_HTTP_Client,(void*)client_fd)!=0)
{
printf("创建处理HTTP客户端线程失败!\n");
break;
}
}
/*7. 关闭服务器套接字*/
close(http_server_fd);
return 0;
}
1.5 网页视频监控的项目代码_多线程处理
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "yuv_to_jpeg.h"
#define UVC_VIDEO_DEVICE "/dev/video15" /*UVC摄像头设备节点*/
int uvc_video_fd; /*存放摄像头设备节点的文件描述符*/
int video_stop_stat=1; /*视频停止状态: 1表示正常执行,0表示退出*/
unsigned char *video_memaddr_buffer[4]; /*存放的是摄像头映射出来的缓冲区首地址*/
int Image_Width; /*图像的宽度*/
int Image_Height; /*图像的高度*/
unsigned char *jpg_video_buffer=NULL; /*转换之后的JPG数据缓冲区首地址*/
unsigned int jpg_video_size; /*存放当前JPG数据缓冲区的大小*/
pthread_mutex_t mutex; /*互斥锁*/
pthread_cond_t cond; /*条件变量*/
#define HTTP_SERVER_PORT 1235 /*HTTP服务器端口号*/
int http_server_fd; /*HTTP服务器套接字*/
/*
函数功能: 处理退出的信号
*/
void exit_sighandler(int sig)
{
video_stop_stat=0; //让摄像头采集线程自动退出
sleep(2);
/*关闭服务器套接字*/
close(http_server_fd);
//退出进程
exit(1);
}
/*
函数功能: 向HTTP客户端发送文件数据
*/
void HTTPClient_SendFileData(int client_fd,char *type,char *file)
{
int file_fd;
int read_len;
struct stat file_buf;
unsigned char buffer[1024];
file_fd=open(file,O_RDONLY);
if(file_fd<0)
{
printf("%s文件打开失败!\n",file);
return;
}
/*1. 获取文件的状态信息*/
stat(file,&file_buf);
//printf("%d\n",file_buf.st_size);
/*2. 构造报文头*/
sprintf(buffer,"HTTP/1.1 200 OK\r\n" \
"Content-type:%s\r\n" \
"Content-Length:%d\r\n"
"Server: wbyq\r\n" \
"\r\n",type,file_buf.st_size);
read_len=strlen(buffer);
/*2. 发送数据*/
do
{
if(write(client_fd,buffer,read_len)<=0)break;
}while((read_len=read(file_fd,buffer,sizeof(buffer)))>0);
}
/*
函数功能: 发送数据流
*/
void SendVideoData(int Clientfd)
{
int image_size;
unsigned char *image_data;
unsigned char buffer[1024];
/*1. 构造报文头: 回应浏览器请求,并告诉浏览器接下来需要使用长连接*/
sprintf(buffer, "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(Clientfd,buffer,strlen(buffer))<0)
{
return;
}
/*2. 循环发送数据流: JPG图片*/
image_data=malloc(Image_Width*Image_Height*3);
if(image_data==NULL)
{
printf("循环发送数据流缓冲区申请失败!\n");
return;
}
while(video_stop_stat)
{
//阻塞方式等待条件变量,等待成功并上锁
pthread_cond_wait(&cond,&mutex);
image_size=jpg_video_size; //保存图片的大小
memcpy(image_data,jpg_video_buffer,image_size);
//互斥锁解锁
pthread_mutex_unlock(&mutex);
/*2.1 构造报文头: 告诉浏览器发送数据类型和数据的长度*/
sprintf(buffer,"Content-type:%s\r\n" \
"Content-Length:%d\r\n"\
"\r\n","image/jpeg",image_size);
if(write(Clientfd,buffer,strlen(buffer))<0)
{
break;
}
/*2.2 发送实际的数据*/
if(write(Clientfd,image_data,image_size)<0)break;
/*2.3 发送间隔符号*/
sprintf(buffer,"\r\n--" "boundarydonotcross" "\r\n"); //间隔符号
if(write(Clientfd,buffer,strlen(buffer))<0)
{
break;
}
}
free(image_data); //释放空间
}
/*
函数功能: 处理HTTP客户端的线程
*/
void *pthread_Handler_HTTP_Client(void *dev)
{
int Clientfd;
unsigned char buffer[1024];
unsigned char *p=buffer;
struct pollfd fds;
int poll_state; /*poll函数的状态值*/
int recv_len; /*接收的数据长度*/
if(dev==NULL)
{
pthread_exit(NULL); /*终止线程*/
}
Clientfd=*(int*)dev; /*保存客户端套接字描述符*/
free(dev); /*释放空间*/
/*1. 接收客户端的请求报文*/
fds.fd=Clientfd;
fds.events=POLLIN;
while(1)
{
/*等待数据*/
poll_state=poll(&fds,1,100);
if(poll_state<=0)break; /*数据接收完毕就退出*/
recv_len=read(Clientfd,p,1024);
p+=recv_len;
if(p-buffer>1024)break;
}
//printf("buffer=%s\n",buffer);
/*1. 判断请求的路径*/
if(strstr(buffer,"GET / HTTP/1.1"))
{
HTTPClient_SendFileData(Clientfd,"text/html","index.html");
}
else if(strstr(buffer,"GET /?action=stream HTTP/1.1"))
{
SendVideoData(Clientfd); //发送视频流数据
}
else if(strstr(buffer,"GET /favicon.ico HTTP/1.1"))
{
HTTPClient_SendFileData(Clientfd,"image/x-icon","123.ico");
}
close(Clientfd);
}
/*
函数功能: UVC摄像头初始化
返回值: 0表示成功
*/
int UVCvideoInit(void)
{
/*1. 打开摄像头设备*/
uvc_video_fd=open(UVC_VIDEO_DEVICE,O_RDWR);
if(uvc_video_fd<0)
{
printf("%s 摄像头设备打开失败!\n",UVC_VIDEO_DEVICE);
return -1;
}
/*2. 设置摄像头的属性*/
struct v4l2_format format;
memset(&format,0,sizeof(struct v4l2_format));
format.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*表示视频捕获设备*/
format.fmt.pix.width=320; /*预设的宽度*/
format.fmt.pix.height=240; /*预设的高度*/
format.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; /*预设的格式*/
format.fmt.pix.field=V4L2_FIELD_ANY; /*系统自动设置: 帧属性*/
if(ioctl(uvc_video_fd,VIDIOC_S_FMT,&format)) /*设置摄像头的属性*/
{
printf("摄像头格式设置失败!\n");
return -2;
}
Image_Width=format.fmt.pix.width;
Image_Height=format.fmt.pix.height;
printf("摄像头实际输出的图像尺寸:x=%d,y=%d\n",format.fmt.pix.width,format.fmt.pix.height);
if(format.fmt.pix.pixelformat==V4L2_PIX_FMT_YUYV)
{
printf("当前摄像头支持YUV格式图像输出!\n");
}
else
{
printf("当前摄像头不支持YUV格式图像输出!\n");
return -3;
}
/*3. 请求缓冲区: 申请摄像头数据采集的缓冲区*/
struct v4l2_requestbuffers req_buff;
memset(&req_buff,0,sizeof(struct v4l2_requestbuffers));
req_buff.count=4; /*预设要申请4个缓冲区*/
req_buff.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/
req_buff.memory=V4L2_MEMORY_MMAP; /*支持mmap内存映射*/
if(ioctl(uvc_video_fd,VIDIOC_REQBUFS,&req_buff)) /*申请缓冲区*/
{
printf("申请摄像头数据采集的缓冲区失败!\n");
return -4;
}
printf("摄像头缓冲区申请的数量: %d\n",req_buff.count);
/*4. 获取缓冲区的详细信息: 地址,编号*/
struct v4l2_buffer buff_info;
memset(&buff_info,0,sizeof(struct v4l2_buffer));
int i;
for(i=0;i
-
视频监控
+关注
关注
17文章
1708浏览量
64913 -
Linux
+关注
关注
87文章
11229浏览量
208926 -
服务器
+关注
关注
12文章
9021浏览量
85183 -
HTTP
+关注
关注
0文章
501浏览量
31063
发布评论请先 登录
相关推荐
评论