Raspberry Pi construye un servidor http para realizar una cámara de vigilancia web

Tabla de contenido


prefacio

Sobre la base de la implementación de yolov5lite en Raspberry Pi, cree un servidor http y agregue la función de monitoreo de la página web


1. Protocolo HTTP

Http (protocolo de transferencia de hipertexto) es el protocolo de transferencia de hipertexto, que puede realizar la función del navegador para cargar archivos locales. Este artículo realiza la función sobre la base de la versión http1.0.

1. Solicitud del cliente

El formato de la solicitud del cliente generalmente consta de: línea de solicitud, encabezado de solicitud, línea en blanco y datos de la solicitud. El formato específico es el siguiente:

Línea de solicitud:         método de solicitud | espacio | URL | espacio | versión del protocolo | retorno de carro | salto de línea |

Encabezado de solicitud:         nombre de campo | : | valor | retorno de carro | avance de línea |

                       ........

                       nombre de campo | : | valor | retorno de carro | avance de línea |

Línea vacía:             retorno de carro | salto de línea |

Solicitar datos:      xxxxxxxx

 2. Respuesta del servidor

El formato de la respuesta del servidor generalmente consta de: línea de estado, encabezado del mensaje, línea en blanco y cuerpo de la respuesta. El formato específico es el siguiente:

Línea de estado:         versión del protocolo | espacio | código de respuesta | espacio | descripción del código | retorno de carro | salto de línea |

Encabezado:         nombre de campo | : | valor | retorno de carro | avance de línea |

                       ........

                       nombre de campo | : | valor | retorno de carro | avance de línea |

Línea vacía:             retorno de carro | salto de línea |

Cuerpo de respuesta:      xxxxxxxx

3. Códigos de respuesta comunes y descripciones correspondientes

código de respuesta Código Descripción
El contenido de la solicitud existe en el servidor y puede responder al cliente 200 DE ACUERDO
La solicitud del cliente es anormal, hay un problema con el método 501 MÉTODO NO APLICADO
El contenido de la solicitud no existe 404 EXTRAVIADO
Hay un problema con el formato de la solicitud enviada por el cliente 400 SOLICITUD INCORRECTA

2. Construcción del servidor

1. Idea

El modo de funcionamiento del protocolo http es que el cliente envía una solicitud, el servidor responde a la solicitud y el cliente es un navegador, por lo que solo necesita analizar la solicitud enviada por el cliente y responder. entorno c++, el servidor utiliza el lenguaje c/c++.

2. Obtenga el encabezado de la solicitud

Reanude la conexión del socket, lea el mensaje enviado por el cliente y analícelo

/*
作用:获取请求行
形参:套接字id,读取内容缓存buff
返回值:-1表示出错,0表示读取一个空行, >0表示成功读取一行
*/
int HTTP::getline(int socketid, string &buff)
{
	while(true)//遇到换行说明一行结束
	{
		char ch = '\0';
		int len = read(socketid, &ch, 1);

		if(len == 1)//正常读取
		{
			if(ch == '\r') 			continue;
			else if (ch == '\n') 	break;

			buff += ch;//string添加
		}
		else if (len == -1)//读取出错
		{
			perror("read error");
			return -1;
		}
		else if (len == 0)//无数据
		{
			std::cerr<<"关闭客户端"<<endl;
			return 0;
		}
	}
	
	return buff.size();
}

/*
作用:获取请求头,遇到两次回车换行代表请求头结束
形参:套接字id, 头部内容缓存
返回值:0表示成功, -1表示错误
*/
int HTTP::gethead(int socketid, string &header)
{
	int len = 0;
	do
	{
		header.clear();
		len = getline(socketid, header);
		cout<<"head: "<<header<<endl;
	}while(len > 0);
	cout<<endl;
	return len;
}

 3. Analice el encabezado para obtener la URL y la solicitud

De acuerdo con el formato del encabezado de la solicitud, primero lea una línea de la línea de la solicitud, use espacios para dividir la cadena y obtenga la URL y la solicitud. Si el formato de la URL es incorrecto, como url=index.htm?xxxxxxxxx, solo ¿buscar? contenido anterior

*
作用:解析请求行内容
形参:请求行内容buff,请求方法缓存request,url路径缓存
返回值:解析成功返回true,否则false
*/
bool HTTP::get_request_url(string buff, string &request, string &url)
{
	if(buff == "")
		return false;

	std::vector<string> strlist;
	strlist.clear();
	buff += " ";
	size_t pos = buff.find(' ');

	while(pos != buff.npos)
	{
		strlist.push_back(buff.substr(0, pos));
		buff = buff.substr(pos+1, buff.size());
		pos = buff.find(' ');
	}

	request = strlist[0];
	url = strlist[1];
	cout<<"request: "<<request<<"\turl:"<<url<<endl;
	return true;
}

/*
作用:获取真正的url并定位到本地文件
参数: url缓存
返回值:返回url文件大小
*/
long int HTTP::get_realurl(string &url, int &status)
{
	url = url.substr(0, url.find('?'));

	struct stat info;
	if(stat((work_dir+url).c_str(), &info) == -1)//文件不存在或者出错,响应404
	{
		cerr<<"stat "<<(work_dir+url)<<" failed, reason:"<<strerror(errno)<<endl;
	}
	else
	{
		if(S_ISDIR(info.st_mode))//如果是目录
		{
			url = work_dir + "/200.html";
		}
		else
		{
			url = work_dir + url;

		}
		status = 200;
		return info.st_size;
	}

	url = work_dir + "/404.html";
	stat(url.c_str(), &info);
	status = 404;
	return info.st_size;
}

4. Respuesta del servidor 

Responda de acuerdo con el método de solicitud y la URL. Dado que en este artículo solo se usa el método GET, solo es necesario resolver GET y non-GET.

/*
作用:响应200 and 404
参数:套接字id, url路径, 响应代号status
*/
void HTTP::do_response(int socketid, std::string url, int status)
{
	cout<<"==============================do_response========================"<<endl;
	int size = get_realurl(url, status);
	cout<<"status: "<<status<<endl;
	cout<<"url: "<<url.c_str()<<endl;
	cout<<"size: "<<size<<endl;
	string header;
	gethead(socketid, header);
	if(url.find("jpg") != url.npos || (url.find("png")) != url.npos || (url.find("ico")) != url.npos)
		header = img_header;
	else
		header = text_header;
	if(status == 200 )//url文件存在
	{
		header = status_200 + header + to_string(size) + "\r\n\r\n";
	}
	else if(status == 404)//文件不存在
	{
		header = status_404 + header + to_string(size) + "\r\n\r\n";;
	}
	cout<<"header: "<<header<<endl;
	//1.发送头部
	int len = write(socketid, header.c_str(), header.size());
	if(len < 0)
	{
		cerr<<"socket write error"<<endl;
		return;
	}
	//2.发送html文件
	FILE* urlfd = fopen(url.c_str(), "rb");
	do
	{
		char buff[1024] = {0};
		len = fread(buff, 1, 1024, urlfd);
		cout<<buff;
		len = write(socketid, buff, len);

		if(len < 0)
		{
			cerr<<"socket write error"<<endl;
			return;
		}
	}while(!feof(urlfd));
	fclose(urlfd);
}

/*
作用:响应400 and 501
参数:套接字id, 响应代号status
*/
void HTTP::do_response(int socketid, int status)
{
	cout<<"==============================do_response========================"<<endl;
	struct stat info;
	string header, url;
	gethead(socketid, header);
	if(status == 400 )//url文件存在
	{
		url = work_dir + "/400.html";
		stat(url.c_str(), &info);
		header = status_400 + text_header + to_string(info.st_size) + "\r\n\r\n";
	}
	else if(status == 501)//文件不存在
	{
		url = work_dir + "/501.html";
		stat(url.c_str(), &info);
		header = status_501 + text_header + to_string(info.st_size) + "\r\n\r\n";;
	}
	cout<<"header: "<<header<<endl;
	//1.发送头部
	int len = write(socketid, header.c_str(), header.size());
	if(len < 0)
	{
		cerr<<"socket write error"<<endl;
		return;
	}

	//2.发送html文件
	FILE* urlfd = fopen(url.c_str(), "rb");
	do
	{
		char buff[1024] = {0};
		len = fread(buff, 1, 1024, urlfd);
		cout<<buff;
		len = write(socketid, buff, len);

		if(len < 0)
		{
			cerr<<"socket write error"<<endl;
			return;
		}
	}while(!feof(urlfd));
	fclose(urlfd);
}

/*
作用:处理客户端请求
参数:套接字id
*/
void HTTP::do_request(int socketid)
{
	string buff, request, url;
	//1.读取请求行并解析方法
	int len = getline(socketid, buff);
	if(len > 0)//读取正常
	{
		
		if(!get_request_url(buff, request, url))//解析错误
			return;
		if(request == "GET")
		{
			int status = 0;
			do_response(socketid, url, status);
		}
		else//非GET请求,读取http头部,并响应客户端501 Mehtod Not Implemented
		{

			do_response(socketid, 501);
		}
	}
	else//读取异常
	{
		do_response(socketid, 400);
	}
}

 5.http.h

#ifndef __HTTP_H
#define __HTTP_H

#include <iostream>
#include "tcp.h"
#include "http.h"
#include "sys/stat.h"
#include <errno.h>
#include <string>
#include <vector>

class HTTP
{
	private:
		const std::string status_200 = "HTTP/1.0 200 OK\r\n";
		const std::string status_501 = "HTTP/1.0 501 METHOD NOT IMPLEMENTED\r\n";
		const std::string status_404 = "HTTP/1.0 404 NOT FOUND\r\n";
		const std::string status_400 = "HTTP/1.0 400 BAD REQUEST\r\n";
		const std::string text_header = "Server: Run Server\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: ";
		const std::string img_header = "Server: Run Server\r\nContent-Type: image/jpeg\r\nConnection: close\r\nContent-Length: ";
		const std::string work_dir = "/home/sy/HTTPCPP/html_docs";
    public:
		int getline(int socketid, std::string &buff);
		int gethead(int socketid, std::string &header);
		bool get_request_url(std::string buff, std::string &request,std::string &url);
		long int get_realurl(std::string &url, int &status);
		void do_request(int socketid);
		void do_response(int socketid, std::string url, int status);
		void do_response(int socketid, int status);
};

#endif

6. tcp.cpp y tcp.h 

#include "tcp.h"

//domain 指定使用何种的地址类型
//PF_INET/AF_INET  Ipv4 网络协议
//PF_INET6/AF_INET6  Ipv6 网络协议
int TCP::Socket(int domain,int type,int protocol)
{
	int socketid = socket(domain,type,protocol);
	if(socketid == -1)
	{
		perror("socket");
		exit(1);
	}
	return socketid;
}

int TCP::Bind(int sockfd,struct sockaddr * my_addr,int addrlen)
{
	int val = bind(sockfd,my_addr,addrlen);
	if(val == -1)
	{
		perror("bind");
		exit(0);
	}
	return val;
}

int TCP::Listen(int s,int backlog)
{
	int val = listen(s,backlog);
	if(val == -1)
	{
		perror("listen");
		exit(0);
	}
	return val;
}

int TCP::Accept(int s,struct sockaddr * addr,socklen_t * addrlen)
{
	int newfd = accept(s,addr,addrlen);
	if(newfd < 0)
	{
		perror("accept");
		exit(0);
	}
	return newfd;
}

int TCP::Connect(int s,struct sockaddr * addr,socklen_t addrlen)
{
	int newfd = connect(s, addr, addrlen);
	if(newfd < 0)
	{
		perror("connet");
		exit(0);
	}
	return newfd;
}

#ifndef TCP_H
#define TCP_H

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <fcntl.h>


class TCP{
public:
		int Socket(int domain,int type,int protocol);
		int Bind(int sockfd,struct sockaddr * my_addr,int addrlen);
		int Listen(int s,int backlog);
		int Accept(int s,struct sockaddr * addr,socklen_t * addrlen);
		int Connect(int s,struct sockaddr * addr,socklen_t addrlen);
};

#endif

 3. Implementación del programa principal

El Raspberry Pi construye el servidor de una manera multiproceso. El programa principal es responsable del reconocimiento facial y guarda imágenes en formato jpg. Se crean subprocesos para transmitir imágenes y archivos html, y se establecen banderas de variables globales. Después de que el archivo de imagen es escrito, el hilo puede enviar imágenes, pero no puede escribir al enviar imágenes.

#include <iostream>
#include <string>
#include <ctime>

#include <MNN/MNNDefine.h>
#include <MNN/MNNForwardType.h>
#include <MNN/Interpreter.hpp>
#include <opencv2/opencv.hpp>

#include "Yolo.h"
#include "base64.h"
#include "tcp.h"
#include "http.h"
#include <pthread.h>

bool sendok = false;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void show_shape(std::vector<int> shape)
{
    std::cout<<shape[0]<<" "<<shape[1]<<" "<<shape[2]<<" "<<shape[3]<<" "<<shape[4]<<" "<<std::endl;
    
}

void scale_coords(std::vector<BoxInfo> &boxes, int w_from, int h_from, int w_to, int h_to)
{
    float w_ratio = float(w_to)/float(w_from);
    float h_ratio = float(h_to)/float(h_from);
    
    
    for(auto &box: boxes)
    {
        box.x1 *= w_ratio;
        box.x2 *= w_ratio;
        box.y1 *= h_ratio;
        box.y2 *= h_ratio;
    }
    return ;
}

cv::Mat draw_box(cv::Mat & cv_mat, std::vector<BoxInfo> &boxes)
{
    int CNUM = 80;
    static const char* class_names[] = {
        "face", "face_mask"
    };
    cv::RNG rng(0xFFFFFFFF);
	cv::Scalar_<int> randColor[CNUM];
	for (int i = 0; i < CNUM; i++)
		rng.fill(randColor[i], cv::RNG::UNIFORM, 0, 256);
    
    for(auto box : boxes)
    {
        int width = box.x2-box.x1;
        int height = box.y2-box.y1;
        char text[256];
        cv::Point p = cv::Point(box.x1, box.y1-5);
        cv::Rect rect = cv::Rect(box.x1, box.y1, width, height);
        cv::rectangle(cv_mat, rect, cv::Scalar(0, 0, 255), 2);
        sprintf(text, "%s %.1f%%", class_names[box.label], box.score * 100);
        cv::putText(cv_mat, text, p, cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
    }
    return cv_mat;
}

void* pthread_listen(void* arg)
{
	TCP tcp;
	HTTP http;

	int socketid = tcp.Socket(AF_INET, SOCK_STREAM, 0);

	struct sockaddr_in httpserver;
	httpserver.sin_family = AF_INET;
	httpserver.sin_port = htons(80);
	httpserver.sin_addr.s_addr = htonl(INADDR_ANY);
	tcp.Bind(socketid, (struct sockaddr *)&httpserver, sizeof(struct sockaddr));
	tcp.Listen(socketid, 100);

	while (true)
	{
		struct sockaddr_in client;
		socklen_t clientlen = sizeof(client);
		char client_ip[64] = {0};

		
		int client_sockfd = tcp.Accept(socketid, (struct sockaddr *)&client, &clientlen);
		cout << "========================================================" << endl;
		cout << "client ip: " << inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip)) << "\tport : " << ntohs(client.sin_port) << endl;
		while(!sendok);
		http.do_request(client_sockfd);
		sendok = false;
		close(client_sockfd);
	}
	close(socketid);
	pthread_exit(NULL);
}

int main(int argc, char* argv[])
{
	Base64 base64;
    std::string model_name = "weights/yolov5s.mnn";

    int num_classes=2;
    std::vector<YoloLayerData> yolov5s_layers{
			{"461",    32, {
   
   {116, 90}, {156, 198}, {373, 326}}},	
            {"403",    16, {
   
   {30,  61}, {62,  45},  {59,  119}}},	
            {"345", 	8,  {
   
   {10,  13}, {16,  30},  {33,  23}}},	
			
    };
    std::vector<YoloLayerData> & layers = yolov5s_layers;

    int net_size =640;
    // get output data
    std::string output_tensor_name0 = layers[2].name ;
    std::string output_tensor_name1 = layers[1].name ;
    std::string output_tensor_name2 = layers[0].name ;
    
    std::shared_ptr<MNN::Interpreter> net = std::shared_ptr<MNN::Interpreter>(MNN::Interpreter::createFromFile(model_name.c_str()));
    if (nullptr == net) {
        return 0;
    }

    MNN::ScheduleConfig config;
    config.numThread = 4;
    config.type      = static_cast<MNNForwardType>(MNN_FORWARD_CPU);
    MNN::BackendConfig backendConfig;
    backendConfig.precision = (MNN::BackendConfig::PrecisionMode)2;
    // backendConfig.precision =  MNN::PrecisionMode Precision_Normal; // static_cast<PrecisionMode>(Precision_Normal);
    config.backendConfig = &backendConfig;
    
    std::vector<std::string> saveNamesVector;
    saveNamesVector.push_back(output_tensor_name0);
    saveNamesVector.push_back(output_tensor_name1);
    saveNamesVector.push_back(output_tensor_name2);
    
    config.saveTensors = saveNamesVector;
    
    MNN::Session *session = net->createSession(config);
    
	cv::VideoCapture capture;
    capture.open(0);  //修改这个参数可以选择打开想要用的摄像头

    cv::Mat frame;
	int INPUT_SIZE = 320;
	std::string img;
	pthread_mutex_init(&lock, NULL);
	pthread_t id;
	pthread_create(&id, NULL, pthread_listen, NULL);
	while(1)
	{ 
		capture >> frame;
		cv::Mat image;
		cv::flip(frame, image, 1);
		cv::Mat raw_image = image;

		cv::resize(raw_image, image, cv::Size(INPUT_SIZE, INPUT_SIZE));
		// preprocessing
		image.convertTo(image, CV_32FC3);
		// image = (image * 2 / 255.0f) - 1;
		image = image /255.0f;

		// wrapping input tensor, convert nhwc to nchw    
		std::vector<int> dims{1, INPUT_SIZE, INPUT_SIZE, 3};
		auto nhwc_Tensor = MNN::Tensor::create<float>(dims, NULL, MNN::Tensor::TENSORFLOW);
		auto nhwc_data   = nhwc_Tensor->host<float>();
		auto nhwc_size   = nhwc_Tensor->size();
		std::memcpy(nhwc_data, image.data, nhwc_size);

		auto inputTensor = net->getSessionInput(session, nullptr);
		inputTensor->copyFromHostTensor(nhwc_Tensor);

		// run network
		clock_t startTime,endTime;
		startTime = clock();//计时开始
		net->runSession(session);
		
		
		MNN::Tensor *tensor_scores  = net->getSessionOutput(session, output_tensor_name0.c_str());
		MNN::Tensor *tensor_boxes   = net->getSessionOutput(session, output_tensor_name1.c_str());
		MNN::Tensor *tensor_anchors = net->getSessionOutput(session, output_tensor_name2.c_str());

		MNN::Tensor tensor_scores_host(tensor_scores, tensor_scores->getDimensionType());
		MNN::Tensor tensor_boxes_host(tensor_boxes, tensor_boxes->getDimensionType());
		MNN::Tensor tensor_anchors_host(tensor_anchors, tensor_anchors->getDimensionType());

		tensor_scores->copyToHostTensor(&tensor_scores_host);
		tensor_boxes->copyToHostTensor(&tensor_boxes_host);
		tensor_anchors->copyToHostTensor(&tensor_anchors_host);

		std::vector<BoxInfo> result;
		std::vector<BoxInfo> boxes;
		
		yolocv::YoloSize yolosize = yolocv::YoloSize{INPUT_SIZE,INPUT_SIZE};
		
		float threshold = 0.45;
		float nms_threshold = 0.5;

		// show_shape(tensor_scores_host.shape());
		// show_shape(tensor_boxes_host.shape());
		// show_shape(tensor_anchors_host.shape());

		
		boxes = decode_infer(tensor_scores_host, layers[2].stride,  yolosize, net_size, num_classes, layers[2].anchors, threshold);
		result.insert(result.begin(), boxes.begin(), boxes.end());

		boxes = decode_infer(tensor_boxes_host, layers[1].stride,  yolosize, net_size, num_classes, layers[1].anchors, threshold);
		result.insert(result.begin(), boxes.begin(), boxes.end());

		boxes = decode_infer(tensor_anchors_host, layers[0].stride,  yolosize, net_size, num_classes, layers[0].anchors, threshold);
		result.insert(result.begin(), boxes.begin(), boxes.end());

		nms(result, nms_threshold);

		std::cout<<result.size()<<std::endl;

		endTime = clock();//计时结束
		cout << "The forward time is: " <<(double)(endTime - startTime) / 1000.0 << "ms" << endl;
		//cout << "The fps is: " <<1000 / (double)(endTime - startTime)<< "frame/s" << endl;
		scale_coords(result, INPUT_SIZE, INPUT_SIZE, raw_image.cols, raw_image.rows);

		cv::Mat frame_show = draw_box(raw_image, result);
		std::vector<unsigned char> vecImg;
		std::vector<int> quality;
		quality.push_back(CV_IMWRITE_JPEG_QUALITY);
		quality.push_back(30);
		cv::imencode(".jpg",frame_show, vecImg, quality);
			
		if(!sendok)
			cv::imwrite("html_docs/output.jpg", frame_show);
		sendok = true;

		//cv::imshow("outpt", frame_show);
		//if (cv::waitKey(30) >= 0)
		//	break;
    }
    return 0;
}

 Cuarto, el archivo html local

Después de recibir la solicitud del cliente, transmitirá el archivo html local para realizar la página web y utilizará el mecanismo de actualización para configurar la imagen para que se actualice cada 200 ms, a fin de lograr el efecto de video.

  • html con código de respuesta 200
<html lang="zh-CN" refresh=6>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>Raspi</title>
</head>
<script>
function refreshimg(){
document.getElementById("myimg").src="output.jpg?time="+(new Date()).getTime();
}
setInterval("refreshimg()",200);
</script>
<body>
<div align="center">
<h1>Raspi Camera</h1>
<img class="myimg"id="myimg"src="output.jpg"vertical-align="middle"width="640px"height="480px">
</div>
</body>
</html>
  • html con código de respuesta 400
<html lang="zh-CN">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>400 BAD REQUEST</title>
</head>
<body>
<div align="center">
<h1>400 BAD REQUEST</h1>
<h1>请求格式有问题!</h1>
</div>
</body>
</html>
  • html con código de respuesta 404
<html lang="zh-CN">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>404 NOT FOUND</title>
</head>
<body>
<div align="center">
<h1>404 NOT FOUND</h1>
<h1>File is not existed!</h1>
</div>
</body>
</html>
  •  html con código de respuesta 501
<html lang="zh-CN">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>501 METHOD NOT IMPLEMENTED</title>
</head>
<body>
<div align="center">
<h1>501 METHOD NOT IMPLEMENTED</h1>
<h1>请求方法异常!</h1>
</div>
</body>
</html>

Resumir

El efecto final es como se muestra en la figura.

Supongo que te gusta

Origin blog.csdn.net/deku_desi/article/details/125586233
Recomendado
Clasificación