Daemon.hpp
cpp
复制代码
#pragma once
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const std::string nullfile = "/dev/null";
void Daemon(const std::string &cwd = "")
{
// 1. 忽略其他异常信号
signal(SIGCLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGSTOP, SIG_IGN);
// 2. 将自己变成独立的会话
if (fork() > 0)
exit(0);
setsid();
// 3. 更改当前调用进程的工作目录
if (!cwd.empty())
chdir(cwd.c_str());
// 4. 标准输入,标准输出,标准错误重定向至/dev/null
int fd = open(nullfile.c_str(), O_RDWR);
if(fd > 0)
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}
cpp
复制代码
#include "HttpServer.hpp"
#include <iostream>
#include <memory>
#include <pthread.h>
#include "Daemon.hpp"
using namespace std;
int main(int argc, char *argv[])
{
if(argc != 2)
{
exit(1);
}
Daemon("/home/ly_centos/http/");
uint16_t port = std::stoi(argv[1]);
// HttpServer *svr = new HttpServer();
// std::unique<HttpServer> svr(new HttpServer());
std::unique_ptr<HttpServer> svr(new HttpServer(port));
svr->Start();
return 0;
}
HttpServer.hpp
cpp
复制代码
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>
#include "Socket.hpp"
const std::string wwwroot="./wwwroot"; // web 根目录
const std::string sep = "\r\n";
const std::string homepage = "index.html";
static const int defaultport = 8082;
class HttpServer;
class ThreadData
{
public:
ThreadData(int fd, HttpServer *s) : sockfd(fd), svr(s)
{
}
public:
int sockfd;
HttpServer *svr;
};
class HttpRequest
{
public:
void Deserialize(std::string req)
{
while(true)
{
std::size_t pos = req.find(sep);
if(pos == std::string::npos) break;
std::string temp = req.substr(0, pos);
if(temp.empty()) break;
req_header.push_back(temp);
req.erase(0, pos+sep.size());
}
text = req;
}
// .png:image/png
void Parse()
{
std::stringstream ss(req_header[0]);
ss >> method >> url >> http_version;
file_path = wwwroot; // ./wwwroot
if(url == "/" || url == "/index.html") {
file_path += "/";
file_path += homepage; // ./wwwroot/index.html
}
else file_path += url; // /a/b/c/d.html->./wwwroot/a/b/c/d.html
auto pos = file_path.rfind(".");
if(pos == std::string::npos) suffix = ".html";
else suffix = file_path.substr(pos);
}
void DebugPrint()
{
for(auto &line : req_header)
{
std::cout << "--------------------------------" << std::endl;
std::cout << line << "\n\n";
}
std::cout << "method: " << method << std::endl;
std::cout << "url: " << url << std::endl;
std::cout << "http_version: " << http_version << std::endl;
std::cout << "file_path: " << file_path << std::endl;
std::cout << text << std::endl;
}
public:
std::vector<std::string> req_header;
std::string text;
// 解析之后的结果
std::string method;
std::string url;
std::string http_version;
std::string file_path; // ./wwwroot/a/b/c.html 2.png
std::string suffix;
};
class HttpServer
{
public:
HttpServer(uint16_t port = defaultport) : port_(port)
{
content_type.insert({".html", "text/html"});
content_type.insert({".png", "image/png"});
}
bool Start()
{
listensock_.Socket();
listensock_.Bind(port_);
listensock_.Listen();
for (;;)
{
std::string clientip;
uint16_t clientport;
int sockfd = listensock_.Accept(&clientip, &clientport);
if (sockfd < 0)
continue;
perror("get a new connect, sockfd");
pthread_t tid;
ThreadData *td = new ThreadData(sockfd, this);
pthread_create(&tid, nullptr, ThreadRun, td);
}
}
static std::string ReadHtmlContent(const std::string &htmlpath)
{
// 坑
std::ifstream in(htmlpath, std::ios::binary);
if(!in.is_open()) return "";
in.seekg(0, std::ios_base::end);
auto len = in.tellg();
in.seekg(0, std::ios_base::beg);
std::string content;
content.resize(len);
in.read((char*)content.c_str(), content.size());
//std::string content;
//std::string line;
//while(std::getline(in, line))
//{
// content += line;
//}
in.close();
return content;
}
std::string SuffixToDesc(const std::string &suffix)
{
auto iter = content_type.find(suffix);
if(iter == content_type.end()) return content_type[".html"];
else return content_type[suffix];
}
void HandlerHttp(int sockfd)
{
char buffer[10240];
ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0); // bug
if (n > 0)
{
buffer[n] = 0;
std::cout << buffer << std::endl; // 假设我们读取到的就是一个完整的,独立的http 请求
HttpRequest req;
req.Deserialize(buffer);
req.Parse();
//req.DebugPrint();
//std::string path = wwwroot;
//path += url; // wwwroot/a/a/b/index.html
// 返回响应的过程
std::string text;
bool ok = true;
text = ReadHtmlContent(req.file_path); // 失败?
if(text.empty())
{
ok = false;
std::string err_html = wwwroot;
err_html += "/";
err_html += "err.html";
text = ReadHtmlContent(err_html);
}
std::string response_line;
if(ok)
response_line = "HTTP/1.0 200 OK\r\n";
else
response_line = "HTTP/1.0 404 Not Found\r\n";
//response_line = "HTTP/1.0 302 Found\r\n";
std::string response_header = "Content-Length: ";
response_header += std::to_string(text.size()); // Content-Length: 11
response_header += "\r\n";
response_header += "Content-Type: ";
response_header += SuffixToDesc(req.suffix);
response_header += "\r\n";
response_header += "Set-Cookie: name=haha&&passwd=12345";
response_header += "\r\n";
//response_header += "Location: https://www.qq.com\r\n";
std::string blank_line = "\r\n"; // \n
std::string response = response_line;
response += response_header;
response += blank_line;
response += text;
send(sockfd, response.c_str(), response.size(), 0);
}
close(sockfd);
}
static void *ThreadRun(void *args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData *>(args);
td->svr->HandlerHttp(td->sockfd);
delete td;
return nullptr;
}
~HttpServer()
{
}
private:
Sock listensock_;
uint16_t port_;
std::unordered_map<std::string, std::string> content_type;
};
Socket.hpp
cpp
复制代码
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
enum
{
SocketErr = 2,
BindErr,
ListenErr,
};
// TODO
const int backlog = 10;
class Sock
{
public:
Sock()
{
}
~Sock()
{
}
public:
void Socket()
{
sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd_ < 0)
{
perror("socker error");
exit(SocketErr);
}
int opt = 1;
setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
}
void Bind(uint16_t port)
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0)
{
perror("bind error");
exit(BindErr);
}
}
void Listen()
{
if (listen(sockfd_, backlog) < 0)
{
perror("listen error");
exit(ListenErr);
}
}
int Accept(std::string *clientip, uint16_t *clientport)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);
if(newfd < 0)
{
perror("accept error");
return -1;
}
char ipstr[64];
inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
*clientip = ipstr;
*clientport = ntohs(peer.sin_port);
return newfd;
}
bool Connect(const std::string &ip, const uint16_t &port)
{
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));
int n = connect(sockfd_, (struct sockaddr*)&peer, sizeof(peer));
if(n == -1)
{
std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
return false;
}
return true;
}
void Close()
{
close(sockfd_);
}
int Fd()
{
return sockfd_;
}
private:
int sockfd_;
};
makefile
cpp
复制代码
HttpServer:HttpServer.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f HttpServer
index.html
html
复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>新年快乐 - 超绚烂烟花秀</title>
<style>
body {
margin: 0;
padding: 0;
background: #000;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
height: 100vh;
}
canvas {
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
/* 按钮样式 */
#fireworkBtn {
position: relative;
z-index: 10;
margin-bottom: 80px;
padding: 18px 50px;
font-size: 28px;
font-weight: bold;
color: #ffd700; /* 金色文字 */
background: linear-gradient(135deg, #ff0000, #cc0000); /* 渐变红背景 */
border: 2px solid #ffd700;
border-radius: 12px;
cursor: pointer;
box-shadow: 0 0 20px rgba(255, 0, 0, 0.6), 0 0 40px rgba(255, 215, 0, 0.3);
transition: all 0.2s;
text-shadow: 0 0 10px rgba(255, 215, 0, 0.8);
}
#fireworkBtn:hover {
transform: scale(1.08);
box-shadow: 0 0 30px rgba(255, 0, 0, 0.8), 0 0 60px rgba(255, 215, 0, 0.5);
}
#fireworkBtn:active {
transform: scale(0.96);
}
</style>
</head>
<body>
<canvas id="fireworks"></canvas>
<button id="fireworkBtn">新年快乐</button>
<script>
const canvas = document.getElementById('fireworks');
const ctx = canvas.getContext('2d');
const btn = document.getElementById('fireworkBtn');
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// 新年专属配色:金、银、红、青
const colors = [
'#ffd700', '#ffec8b', '#fff8dc', // 金色系
'#ffffff', '#e8e8e8', '#c0c0c0', // 银色系
'#ff0043', '#ff4500', '#ff6347', // 红色系
'#00ffff', '#7fffd4', '#00fa9a' // 青绿色系
];
// 粒子类(带拖尾)
class Particle {
constructor(x, y, color, speed, angle, size) {
this.x = x;
this.y = y;
this.color = color;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
this.alpha = 1;
this.decay = Math.random() * 0.01 + 0.003;
this.gravity = 0.03;
this.size = size || Math.random() * 3 + 1;
this.trail = []; // 拖尾数组
this.maxTrailLength = 8;
}
update() {
// 保存当前位置到拖尾
this.trail.push({ x: this.x, y: this.y, alpha: this.alpha });
if (this.trail.length > this.maxTrailLength) {
this.trail.shift();
}
this.vx *= 0.99;
this.vy *= 0.99;
this.vy += this.gravity;
this.x += this.vx;
this.y += this.vy;
this.alpha -= this.decay;
}
draw() {
// 绘制拖尾
for (let i = 0; i < this.trail.length; i++) {
const t = this.trail[i];
const trailAlpha = (i / this.trail.length) * t.alpha * 0.5;
ctx.save();
ctx.globalAlpha = trailAlpha;
ctx.beginPath();
ctx.arc(t.x, t.y, this.size * (i / this.trail.length), 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
}
// 绘制粒子本体(带发光效果)
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.shadowBlur = 10;
ctx.shadowColor = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
}
isAlive() {
return this.alpha > 0;
}
}
// 烟花类(双重爆炸+多种形状)
class Firework {
constructor() {
this.x = Math.random() * (canvas.width * 0.6) + canvas.width * 0.2;
this.y = canvas.height;
this.targetY = Math.random() * (canvas.height * 0.4) + canvas.height * 0.1;
this.color = colors[Math.floor(Math.random() * colors.length)];
this.speed = Math.random() * 4 + 5;
this.particles = [];
this.exploded = false;
this.secondExplosion = false;
this.shape = Math.floor(Math.random() * 4); // 0:圆形 1:心形 2:星形 3:环形
}
update() {
if (!this.exploded) {
this.y -= this.speed;
this.speed *= 0.99;
if (this.speed <= 1.5 || this.y <= this.targetY) {
this.explode();
}
} else {
for (let i = this.particles.length - 1; i >= 0; i--) {
this.particles[i].update();
if (!this.particles[i].isAlive()) {
this.particles.splice(i, 1);
}
}
// 第一次爆炸后0.3秒进行第二次小爆炸
if (!this.secondExplosion && this.particles.length > 0 && this.particles[0].alpha < 0.7) {
this.secondExplode();
}
}
}
explode() {
this.exploded = true;
const particleCount = 180 + Math.floor(Math.random() * 100);
for (let i = 0; i < particleCount; i++) {
let angle, speed;
// 根据形状计算角度和速度
switch (this.shape) {
case 0: // 圆形爆炸
angle = (i / particleCount) * Math.PI * 2;
speed = Math.random() * 4 + 2;
break;
case 1: // 心形爆炸
angle = (i / particleCount) * Math.PI * 2;
const heart = Math.pow(Math.sin(angle), 3);
speed = (2 - Math.cos(angle) - 2 * Math.cos(2 * angle) - Math.cos(3 * angle) - Math.cos(4 * angle)) * 1.5;
break;
case 2: // 星形爆炸
angle = (i / particleCount) * Math.PI * 2;
const star = 1 + 0.3 * Math.sin(5 * angle);
speed = star * (Math.random() * 3 + 2);
break;
case 3: // 环形爆炸
angle = (i / particleCount) * Math.PI * 2;
speed = 3 + Math.random() * 0.5;
break;
}
const size = Math.random() * 3 + 1.5;
const color = colors[Math.floor(Math.random() * colors.length)];
this.particles.push(new Particle(this.x, this.y, color, speed, angle, size));
}
}
secondExplode() {
this.secondExplosion = true;
const secondCount = 60;
for (let i = 0; i < secondCount; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 2 + 1;
const color = '#ffd700'; // 第二次爆炸用金色
this.particles.push(new Particle(this.x, this.y, color, speed, angle, 2));
}
}
draw() {
if (!this.exploded) {
// 绘制上升的烟花弹(带发光尾迹)
ctx.save();
ctx.shadowBlur = 15;
ctx.shadowColor = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, 4, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
} else {
this.particles.forEach(p => p.draw());
}
}
isAlive() {
return this.exploded && this.particles.length === 0;
}
}
const fireworks = [];
function animate() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.15)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = fireworks.length - 1; i >= 0; i--) {
fireworks[i].update();
fireworks[i].draw();
if (fireworks[i].isAlive()) {
fireworks.splice(i, 1);
}
}
requestAnimationFrame(animate);
}
btn.addEventListener('click', () => {
// 每次点击发射 5 颗烟花,超热闹
for (let i = 0; i < 5; i++) {
setTimeout(() => {
fireworks.push(new Firework());
}, i * 120);
}
});
animate();
</script>
</body>
</html>
实际效果