URL
我们所说的网址就是 URL。

如果在参数中出现特殊字符,会自动转义。/、? 等字符已被 URL 特殊处理。

+ 被转义为 %2b。
HTTP 协议格式
请求格式

- 请求行:方法 + URL + 版本号 +
\r\n - 请求报头:每行一对键值对 +
\r\n - 空行:
\r\n(没有空行的话无法区分报头和正文) - 正文:大小由报头中的
Content-Length确定
例如:

HTTP 常用方法

常用的有 GET、POST。
响应格式

与请求类似:

HTTP 协议的基础概念,包括 URL 编码、请求与响应报文结构、常用方法、状态码及常见 Header 字段。接着通过 C++ Socket 编程演示了简易 HTTP 服务器的实现过程,涉及监听、连接处理、文件读取及 Cookie 机制的配置。

我们所说的网址就是 URL。

如果在参数中出现特殊字符,会自动转义。/、? 等字符已被 URL 特殊处理。

+ 被转义为 %2b。

\r\n\r\n\r\n(没有空行的话无法区分报头和正文)Content-Length 确定例如:


常用的有 GET、POST。

与请求类似:


微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online

常用状态码:
Content-Type: 数据类型 (text/html 等) Content-Length: Body 的长度 Host: 客户端告知服务器,所请求的资源是在哪个主机的哪个端口上 User-Agent: 声明用户的操作系统和浏览器版本信息 Referer: 当前页面是从哪个页面跳转过来的 Location: 搭配 3xx 状态码使用,告诉客户端接下来要去哪里访问 Cookie: 用于在客户端存储少量信息,通常用于实现会话 (session) 的功能

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "log.hpp"
#include <string.h>
#include <unistd.h>
using namespace std;
Log lg;
const int backlog = 10;
enum {
SocketErr = 2,
BindErr,
ListenErr,
};
class Sock {
public:
Sock() {}
~Sock() {}
void Socket() {
sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd_ < 0) {
lg(Fatal, "socket error, strerror: %s, errno: %d", strerror(errno), errno);
exit(SocketErr);
}
}
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) {
lg(Fatal, "Bind error, strerror: %s, errno: %d", strerror(errno), errno);
exit(BindErr);
}
}
void Listen() {
if (listen(sockfd_, backlog) < 0) {
lg(Fatal, "Listen Error, strerror: %s, errno: %d", strerror(errno), errno);
exit(ListenErr);
}
}
int Accept(std::string* ip, uint16_t* port) {
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);
if (newfd < 0) {
lg(Warning, "accept error, strerror: %s, errno: %d", strerror(errno), errno);
return -1;
}
char ipstr[64];
inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
*ip = ipstr;
*port = ntohs(peer.sin_port);
return newfd;
}
bool Connect(const 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 < 0) {
cerr << "Connect to" << ip << ":" << port << endl;
return false;
}
return true;
}
void Close() { close(sockfd_); }
int Fd() { return sockfd_; }
private:
int sockfd_;
};
#pragma once
#include <iostream>
#include "Socket.hpp"
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>
std::string blank_line = "\r\n";
std::string wwwroot = "./wwwroot";
std::string homepage = "index.html";
class HttpServer;
class ThreadData {
public:
ThreadData(int sockfd, HttpServer* hs) : _sockfd(sockfd), _hs(hs) {}
public:
int _sockfd;
HttpServer* _hs;
};
class HttpResponse {
public:
void Deserialize(std::string req) {
while (true) {
std::size_t pos = req.find(blank_line);
if (pos == std::string::npos) break;
std::string temp = req.substr(0, pos);
if (temp.empty()) // 空行
break;
vc.push_back(temp);
req.erase(0, pos + blank_line.size());
}
text += req;
}
void Parse() {
std::stringstream ss(vc[0]);
ss >> method >> url >> version;
file_path = wwwroot;
if (url == "/" || url == "index.html") {
file_path += "/";
file_path += homepage; // wwwroot/index.html
} else {
file_path += url; // wwwroot/a/b/t/t
}
}
void DebugPrint() {
for (auto line : vc) {
std::cout << line << std::endl;
std::cout << "------------" << std::endl;
}
std::cout << "method: " << method << std::endl;
std::cout << "url: " << url << std::endl;
std::cout << "version: " << version << std::endl;
std::cout << "file_path: " << file_path << std::endl;
std::cout << "text: " << text << std::endl;
}
public:
vector<string> vc;
string text;
std::string method;
std::string url;
std::string version;
std::string file_path;
};
class HttpServer {
public:
HttpServer(uint16_t port) : _port(port) {}
void start() {
_listen.Socket();
_listen.Bind(_port);
_listen.Listen();
lg(Info, "server create success");
for (;;) {
std::string clientip;
uint16_t port;
int sockfd = _listen.Accept(&clientip, &port);
lg(Info, "get a newfd:%d", 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);
if (!in.is_open()) { return "404"; }
std::string content;
std::string line;
while (getline(in, line)) {
content += line;
}
in.close();
return content;
}
static void HandlerHttp(int sockfd) {
char buffer[10240];
ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0) {
buffer[n] = 0;
HttpResponse rep;
rep.Deserialize(buffer);
rep.Parse();
rep.DebugPrint();
std::string text = ReadHtmlContent(rep.file_path);
std::string response_line = "HTTP/1.0 200 OK\r\n";
std::string response_header = "Content-Length: ";
response_header += std::to_string(text.size());
std::string response = response_line;
response += response_header;
response += "\r\n";
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);
HandlerHttp(td->_sockfd);
close(td->_sockfd);
delete td;
return nullptr;
}
Sock _listen;
uint16_t _port;
};
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head>
<body>
<h1>这个是我们的首页</h1>
<a href="http://127.0.0.1:8080/a/b/hello.html">到第二张网页</a>
<a href="http://127.0.0.1:8080/c/d/hello2.html">到第三张网页</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head>
<body>
<h1>第二张图片</h1>
<a href="http://127.0.0.1:8080">回到首页</a>
<a href="http://127.0.0.1:8080/c/d/hello2.html">到第三张网页</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head>
<body>
<h1>第三张图片</h1>
<a href="http://127.0.0.1:8080">回到首页</a>
<a href="http://127.0.0.1:8080/a/b/hello.html">到第三张网页</a>
</body>
</html>
#pragma once
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
#define SIZE 1024
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
#define Screen 1
#define Onefile 2
#define Classfile 3
#define Logfile "log.txt"
class Log {
public:
Log() { printmethod = Screen; path = "./log/"; mkdir(path.c_str(), 0777); }
void Enable(int method) { printmethod = method; }
string levelToString(int level) {
switch (level) {
case Info: return "Info";
case Debug: return "Debug";
case Warning: return "Warning";
case Error: return "Error";
case Fatal: return "Fatal";
}
}
void operator()(int level, const char* format, ...) {
time_t t = time(nullptr);
struct tm* ctime = localtime(&t);
char leftbuffer[SIZE];
snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d %d %d]", levelToString(level).c_str(), ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
va_list s;
va_start(s, format);
char rightbuffer[SIZE];
vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
va_end(s);
char logtxt[SIZE * 2];
snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);
printlog(level, logtxt);
}
void printlog(int level, const string& logtxt) {
switch (printmethod) {
case Screen: cout << logtxt << endl; break;
case Onefile: printOnefile(Logfile, logtxt); break;
case Classfile: printClassfile(level, logtxt); break;
}
}
void printOnefile(const string& failname, const string& logtxt) {
string _failname = path + failname;
int fd = open(_failname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
if (fd < 0) { return; }
write(fd, logtxt.c_str(), logtxt.size());
close(fd);
}
void printClassfile(int level, const string& logtxt) {
string _failname = Logfile;
_failname += ".";
_failname += levelToString(level);
printOnefile(_failname, logtxt);
}
~Log() {}
private:
int printmethod;
string path;
};
#include <iostream>
#include "HttpServer.hpp"
using namespace std;
int main(int argc, char* argv[]) {
if (argc != 2) { exit(1); }
HttpServer* svr = new HttpServer(stoi(argv[1]));
svr->start();
return 0;
}
启动服务端后,打开浏览器输入 IP:端口号。

实现成功,点击第二张网页。

通过终端工具查看浏览器发送的请求。

我们可以看到域名也发生了变化,其实就是跳转到特定的文件。

为什么我们第一次访问网站要登录账户,但是第二次就不需要再登录了? 因为浏览器帮我们记住账户了,账户信息存储在 Cookie 中。此外,服务器为我们分配了 ID,我们每次访问网站,浏览器都会带着 Cookie 和 ID 让服务器确认账号信息。
Cookie 信息在浏览器左上角,如果我们删除 Cookie 就会让我们再次输入账户信息。

客户端第一次向服务端发送账号和密码,服务器为客户端建立 Cookie 并分配 ID,之后客户端每次访问服务端都带着 Cookie 和 ID,服务端在数据库查找确认。
在我们服务器代码加入 Cookie 报头:
response_header += "Set-Cookie: name=haha&&password=123456";
response_header += "\r\n";
左上角打开我们的浏览器 Cookie 信息。
