抓包工具
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include "Socket.hpp"
#include "logs/ljwlog.h"
using namespace std;
static const int dafaultport = 8080;
struct ThreadData
{
int sockfd;
};
class HttpServer
{
public:
HttpServer(uint16_t port = dafaultport):port_(port)
{}
bool Start()
{
listensock_.Socket();
listensock_.Bind(port_);
listensock_.Listen();
for(;;)
{
string clientip;
uint16_t clientport;
int sockfd = listensock_.Accept(&clientip, &clientport);
pthread_t tid;
ThreadData *td = new ThreadData();
td->sockfd = sockfd;
pthread_create(&tid, nullptr, ThreadRun, td);
}
}
//把收到的信息打印出来
static void *ThreadRun(void *args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData*>(args);
char buffer[10240];
//跟read用法很像
ssize_t n = recv(td->sockfd, buffer, sizeof(buffer), 0);
if(n > 0)
{
buffer[n] = 0;
cout<< buffer;
}
close(td->sockfd);
delete td;
return nullptr;
}
~HttpServer()
{}
private:
uint16_t port_;
Sock listensock_;
};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" const="width=device-width,initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>hello world</h1>
</body>
</html>
更改一下写的网页,服务器不用关,浏览器刷新一下就可以
stringstream流式分隔符:stringstream - C++ Reference (cplusplus.com)
先开启服务器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" const="width=device-width,initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>hello world</h1>
<h1>hello world</h1>
<h1>hello world</h1>
<h1>hello world</h1>
<h1>hello world</h1>
<h1>hello world</h1>
<a href="https://blog.csdn.net/2401_83427936?spm=1000.2115.3001.5343">Ljw的博客链接</a>
</body>
</html>
在这之间输入
get后会把自己输入的账号密码放进链接后面了
在 HTTP协议 中,长连接和短连接的概念主要体现在连接的持久性上。具体来说,HTTP协议的长短连接关系到客户端和服务器之间的连接是否保持持久,连接是否在多个请求之间复用。
在HTTP/1.0中,默认情况下每个请求都建立一个新的连接,通信完成后连接就会立即关闭。这种方式被称为 短连接。每当客户端发起一个HTTP请求时,服务器会为这个请求创建一个新的TCP连接,并在响应发送完毕后立即关闭这个连接。对于每个请求和响应,都会有一次建立连接、传输数据和断开连接的过程。
短连接的特点:
假设你使用C/C++编写一个HTTP客户端发起HTTP请求并接收响应:
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#define PORT 80
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
std::cerr << "Socket creation failed!" << std::endl;
return -1;
}
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr("93.184.216.34"); // example.com IP
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
std::cerr << "Connection failed!" << std::endl;
return -1;
}
const char *request = "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n";
send(sockfd, request, strlen(request), 0);
char buffer[1024];
int bytes_received = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
buffer[bytes_received] = '\0';
std::cout << "Response:\n" << buffer << std::endl;
close(sockfd); // Connection is closed after response
return 0;
}
在这个短连接示例中,我们使用HTTP/1.0协议,每次请求完毕后,TCP连接会立即关闭。
HTTP/1.1引入了 长连接(Keep-Alive) 的概念,在这种模式下,连接不会在每个请求后关闭,而是保持打开状态,可以复用相同的连接来处理多个请求。这意味着客户端和服务器之间的连接可以用于多个请求和响应,直到明确关闭连接。
默认情况下,HTTP/1.1会使用长连接,但可以通过设置请求头 Connection: close
来显式关闭连接。
长连接的特点:
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#define PORT 80
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
std::cerr << "Socket creation failed!" << std::endl;
return -1;
}
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr("93.184.216.34"); // example.com IP
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
std::cerr << "Connection failed!" << std::endl;
return -1;
}
// Send multiple requests over the same connection
const char *request1 = "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: keep-alive\r\n\r\n";
send(sockfd, request1, strlen(request1), 0);
char buffer[1024];
int bytes_received = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
buffer[bytes_received] = '\0';
std::cout << "Response 1:\n" << buffer << std::endl;
const char *request2 = "GET /about HTTP/1.1\r\nHost: example.com\r\nConnection: keep-alive\r\n\r\n";
send(sockfd, request2, strlen(request2), 0);
bytes_received = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
buffer[bytes_received] = '\0';
std::cout << "Response 2:\n" << buffer << std::endl;
close(sockfd); // Connection can be kept alive, but we close it here
return 0;
}
在这个长连接的例子中,客户端向服务器发送两个HTTP请求,两个请求共享同一个TCP连接,直到手动关闭连接。通过在请求头中加入 Connection: keep-alive
,客户端表明希望连接保持活跃。
长连接有助于减少连接的建立与关闭的开销,提高通信效率,尤其是在大量小请求的场景中,而短连接则适合一次性的请求和响应模式。
文件后缀
文件级:可以记住一段时间
内存级:关闭就忘记
虽然我们说, 应用层协议是我们程序猿自己定的.
但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)
就是其中之一.
平时我们俗称的 "网址" 其实就是说的 URL
像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
例如:
"+" 被转义成了 "%2B" urldecode就是urlencode的逆过程;
工具:URL 编码和解码 - 在线 (urlencoder.org)
HTTP请求
HTTP响应
其中最常用的就是GET方法和POST方法.
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
实现一个最简单的HTTP服务器, 只在网页上输出 "hello world"; 只要我们按照HTTP协议的要求构造数据, 就很容易 能做到;
HttpServer.cc
#include "HttpServer.hpp"
#include<memory>
#include<iostream>
using namespace std;
void Usage(const string& proc)
{
cout<<"\nUsage" << proc << "port\n\n" <<endl;
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t port = stoi(argv[1]);
unique_ptr<HttpServer> svr(new HttpServer(port));
svr->Start();
return 0;
}
HttpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <pthread.h>
#include <sstream>
#include <vector>
#include "Socket.hpp"
#include "logs/ljwlog.h"
using namespace std;
static const int dafaultport = 8080;
const string wwwroot = "./wwwroot";//web根目录
const string sep = "\r\n";
//首页
const string homepage = "index.html";
class ThreadData
{
public:
ThreadData(int fd): sockfd(fd)
{}
public:
int sockfd;
};
class HttpRequest
{
public:
//反序列化 这里把一个字符串变成多个字符串
void Deserialize(string req)
{
while(true)
{
//找一行移动一行
// 分隔符
size_t pos = req.find(sep);
if(pos == string::npos)
{
break;
}
string temp = req.substr(0, pos);
//读到空行就直接跳出
if(temp.empty()) break;
req_header.push_back(temp);
req.erase(0, pos + sep.size());
}
text = req;
}
void DebugPrint()
{
cout<<"-----------------------"<<endl;
for(auto& line:req_header)
{
cout<< line << "\n\n";
}
cout<< "method: " << method << endl;
cout<< "url" << url <<endl;
cout<< "http_version" << http_version <<endl;
cout << text;
}
//解析第一行 vector 0号下标里的字符串
void Parse()
{
stringstream ss(req_header[0]);
ss >> method >> url >> http_version;
file_path = 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
}
}
public:
vector<string> req_header;//把每一行push到req_header
string text;//正文
//解析后的结果 vector 0号下标里的字符串
string method;
string url;
string http_version;
string file_path;
};
class HttpServer
{
public:
HttpServer(uint16_t port = dafaultport):port_(port)
{}
bool Start()
{
listensock_.Socket();
listensock_.Bind(port_);
listensock_.Listen();
for(;;)
{
string clientip;
uint16_t clientport;
int sockfd = listensock_.Accept(&clientip, &clientport);
if(sockfd < 0) continue;
pthread_t tid;
ThreadData *td = new ThreadData(sockfd);
pthread_create(&tid, nullptr, ThreadRun, td);
}
}
static string ReadHtmlContent(const string& htmlpath)
{
ifstream in(htmlpath);
if(!in.is_open()) return "404";
string content;
string line;
while(getline(in, line))
{
content += line;
}
in.close();
return content;
}
static void HandlerHttp(int sockfd)
{
char buffer[10240];
//跟read用法很像
ssize_t n = recv(sockfd, buffer, sizeof(buffer), 0);
if(n > 0)
{
buffer[n] = 0;
cout<< buffer; //假设我们读到的是一个完整的请求,独立的Http请求
HttpRequest req;
req.Deserialize(buffer);
req.Parse();//解析
//req.DebugPrint();
// //返回相应的过程
string text = ReadHtmlContent(req.file_path);
string response_line = "HTTP/1.0 200 OK\r\n ";
string response_header = "Content-Length ";
response_header = to_string(text.size());
response_header += "\r\n";
string blank_line = "\r\n";
string response = response_line;
response += response_header;
response += blank_line;
response += text;
//和write类似
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);
delete td;
return nullptr;
}
~HttpServer()
{}
private:
uint16_t port_;
Sock listensock_;
};
makefile
httpServer:HttpServer.cc
g++ -g -o $@ $^ -std=c++11 -lpthread
.PHONT:clean
clean:
rm -f httpServer
Socket.hpp
#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <cstring>
#include "./logs/ljwlog.h"
using namespace std;
enum
{
SocketErr = 2,
BindErr,
ListenErr
};
const int backlog = 10;
class Sock
{
public:
Sock()
{
}
~Sock()
{
}
public:
void Socket() // 创建套接字的接口
{
sockfd_ = socket(AF_INET, SOCK_STREAM, 0); // 流式套接字 第二个参数是协议类型
if (sockfd_ < 0)
{
FATAL("Socket errno,error:%d,errstring:%s", errno, strerror(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; // ip默认0.0.0.0
if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0)
{
FATAL("Bind errno,error:%d,errstring:%s", errno, strerror(errno));
exit(BindErr);
}
}
void Listen() // 监听状态的接口
{
if (listen(sockfd_, backlog) < 0)
{
FATAL("Listen errno,error:%d,errstring:%s", errno, strerror(errno));
exit(ListenErr);
}
}
// 知道谁链接的我
int Accept(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)
{
WARN("accept error, %s: %d", strerror(errno), errno);
return -1;
}
// 网络转主机
// 拿出客户端的ip和端口号
char ipstr[64];
inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr)); // 网络转主机
*clientip = ipstr; // 网络转主机
*clientport = ntohs(peer.sin_port); // 网络转主机
return newfd;
}
void Close()
{
close(sockfd_);
}
int Connect(const string &ip, const uint16_t &port) // 方便两个客户端和服务器都能使用这个Sock的这个公共方法
{
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)
{
cerr << "connect to" << ip << "::" << port <<"error"<< endl;
return false;
}
return true;
}
int Fd()
{
return sockfd_;
}
private:
int sockfd_;
};
wwwroot/a/b/hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" const="width=device-width,initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>第二个网页</h1>
<h1>第二个网页</h1>
<h1>第二个网页</h1>
<h1>第二个网页</h1>
<h1>第二个网页</h1>
<h1>第二个网页</h1>
</body>
</html>
wwwroot/image
wwwroot/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" const="width=device-width,initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- <form action="/a/b/hello.html" method="post">
name: <input type="text" name="name"><br>
password: <input type="password" name="password"><br>
<input type="submit" value="提交">
</form> -->
<h1>这是我们的首页</h1>
<img src="image/OIP-C.jpg" alt="这是一个咖啡">
</body>
</html>
<!-- <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" const="width=device-width,initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>hello world</h1>
<h1>hello world</h1>
<h1>hello world</h1>
<h1>hello world</h1>
<h1>hello world</h1>
<h1>hello world</h1>
<a href="https://blog.csdn.net/2401_83427936?spm=1000.2115.3001.5343">Ljw的博客链接</a>
<a href="https://101.34.66.193:8080/a/b/hello.html">第二个网页</a>
</body>
</html> -->
wwwroot/x/y/world.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" const="width=device-width,initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>第三个网页</h1>
<h1>第三个网页</h1>
<h1>第三个网页</h1>
<h1>第三个网页</h1>
<h1>第三个网页</h1>
<h1>第三个网页</h1>
</body>
</html>