1.HTTP协议
虽然我们说,应用层协议是我们程序猿自己定的.但实际上,已经有大佬们定义了一些现成的,又非常好用的应用层协议,供我们直接参考使用.HTTP(超文本传输协议)就是其中之一。
在互联网世界中,HTTP(HyperTextTransfer Protocol,超文本传输协议)是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如HTML文档)。
HTTP协议是客户端与服务器之间通信的基础。客户端通过HTTP协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP协议是一个无连接、无状态的协议,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息。
2. HTTP 协议的工作过程
当我们在浏览器输入一个网址,此时浏览器就会给对应的服务器发送一个 HTTP 请求,对应的服务器收到这个请求之后,经过计算处理,就会返回一个 HTTP 响应。并且当我们访问一个网站时,可能涉及不止一次的 HTTP 请求和响应的交互过程。
基础术语:
- 客户端: 主动发起网络请求的一端
- 服务器: 被动接收网络请求的一端
- 请求: 客户端给服务器发送的数据
- 响应: 服务器给客户端返回的数据
HTTP 协议的重要特点: 一发一收,一问一答
注意: 网络编程中,除了一发一收之外,还有其它的模式
多发一收:例如上传大文件
一发多收:例如看直播时,搜索一个词条可以得到多个视频源
多发多收:例如串流(steam link、moonlight 等等)
3. Fiddler 抓包工具介绍
3.1 抓包工具的使用
当我们访问一个网站时,可能涉及不止一次的 HTTP 请求和响应的交互,为此为了更加清楚的了解我们访问一个网站时 HTTP 请求/协议是怎么交互的,由于 HTTP 是一个文本格式的协议,就可以通过以下两种方式:
方式一: 通过 F12 打开浏览器的开发者工具,点击 Network 标签页,然后刷新页面就行。显示的每一条记录都是一次 HTTP 请求/响应
方式二(推荐): 抓包工具,这里以 Fiddler 为例,它能够直接读取你电脑上网卡的信息,网卡上有什么数据流动,它都能感知到并且显示出来
Fiddler 使用方式:
打开 Fiddler,然后打开你要访问的网站,访问该网站的 HTTP 请求和响应就会显示在 Fiddler 上
urlencode 和urldecode
像/?:等这样的字符,已经被url当做特殊意义理解了.因此这些字符不能随意出现.
比如,某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位
做一位,前面加上%,编码成%XY格式

当我们选择好我们具体要查看的 HTTP/HTTPS 请求和响应时,右上栏就会显示具体的请求报文内容,右下角就会显示具体的响应报文内容(需要点击 Raw 标签来查看详细的数据格式)
请求和响应的详细数据,可以通过右下角的 view in Notepad 键通过记事本打开
为了方便我们抓取我们自己想查看的抓包结果,可以通过 ctrl + a 全选左侧的抓包结果,ctrl + x 删除选中的所有抓包结果,再进行网页的刷新就行
响应的正文往往是被显示在浏览器上,最常见的响应格式就是 html,很多网站返回的 html 是压缩过的(因为压缩之后,网络传输的数据量就变少了,即节省了网络带宽),所以需要进行解压缩 decode
4.HTTP协议请求与响应格式
HTTP请求
首行:[方法]+[url]+[版本]
• Header:请求的属性,冒号分割的键值对;每组属性之间使用\r\n分隔;遇到空行
表示Header部分结束
• Body:空行后面的内容都是Body.Body允许为空字符串.如果Body存在,则在Header 中会有一个Content-Length属性来标识Body的长度;

5.认识"方法"(method)
HTTP 中的方法,就是 HTTP 请求报文中的首行的第一个部分。
原本 HTTP 的设计者,是希望通过不同的方法,来表达不同的语义。但是至今,其实也没有被实现,以下具体的方法具体起到了什么作用,完全看程序员在代码中是如何实现的。
虽然 HTTP 中的方法很多,但是最常用的就两个 GET 和 POST。以下主要介绍这两个方法
GET 方法
基本介绍:
GET 是最常用的 HTTP 方法,常用于获取服务器上的某个资源。以下几种方式都会触发 GET 方法的请求
在浏览器中直接输入 URL 回车或点击浏览器收藏夹中的链接,此时浏览器就会发送出一个 GET 请求。
HTML 中的 link、img、script 等标签的属性中放的 URL,浏览器也会构造出 HTTP GET 请求
使用 Javascript 重点 ajax,也能构造出 HTTP GET 请求
各种编程语言(只要能够访问网络),就都能够构造出 HTTP GER 请求
GET 请求的特点:
首行里面的第一个部分就是 GET
URL 里面的 query string 可以为空,也可以不为空
GET 请求的 header 有若干个键值对结构
GET 请求的 body 一般是空的
POST 方法
基本介绍:
POST 方法也是一种常见的方法,多用于提交用户输入的数据给服务器(如登录页面)。以下几种方法都会触发 POST 方法的请求
通过 HTML 中的 form 标签可以构造 POST 请求
使用 JavaScript 的 ajax 可以构造 POST 请求
POST 请求的特点:
首行第一个部分就是 POST
URL 里面的 query string 一般是空的
POST 请求的 header 里面有若干个键值对
POST 请求的 body 一般不为空(body 的具体数据格式,由 header 中的 Content-Type 来描述;body 的具体数据长度,由 header 中的 Content-Length 来描述
GET 和 POST 的区别
其实 GET 和 POST 的区别是一个经典的面试题,以下为大家介绍如何在面试中回答上述问题
答题模板:
GET 和 POST 其实没有本质区别,使用 GET 的场景完全可以使用 POST 代替,使用 POST 的场景一样可以使用 GET 代替。但是在具体的使用上,还是存在一些细节的区别
GET 习惯上会把客户端的数据通过 query string 来传输(body 部分是空的);POST 习惯上会把客户端的数据通过 body 来传输(query string 部分是空的)
GET 习惯上用于从服务器获取数据;POST 习惯上是客户端给服务器提交数据
一般情况,程序员会把 GET 请求的处理,实现成"幂等"的;对于 POST 请求的处理,不要求实现成"幂等"
GET 请求可以被缓存,可以被浏览器保存到收藏夹中;POST 请求不能被缓存
cpp
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <unordered_map>
#include <fstream>
#include <memory>
#include "Log.hpp"
const std::string Separator = "\r\n"; // 常量定义,表示回车换行符
static const std::string sep = "\r\n"; // 静态常量定义,表示回车换行符
static const std::string header_sep = ": "; // 静态常量定义,表示冒号和空格
static const std::string wwwroot = "wwwroot"; // 静态常量定义,表示wwwroot目录
static const std::string homepage = "index.html"; // 静态常量定义,表示首页文件名
static const std::string httpversion = "HTTP/1.0"; // 静态常量定义,表示HTTP版本
static const std::string httpversion = "HTTP/1.0"; // 静态常量定义,表示HTTP版本
static const std::string space = " "; // 静态常量定义,表示空格
static const std::string filesuffixsep = "."; // 静态常量定义,表示文件后缀分隔符
using func_t = std::function<std::shared_ptr<HttpResponse>(std::shared_ptr<HttpRequest>)>; // 定义回调函数类型
using func_t = std::function<std::shared_ptr<HttpResponse>(std::shared_ptr<HttpRequest>)>;
using func_t = std::function<std::shared_ptr<HttpResponse>(std::shared_ptr<HttpRequest>)>; // 定义回调函数类型
class HttpRequest // 定义一个HTTP请求类
{
private:
std::string GetOneLine(std::string &str) // 读取一行数据
{
if (str.empty())
{
return std::string(); // 如果字符串为空,则返回空字符串
}
auto pos = str.find(Separator); // 查找回车换行符
if (pos == std::string::npos) // 如果没有找到回车换行符,则返回空字符串
{
return std::string();
}
std::string line = str.substr(0, pos); // 截取一行数据
str.erase(0, pos + Separator.length()); // 删除已读取的字符串
return line.empty() ? Separator : line; // 如果一行数据为空,则返回回车换行符,否则返回一行数据
}
bool PraseHeaderHelper(const std::string &line, std::string *key, std::string *value) // 解析请求头
{
auto pos = line.find(':'); // 查找冒号
if (pos == std::string::npos)
{
return false; // 如果没有找到冒号,则返回false
}
*key = line.substr(0, pos); // 截取键
*value = line.substr(pos + 1); // 截取值
}
bool PraseRequestLine()
{
if (_req_line.empty())
{
return false; // 如果请求行为空,则返回false
}
std::stringstream ss(_req_line); // 字符串流
ss >> _method >> _url >> _version; // 解析请求行
_path += _url; // 路径
if (_path[_path.length() - 1] == '/') // 如果URL以/结尾,则添加index.html
{
// TODO: 这里需要添加index.html的处理逻辑
_path += homepage; // 这里需要添加index.html的处理逻辑
}
auto pos = _path.rfind(filesuffixsep); // rfind表示从右边开始查找,找到最后一个.的位置
if (pos == std::string::npos)
{
_suffix = ".html"; // 如果没有后缀名,则默认为.html
}
else
{
_suffix = _path.substr(pos); // 截取后缀名
}
LOG(INFO, "client wang get %s\n", _path.c_str()); // 打印日志
return true; // 解析成功
}
bool PraseHeader() // 解析请求头
{
for (const auto &line : _headers) // 遍历请求头
{
std::string key, value; // 定义键值对
if (PraseHeaderHelper(line, &key, &value))
{
_header.insert(std::make_pair(key, value)); // 插入键值对
}
}
}
void Print() // 打印HTTP请求
{
std::cout << "===" << _req_line << "===" << std::endl; // 打印请求行
for (const auto &header : _headers)
{
std::cout << "***" << header << "***" << std::endl; // 打印请求头
}
std::cout << _Null_String << std::endl; // 打印请求正文;
std::cout << "===" << _resq_text << "===" << std::endl; // 打印响应
std::cout << "method ###" << _method << "###" << std::endl; // 打印请求方法
std::cout << "url ###" << _url << "###" << std::endl; // 打印URL
std::cout << "path ###" << _path << "###" << std::endl; // 打印路径
std::cout << "args ###" << _args << "###" << std::endl; // 打印参数
}
public:
HttpRequest()
{
}
void Serialization() // 序列化HTTP请求
{
}
void DeSerialization(std::string &request) // 反序列化HTTP请求
{
_req_line = GetOneLine(request); // 读取请求行
while (true)
{
std::string line = GetOneLine(request); // 读取请求头
if (line.empty())
{
break; // 读取到空行,则表示请求头结束
}
else if (line == Separator)
{
_resq_text = request; // 读取请求正文;
break; // 读取到空行,则表示请求头结束
}
else
{
_headers.push_back(line); // 读取请求头
}
}
}
std::string Path() // 获取文件路径
{
return _path; // 为什么要获取文件路径?->为了找到文件并返回给客户端
}
std::string Suffix() // 获取文件后缀名
{
return _suffix;
}
std::string Method() // 获取请求方法
{
return _method;
}
std::string Args() // 获取参数
{
return _args;
}
std::string Text()
{
return _resq_text; // 读取请求正文;
}
~HttpRequest()
{
}
private:
std::string _req_line; // 请求行
std::vector<std::string> _headers; // 请求头
std::string _Null_String; // 空行
std::string _resq_text; // 请求正文;
std::unordered_map<std::string, std::string> _header; // 请求头
std::string _method; // 请求方法
std::string _url; // URL
std::string _args; // 参数
std::string _path; // 路径
std::string _suffix; // 后缀名
std::string _version; // HTTP版本
};
class HttpResponse // 定义一个HTTP响应类
{
public:
HttpResponse()
: _version(httpversion),
_code(200),
_Null_String(Separator)
{
}
void AddStatusLine(int code, std::string message) // 添加状态行
{
_code = code;
_message = message; // TODO: 这里需要添加响应信息
_desc = "OK"; // TODO: 这里需要添加描述信息
}
void AddHeader(const std::string &key, const std::string &value) // 添加响应头
{
_header.insert(std::make_pair(key, value)); // 插入响应头
_header[key] = value; // 插入响应头
}
void AddText(const std::string &text) // 添加响应内容
{
_resq_text = text; // 响应内容
}
std::string Serialize() // 序列化HTTP响应
{
std::string _status_line = _version + space + std::to_string(_code) + space + _desc + Separator; // 状态行
for (const auto &header : _header)
{
_headers.push_back(header.first + header_sep + header.second + Separator); // 响应头
}
// 序列化;
std::string respstr = _status_line; // 状态行
for (const auto &header : _headers)
{
respstr += header; // 响应头
}
respstr += _Null_String; // 空行
respstr += _resq_text; // 响应内容
return respstr; // 序列化;
}
~HttpResponse()
{
}
private:
int _code; // 响应代码
std::string _desc; // 响应描述
std::string _message; // 响应信息
std::string _content; // 响应内容
std::string _version; // HTTP版本
std::unordered_map<std::string, std::string> _header; // 响应头
std::string _req_line; // 请求行
std::vector<std::string> _headers; // 请求头
std::string _Null_String; // 空行
std::string _resq_text; // 请求正文;
};
class Factory // 定义一个工厂类
{
public:
// 静态成员函数
// 1.所有对象共享同一个函数
// 2.静态成员函数只能访问静态成员变量
// 3.静态成员函数也有访问权限 写在private里,类外是访问不到的
static std::shared_ptr<HttpRequest> BuildHttpRequest() // 静态工厂函数,用于创建HTTP请求对象
{
return std::make_shared<HttpRequest>(); // 实例化一个HTTP请求对象并返回
}
static std::shared_ptr<HttpResponse> BuildHttpReponse() // 静态工厂函数,用于创建HTTP响应对象
{
return std::make_shared<HttpResponse>(); // 实例化一个HTTP响应对象并返回
}
};
class HttpServer // 定义一个HTTP服务器类
{
public:
HttpServer()
{
_mime_type.insert(std::make_pair(".html", "text/html")); // 定义文件类型
_mime_type.insert(std::make_pair(".css", "text/css")); // 定义文件类型
_mime_type.insert(std::make_pair(".js", "application/x-javascript")); // 定义文件类型
_mime_type.insert(std::make_pair(".png", "image/png")); // 定义文件类型
_mime_type.insert(std::make_pair(".jpg", "image/jpeg")); // 定义文件类型
_mime_type.insert(std::make_pair(".unknown", "text/html")); // 定义文件类型
_code_to_desc.insert(std::make_pair(100, "Continue")); // 定义响应描述
_code_to_desc.insert(std::make_pair(200, "OK")); // 定义响应描述
_code_to_desc.insert(std::make_pair(301, "Moved Permanently")); // 定义响应描述
_code_to_desc.insert(std::make_pair(302, "Found")); // 定义响应描述
_code_to_desc.insert(std::make_pair(404, "Not Found")); // 定义响应描述
_code_to_desc.insert(std::make_pair(500, "Internal Server Error")); // 定义响应描述
}
std::string ReadFileContent(const std::string path, int *Size) // 读取文件内容
{
std::ifstream ifs(path, std::ios::binary); // 打开文件
if (!ifs.is_open())
{
return std::string(); // 打开失败,返回空字符串
}
ifs.seekg(0, ifs.end); // 移动文件指针到文件末尾
int filesize = ifs.tellg(); // 获取文件大小
ifs.seekg(0, ifs.beg); // 移动文件指针到文件开头
std::string content;
content.resize(filesize); // 预分配内存
ifs.read((char *)content.c_str(), filesize);
*Size = filesize; // 保存文件大小
ifs.close(); // 关闭文件
}
std::string HandlerHttpReuqest(std::string request) // 处理HTTP请求的函数
{
#ifdef TEST
std::cout << "HTTP Request: " << request << std::endl; // 打印HTTP请求
std::string response = "HTTP/1.0 200 OK\r\n"; // 200 OK
response += "\r\n"; // 空行
response += "<html><boby><h1>Hello C++!</h1></body></html>"; // 响应内容
return response;
#else
// return "HTTP/1.0 404 Not Found\r\n\r\n<html><body><h1>404 Not Found</h1></body></html>";
std::shared_ptr<HttpRequest> req = Factory::BuildHttpRequest(); // 创建HTTP请求对象
req->DeSerialization(request); // 反序列化HTTP请求
int content_size = 0;
std::string content = ReadFileContent(req->Path(), &content_size); // 读取文件内容
std::string suffix = req->Suffix(); // 获取文件后缀名
std::shared_ptr<HttpResponse> res = Factory::BuildHttpReponse(); // 创建HTTP响应对象
res->AddStatusLine(200, "OK"); // 添加状态行
res->AddHeader("Content-Type", _mime_type[suffix]); // 添加响应头
res->AddHeader("Content-Length", std::to_string(content_size)); // 添加响应头
res->AddHeader("Location", "https://www.baidu.com"); // 添加响应头,重定向
res->AddText(content); // 添加响应内容
return res->Serialize(); // 序列化HTTP响应
int code = 0; // 响应代码
if (req->Path() == "wwwroot/index.html")
{
code = 301; // 重定向
res->AddHeader("Location", "https://www.baidu.com"); // 添加响应头,重定向
res->AddHeader("Content-Type", "text/html"); // 添加响应头
}
else
{
code = 200; // 200 OK
int content_size = 0; // 响应内容大小
std::string text = ReadFileContent(req->Path(), &content_size); // 读取文件内容
res->AddHeader("Content_length", std::to_string(content_size)); // 添加响应头
res->AddText(text);
if (text.empty()) // 如果文件内容为空,则返回404//文件内容为空表示文件不存在
{
code = 404;
res->AddStatusLine(code, _code_to_desc[code]); // 添加状态行
std::string text404 = ReadFileContent("wwwroot/404.html", &content_size); // 读取404页面内容
res->AddHeader("Content-Length", std::to_string(content_size)); // 添加响应头
res->AddHeader("Content-Type", _mime_type[".html"]); // 添加响应头
res->AddText(text404); // 添加响应内容
}
else
{
std::string suffix = req->Suffix(); // 获取文件后缀名
res->AddStatusLine(code, _code_to_desc[code]); // 添加状态行
res->AddHeader("Content-Length", std::to_string(content_size)); // 添加响应头
res->AddText(text); // 添加响应内容
res->AddHeader("Content-Type", _mime_type[suffix]); // 添加响应头
}
return res->Serialize(); // 序列化HTTP响应
}
#endif
}
~HttpServer()
{
}
private:
int _code; // 响应代码
std::string _message; // 响应信息
std::string _content; // 响应内容
std::string _version; // HTTP版本
std::unordered_map<std::string, std::string> _header; // 响应头
std::string _req_line; // 请求行
std::vector<std::string> _headers; // 请求头
std::string _Null_String; // 空行
std::string _resq_text; // 请求正文;
std::unordered_map<std::string, std::string> _mime_type; // _mime_type表示文件类型,为什么要有文件类型->因为HTTP协议是基于文本的协议,所以需要知道文件类型才能知道如何解析
std::unordered_map<int, std::string> _code_to_desc; // _code_to_desc表示响应描述
};
cpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
class InetAddr
{
private:
void GetAddress(std::string *ip, uint16_t *port)
{
*port = ntohs(_addr.sin_port);
*ip = inet_ntoa(_addr.sin_addr);
}
public:
InetAddr(const struct sockaddr_in &addr) : _addr(addr)
{
GetAddress(&_ip, &_port);
}
InetAddr(const std::string &ip, uint16_t port) : _ip(ip), _port(port)
{
_addr.sin_family = AF_INET;
_addr.sin_port = htons(_port);
_addr.sin_addr.s_addr = inet_addr(_ip.c_str());
}
InetAddr()
{
}
std::string Ip()
{
return _ip;
}
bool operator==(const InetAddr &addr)
{
// if(_ip == addr._ip)
if (_ip == addr._ip && _port == addr._port) // 方便测试
{
return true;
}
return false;
}
// bool operator = (const struct sockaddr_in &addr)
// {
// _addr = addr;
// }
struct sockaddr_in Addr()
{
return _addr;
}
uint16_t Port()
{
return _port;
}
~InetAddr()
{
}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
cpp
#ifndef __LOCK_GUARD_HPP__
#define __LOCK_GUARD_HPP__
#include <iostream>
#include <pthread.h>
class LockGuard
{
public:
LockGuard(pthread_mutex_t *mutex) : _mutex(mutex)
{
pthread_mutex_lock(_mutex); // 构造加锁
}
~LockGuard()
{
pthread_mutex_unlock(_mutex);
}
private:
pthread_mutex_t *_mutex;
};
#endif
cpp
#pragma once
#include <iostream>
#include <fstream>
#include <cstdio>
#include <string>
#include <ctime>
#include <cstdarg>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include "LockGuard.hpp"
bool gIsSave = false;
const std::string logname = "log.txt";
// 1. 日志是由等级的
enum Level
{
DEBUG = 0,
INFO,
WARNING,
ERROR,
FATAL
};
void SaveFile(const std::string &filename, const std::string &message)
{
std::ofstream out(filename, std::ios::app);
if (!out.is_open())
{
return;
}
out << message;
out.close();
}
std::string LevelToString(int level)
{
switch (level)
{
case DEBUG:
return "Debug";
case INFO:
return "Info";
case WARNING:
return "Warning";
case ERROR:
return "Error";
case FATAL:
return "Fatal";
default:
return "Unknown";
}
}
std::string GetTimeString()
{
time_t curr_time = time(nullptr);
struct tm *format_time = localtime(&curr_time);
if (format_time == nullptr)
return "None";
char time_buffer[1024];
snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
format_time->tm_year + 1900,
format_time->tm_mon + 1,
format_time->tm_mday,
format_time->tm_hour,
format_time->tm_min,
format_time->tm_sec);
return time_buffer;
}
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
// 2. 日志是由格式的
// 日志等级 时间 代码所在的文件名/行数 日志的内容
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{
std::string levelstr = LevelToString(level);
std::string timestr = GetTimeString();
pid_t selfid = getpid();
char buffer[1024];
va_list arg;
va_start(arg, format);
vsnprintf(buffer, sizeof(buffer), format, arg);
va_end(arg);
std::string message = "[" + timestr + "]" + "[" + levelstr + "]" +
"[" + std::to_string(selfid) + "]" +
"[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer;
LockGuard lockguard(&lock);
// pthread_mutex_lock(&lock);
if (!issave)
{
std::cout << message;
}
else
{
SaveFile(logname, message);
}
// pthread_mutex_lock(&lock); // bug??
// std::cout << levelstr << " : " << timestr << " : " << filename << " : " << line << ":" << buffer << std::endl;
}
// C99新特性__VA_ARGS__
#define LOG(level, format, ...) \
do \
{ \
LogMessage(__FILE__, __LINE__, gIsSave, level, format, ##__VA_ARGS__); \
} while (0)
#define EnableFile() \
do \
{ \
gIsSave = true; \
} while (0)
#define EnableScreen() \
do \
{ \
gIsSave = false; \
} while (0)
// 默认传递进来的参数都是整数
// void Test(int num, ...)
// {
// va_list arg;
// va_start(arg, num);
// while(num)
// {
// int data = va_arg(arg, int);
// std::cout << "data: " << data << std::endl;
// num--;
// }
// va_end(arg); // arg = NULL
// }
cpp
#include "TcpServer.hpp"
#include "Http.hpp"
void Usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " local_port\n"
<< std::endl;
}
std::shared_ptr<HttpResponse> Login(std::shared_ptr<HttpRequest> req)
{
LOG(DEBUG, "=========================\n");
std::string userdata;
if (req->Method() == "GET")
{
userdata = req->Args();
}
else if (req->Method() == "POST")
{
userdata = req->Text(); // 获取请求体
}
else
{
}
// 1. 进程间通信, 比如 pipe! 还有环境变量!
// 2. fork();
// 3. exec(); python / php / java / C++
// 处理数据了
LOG(DEBUG, "enter data handler, data is : %s\n", userdata.c_str());
auto response = Factory::BuildHttpReponse();
response->AddStatusLine(200, "OK");
response->AddHeader("Content-Type", "text/html");
response->AddText("<html><h1>handler data done</h1></html>");
LOG(DEBUG, "=========================\n");
return response;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
return 1;
}
uint16_t port = atoi(argv[1]); // 获取端口号
HttpServer http_service; // 实例化HTTP服务器类
TcpServer tcp_server(port, std::bind(&HttpServer::HandlerHttpReuqest, &http_service, std::placeholders::_1)); // 实例化TCP服务器类,绑定HTTP请求处理函数
return 0;
}
cpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <pthread.h>
#include <sys/types.h>
#include <memory>
#include "InetAddr.hpp"
#include "Log.hpp"
// 模版方法模式
namespace socket_ns
{
class Socket;
const static int gbacklog = 8;
using socket_sptr = std::shared_ptr<Socket>;
enum
{
SOCKET_ERROR = 1,
BIND_ERROR,
LISTEN_ERROR,
USAGE_ERROR
};
// std::unique_ptr<Socket> listensock = std::make_unique<TcpSocket>();
// listensock->BuildListenSocket();
// std::unique_ptr<Socket> clientsock = std::make_unique<TcpSocket>();
// clientsock->BuildClientSocket();
// clientsock->send();
// clientsock->Recv();
class Socket
{
public:
virtual void CreateSocketOrDie() = 0; // 创建套接字
virtual void BindSocketOrDie(InetAddr &addr) = 0; // 绑定本地地址
virtual void ListenSocketOrDie() = 0; // 监听套接字
virtual socket_sptr Accepter(InetAddr *addr) = 0; // 接收客户端连接
virtual bool Connetcor(InetAddr &addr) = 0; // 连接服务器
virtual void SetSocketAddrReuse() = 0; // 设置套接字地址复用
virtual int SockFd() = 0; // 获取套接字描述符
virtual void SetSocketAddrReuse() = 0; // 设置套接字地址复用->为什么要设置套接字地址复用?因为服务器程序在运行时,可能会多次启动,而端口号是临时端口,所以需要设置套接字地址复用,使得端口号可以被重复使用。
virtual int Recv(std::string *out) = 0; // 接收数据
virtual int Send(const std::string &in) = 0; // 发送数据
virtual void Close() = 0;
// virtual void Recv() = 0;
// virtual void Send() = 0;
// virtual void other() = 0;
public:
void BuildListenSocket(InetAddr &addr)
{
CreateSocketOrDie();
SetSocketAddrReuse();
BindSocketOrDie(addr);
ListenSocketOrDie();
}
bool BuildClientSocket(InetAddr &addr)
{
CreateSocketOrDie();
return Connetcor(addr);
}
// void BuildUdpSocket()
// {
// CreateSocketOrDie();
// BindSocketOrDie();
// }
};
class TcpSocket : public Socket
{
public:
TcpSocket(int fd = -1) : _sockfd(fd)
{
}
void CreateSocketOrDie() override
{
// 1. 创建流式套接字
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(FATAL, "socket error");
exit(SOCKET_ERROR);
}
LOG(DEBUG, "socket create success, sockfd is : %d\n", _sockfd);
}
void BindSocketOrDie(InetAddr &addr) override // 绑定本地地址
{
// 2. bind
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(addr.Port());
local.sin_addr.s_addr = inet_addr(addr.Ip().c_str());
int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local)); //_sockfd表示套接字描述符,(struct sockaddr *)&local表示要绑定的地址,sizeof(local)表示地址长度
if (n < 0)
{
LOG(FATAL, "bind error\n");
exit(BIND_ERROR);
}
LOG(DEBUG, "bind success, sockfd is : %d\n", _sockfd);
}
void ListenSocketOrDie() override
{
int n = ::listen(_sockfd, gbacklog);
if (n < 0)
{
LOG(FATAL, "listen error\n");
exit(LISTEN_ERROR);
}
LOG(DEBUG, "listen success, sockfd is : %d\n", _sockfd);
}
socket_sptr Accepter(InetAddr *addr) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = ::accept(_sockfd, (struct sockaddr *)&peer, &len);
if (sockfd < 0)
{
LOG(WARNING, "accept error\n");
return nullptr;
}
*addr = peer;
socket_sptr sock = std::make_shared<TcpSocket>(sockfd);
return sock;
}
bool Connetcor(InetAddr &addr) override
{
// tcp client 要bind,不要显示的bind.
struct sockaddr_in server;
// 构建目标主机的socket信息
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(addr.Port());
server.sin_addr.s_addr = inet_addr(addr.Ip().c_str());
int n = connect(_sockfd, (struct sockaddr *)&server, sizeof(server));
if (n < 0)
{
std::cerr << "connect error" << std::endl;
return false;
}
return true;
}
void SetSocketAddrReuse() override
{
int opt = 1;
::setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
}
int Recv(std::string *out) override
{
char inbuffer[4096];
ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);
if (n > 0)
{
inbuffer[n] = 0;
*out = inbuffer; // ??? +=
}
return n;
}
void SetSocketAddrReuse() override // 设置套接字地址复用->为什么要设置套接字地址复用?因为服务器程序在运行时,可能会多次启动,而端口号是临时端口,所以需要设置套接字地址复用,使得端口号可以被重复使用。
{
int opt = 1; // 1表示开启地址复用
::setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); // 设置套接字地址复用
}
int Send(const std::string &in) override
{
int n = ::send(_sockfd, in.c_str(), in.size(), 0);
return n;
}
int SockFd() override
{
return _sockfd;
}
void Close() override
{
if (_sockfd > -1)
::close(_sockfd);
}
private:
int _sockfd;
};
// class SocketFactor
// {
// public:
// void Build
// }
} // namespace socket_ns
cpp
#include <iostream>
#include <string>
#include <functional>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <memory>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Socket.hpp"
using namespace socket_ns;
class TcpServer;
using http_t = std::function<std::string(std::string reqeust)>;
class ThreadData
{
public:
ThreadData(socket_sptr fd, InetAddr addr, TcpServer *s) : sockfd(fd), clientaddr(addr), self(s)
{
}
public:
socket_sptr sockfd;
InetAddr clientaddr;
TcpServer *self;
};
class TcpServer
{
public:
TcpServer(int port, http_t service)
: _localaddr("0", port), // 本地地址为什么要设置为0?因为0.0.0.0可以表示任何地址,而0.0.0.0:8080表示本机的8080端口
_listensock(std::make_unique<TcpSocket>()),
_http_service(service),
_isrunning(false)
{
_listensock->BuildListenSocket(_localaddr);
}
static void *HandlerSock(void *args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData *>(args);
std::string request, response;
// 有较大的概率,读到的就是一个完整的http request
ssize_t n = td->sockfd->Recv(&request); // 读取请求
if (n > 0)
{
response = td->self->_http_service(request); // self表示TcpServer对象
td->sockfd->Send(response); // 发送应答
}
td->sockfd->Close();
delete td;
return nullptr;
}
void Loop()
{
_isrunning = true;
while (_isrunning)
{
InetAddr peeraddr; // 客户端地址
socket_sptr normalsock = _listensock->Accepter(&peeraddr); // 接受连接
if (normalsock == nullptr)
{
continue;
}
pthread_t t; // 线程
ThreadData *td = new ThreadData(normalsock, peeraddr, this); // 创建线程数据
}
}
void Loop() // 启动服务
{
_isrunning = true;
// 4. 不能直接接受数据,先获取连接
while (_isrunning)
{
InetAddr peeraddr;
socket_sptr normalsock = _listensock->Accepter(&peeraddr);
if (normalsock == nullptr)
continue;
pthread_t t;
ThreadData *td = new ThreadData(normalsock, peeraddr, this);
pthread_create(&t, nullptr, HandlerSock, td); // 将线程分离
}
_isrunning = false;
}
~TcpServer()
{
}
private:
InetAddr _localaddr;
std::unique_ptr<Socket> _listensock;
bool _isrunning;
http_t _http_service;
};