目录

前言

一、HTTP协议

1.客户端请求

2.服务器响应

3.常见响应代号和对应描述

二、服务器搭建

1.思路

2.获取请求头部

3.解析头部获取URL和请求

4.服务器响应

5.http.h

6.tcp.cpp 和tcp.h

三、主程序的实现

四、本地html文件

总结


前言

在树莓派部署yolov5lite的基础上,搭建http服务器,添加网页监控功能


一、HTTP协议

http(hyper text transfer prtocol)即超文本传输协议,利用该协议能够实现浏览器加载本地文件的功能,本文在http1.0版本的基础上实现功能。

1.客户端请求

客户端请求的格式一般由:请求行、请求头、空行和请求数据组成,具体格式如下:

请求行:        请求方法|空格|URL|空格|协议版本|回车符|换行符|

请求头:        字段名|||回车符|换行符|

........

字段名||值|回车符|换行符|

空行:            回车符|换行符|

请求数据:     xxxxxxxx

2.服务器响应

服务器响应的格式一般由:状态行、消息头、空行和响应正文组成,具体格式如下:

状态行:        协议版本|空格|响应代号|空格|代号描述|回车符|换行符|

消息头:        字段名|||回车符|换行符|

........

字段名|||回车符|换行符|

空行:            回车符|换行符|

响应正文:     xxxxxxxx

3.常见响应代号和对应描述

响应代号 代号描述
服务器上存在请求内容并可以响应给客户端 200 OK
客户端请求异常,方法有问题 501 METHOD NOT IMPLEMENTED
请求内容不存在 404 NOT FOUND
客户端发送的请求格式有问题 400 BAD REQUEST

二、服务器搭建

1.思路

http协议工作模式为客户端发送请求,服务器响应请求,客户端为浏览器,因此只需要解析客户端发来的请求并响应即可,由于yolov5在c++环境下部署,因此服务器采用c/c++语言。

2.获取请求头部

简历socket连接,read客户端发来的消息并解析

/*
作用:获取请求行
形参:套接字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.解析头部获取URL和请求

根据请求头格式,先读取一行请求行,利用空格划分字符串并获取URL和请求,如果URL格式不正确,比如url=index.htm?xxxxxxxxx,只取?前的内容

*
作用:解析请求行内容
形参:请求行内容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.服务器响应

根据请求方法和URL进行响应,由于本文只用到了GET方法,故只需要解决GET和非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;elseheader = 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 和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

三、主程序的实现

树莓派搭建服务器采用多线程的方式,主程序负责人脸识别并保存jpg格式图片,创建线程用来实现图片和html文件传输,设置全局变量标志位,当图像文件写入完成后,线程才能发送图片,而发送图片时不能写入。

#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 datastd::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));// preprocessingimage.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 networkclock_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;
}

四、本地html文件

接收到客户端请求后,会将本地的html文件进行传输,从而实现网页,利用刷新机制,设置图片为200ms刷新一次,从而实现视频效果。

  • 响应代码为200的html
<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>
  • 响应代码为400的html
<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>
  • 响应代码为404的html
<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>
  • 响应代码为501的html
<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>

总结

最终实现效果如图

树莓派搭建http服务器实现网页监控摄像头相关推荐

  1. 2022年,树莓派搭建web服务器,加各种bug的解决

    2022年,树莓派搭建web服务器,加各种bug的解决 配置: 1.树莓派3B+ 2.树莓派系统:2022-1-28 桌面版 文章目录 2022年,树莓派搭建web服务器,加各种bug的解决 一.首先 ...

  2. 如何用树莓派搭建远程服务器 (zerotier)

    如何用树莓派搭建远程服务器 (zerotier) 文章目录 简述 什么是zerotier 安装 注册 树莓派端安装(linux类均可使用该方法) 安装软件 添加网络 配置自启动 安卓手机安装 添加网络 ...

  3. 树莓派搭建nas服务器的详细过程

    前奏 默认的登录帐号为 pi,密码是 raspberry 开启 ssh : 在根目录,新建一个名为 ssh 的空白文件就行了.然后,重启就可以ssh访问了. 命令行下配置:sudo raspi-con ...

  4. 树莓派文件服务器nas,树莓派搭建NAS服务器

    最近在B站上无意间刷到了树莓派NAS系统搭建这个视频,正好我们开发的项目上传的文件也是存放在NAS服务器上的,于是自己也用树莓派搭了个NAS服务器. NAS服务器 搭建准备工作 首先更换源,在sour ...

  5. 树莓派制作minecraft服务器,用树莓派搭建Minecraft服务器

    如果我们需要更轻量级,更省电,而且要一直在线的,朋友们随时可以上来玩的Minecraft服务器.下文介绍了如何用树莓派搭建低能耗的Minecraft服务器,你可以全天不间断地开着,每天的电费大概只要一 ...

  6. 树莓派搭建家庭服务器

    概述: 初步设想:采用一个树莓派作为服务器常开,安装arm64位debian系统: 另外一个树莓派连接电视作为网络机顶盒,构建家庭影院. 1. 服务器实现的功能: 1.1 搭建homeassistan ...

  7. 搭建web服务器访问网页

    一.要求搭建web服务器,能够访问到网页内容为"小胖,你咋这么胖呢!" 1.安装yum源 保存退出 2.开启httpd服务,关闭防火墙 3.写入网页内容 4.在浏览器输入http: ...

  8. 树莓派搭建 DNS 服务器 | 树莓派小无相系列

    自建 DNS 服务器,可以规避运营商的 DNS 劫持,还可以一定程度上加速网络访问.除此之外,在自定一些配置之后,能实现广告拦截及其他的一些功能.在这里我们通过在树莓派使用 dnsmasq 搭建这样一 ...

  9. 树莓派搭建MQTT服务器(基于EMQ)

    文章目录 1.准备工作 1.1知识储备 2.安装EMQ 3.测试MQTT服务器 3.1 新建服务器管理员 3.2 登录到服务器后台 3.3 MQTT客户端测试 1.准备工作 1.1知识储备 关于MQT ...

  10. 树莓派搭建minecraft服务器

    注意:一代树莓派由于性能不足,无法完美运行minecraft服务器(至少我试了不行).请至少使用二代B型来尝试. 一.安装JDK 很多网上的教程都说要安装JDK,其实raspbian系统自带了orac ...

最新文章

  1. 据说这套组合拳,可以把面试官给问懵逼了,你要不要试试?
  2. Chrome 正在测试标签页的预览功能
  3. Codeforce 1042 D. Petya and Array(树状数组、前缀和)
  4. superset可视化-world map
  5. mysql查看主键别名_MySQL怎么查看约束的别名呢?
  6. 为什么你一直在写bug?原因找到了
  7. 【渝粤教育】电大中专幼儿园课程论作业 题库
  8. 我的 Java/C# web 后端开发技术选择
  9. ocx注册方法,vs安装包自动安装ocx,以及ocx注册失败的解决方法
  10. 跨站脚本攻击原理、攻击过程及防御方法简介
  11. GLSL内置数学函数部分解析
  12. 使用springboot,Oauth2.0,jwt令牌实现单点登录,权限控制等功能的基本流程
  13. ORB_SLAM3系统框图
  14. 浏览器-IE主页被篡改后修复
  15. 吉林大学软件学院黄庆道《最优化算法》对偶单纯形使用大M法条件
  16. RMAN恢复Oracle数据库到不同的服务器
  17. Qt 天气预报 天气查询 (代码附上)
  18. Python 两种POST请求的方式
  19. Transact-SQL 语法元素之数据类型
  20. php外卖小哥,【看点】外卖小哥,谢谢你(随笔)

热门文章

  1. 支付宝提现回调地址问题
  2. 超声波清洗机是什么?
  3. html5设置视频显示第一帧,【前端】如何在video上显示视频的第一帧
  4. if语句(判断年月日)
  5. HTB-Sequel
  6. python绝对值_python求绝对值的三种方法小结
  7. 12306 崩了,90% 的人都用过这三款抢票工具
  8. python获取电脑屏幕分辨率
  9. Edge浏览器运行卡顿怎么办 怎样让Edge浏览器速度更快
  10. iOS 依赖注入:Objection 和 Typhoon