HTTP 代理服务器的设计与实现(C++)
實驗內容
- 設計并實現一個基本 HTTP 代理服務器。要求在指定端口(例如 8080)接收來自客戶的 HTTP 請求并且根據其中的 URL 地址訪問該地址 所指向的 HTTP 服務器(原服務器),接收 HTTP 服 務器的響應報文,并 將響應報文轉發給對應的客戶進行瀏覽。
- 設計并實現一個支持 Cache 功能的 HTTP 代理服務器。要求能緩 存原服務器響應的對象,并 能夠通過修改請求報文(添加 if-modified-since 頭行),向原服務器確認緩存對象是否是最新版本。 (選作內容)
- 擴展 HTTP 代理服務器,支持如下功能: (選作內容)
- 網站過濾:允許/不允許訪問某些網站;
- 用戶過濾:支持/不支持某些用戶訪問外部網站;
- 網站引導:將用戶對某個網站的訪問引導至一個模擬網站(釣魚)。
代理服務器的概念
代理服務器,允許一個網絡終端(一般為客戶端)通過這個服務與另一 個網絡終端(一般為服務器)進行非直接的連接。普通 Web 應用通信方式與采用代理服務器的 通信方式的對比如下圖所示:
代理服務器在指定端口(本實驗中所指定的是666端口)監聽瀏覽器的訪問請求(需要在客戶端瀏覽器進行相應的設置),接收到瀏覽器對遠程網站的瀏覽請求時,代理服務器開始在代理服務器的緩存中檢索 URL 對應的對象(網頁、 圖像等對象),找到對象文件后,提取該對象文件的最新被修改時間;代理服務器程序在客戶的請求報文首部插入,并向原 Web 服務器轉發修改后的請求報文。如果代理服務器沒有該對象的緩存,則會直接向原服務器轉發請求報文,并將原服務器返回的響應直接轉發給客戶端,同時將對象緩存到代理服務器中。代理服務器程序會根據緩存的時間、大小和提取記錄等對緩存進行清理。
代碼結構
代碼*實現 3個類,分別為WebsiteDetector類、Cache類和HttpProxyServer類。
WebsiteDetector類:
該類實現了網站過濾和網站引導功能。通過構造函數直接靜態設置了釣魚網站和屏蔽的網站:
WebsiteDetector::WebsiteDetector()
{
AddValidURL("http://jwc.hit.edu.cn/","http://jwts.hit.edu.cn/");
AddBlockedURL("http://xltj.hit.edu.cn/");
}
可知,屏蔽了心理網站。將教務處網站引導到本科教學管理與服務平臺。
Cache類
該類在當前目錄下創建文件夾.cache/,在其中存儲瀏覽緩存對象。同時該類中,保存著對象與文件名的映射關系,對象和LastModified字段的映射關系。
class Cache {
public:
std::string GetDate(const std::string& url); // 獲取url對應保存的LastModified字段
bool Get(const std::string& url, char* response, size_t& start, size_t& responseSize); // 讀取緩存
bool Put(const std::string& url, const char* response, size_t responseSize, size_t& start); // 保存緩存
private:
? std::string cacheDirectory_; // 存放緩存的文件目錄
? std::map<std::string, std::string> cacheMap_; // 對象和LastModified字段的映射關系
? std::map<std::string, std::string> fileMap_; / 對象與文件名的映射關系
? std::mutex mutex_; // 多線程同時讀寫文件的互斥鎖
};
HttpProxyServer類
該類是代理服務器的實現類。是一個多用戶代理服務器。首先該類創建HTTP代理服務的TCP主套接字,該套接字監聽等待客戶端的連接請求。當客戶端連接之后,創建一個子線程,由子線程行上述一對一的代理過程,服務結束之后子線程終止。
class HttpProxyServer
{
public:
HttpProxyServer(int port); // 構造函數,參數為端口號
void Start(); // 監聽客戶端連接請求
private:
int serverSocket_; // 代理服務Socket
int port_; // 端口號
struct sockaddr_in serverAddr_; // 代理服務地址
Cache cache_; // Cache類
WebsiteDetector websiteDetector_; // websiteDetector類
void HandleClient(int clientSocket); // 子線程調用函數
std::string ExtractUrl(const std::string &httpRequest); // 解析URL
int CreateServerSocket(const std::string &host); // 創建與原服務器連接的Socket
bool ServerToClient(const std::string &url, int clientSocket); // 轉發數據
void ParseUrl(const std::string &url, std::string &host, std::string &path); // 解析主機名與路徑名
};
程序基本流程
(1) 初始化服務器Socket,監聽等待客戶端的連接請求。
(2) 當客戶端連接后,創建子線程處理請求。
(3) 子線程接收請求,解析HTTP請求的首部行和請求頭。然后提取Url,Url作為參數通過websiteDetector類判斷是否屏蔽或者引導。
(4) 然后進入轉發的過程,首先進行域名解析,然后創建Socket先原服務器發送請求,接收響應,將數據轉發到客戶端。
(5) 在轉發的過程中,涉及保存緩存和讀取緩存。
網站引導功能
利用首部行中的location字段,實現引導。
std::string locationResponse = std::string("HTTP/1.1 302 Found") + MY_CRLF + "Location: " + newUrl + MY_CRLF + MY_CRLF;
send(clientSocket, locationResponse.c_str(), locationResponse.size(), 0);
用戶過濾功能
設置服務器地址信息時實現。
serverAddr_.sin_addr.s_addr = INADDR_ANY;
// serverAddr_.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //只允許本機用戶訪問服務器
Cache功能
1. 代理服務器處理客戶端請求時,對于第一次出現的對象,會保存下。當客戶端再次請求時,代理服務器就會在請求中添加If-Modified-Since首部行。
date = cache_.GetDate(url);
std::string cacheRequest = httpRequest + "If-Modified-Since: " + date + MY_CRLF + MY_CRLF;
- 發送該請求后,等待原服務器響應,并判斷是否回應304狀態碼。
if (IsResponseNotModified(responseNotModified) ) {
? // std::cout << "304 Not Modified" << std::endl;
? sel = false;
}else {
? cache_.ClearFileContent(url); //清空
? sel = true;
}
- sel為false時,則讀取Cache轉發到客戶端。若為true,則發送HTTP請求到原服務器,再接收響應,轉發到客戶端,再保存到Cache。
修改Chrome瀏覽器代理配置
--proxy-server="http://127.0.0.1:666"
VScode編譯運行
?
該代理服務器成功在666端口啟動,并輸出了cache目錄。
驗證
驗證基礎的代理功能
訪問今日哈工大網站:http://today.hit.edu.cn
可以看到,網站資源順利加載,輸出欄中,輸出了請求的各個資源對象的url。
驗證網站引導功能
輸入網址:http://jwc.hit.edu.cn/
最后直接跳轉到到了,http://jwts.hit.edu.cn/
驗證網站過濾功能
輸入網址:http://xltj.hit.edu.cn/
可以看到,無法訪問。
驗證用戶過濾功能
驗證Cache功能
將在Cache中的資源 http://jwts.hit.edu.cn/resources/css/common/ydy.css ,修改一下。
把色彩均改為紅色,再次訪問 http://jwts.hit.edu.cn/
可以看到,字體顏色變為紅色。可知,HTTP代理服務器這次使用的是Cache中的資源。
源代碼
點擊查看代碼
//g++ your_code.cpp -o your_executable -lws2_32
#include <fstream>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <thread>
#include <vector>
#include <mutex>
#include <list>
#include <map>
#include <sstream>
#include <winsock2.h>
#include <WS2tcpip.h>
#define MAX_CLIENTS 6
#define BUFSIZE 655360
#define HEADSIZE 128
#define MY_CRLF "\r\n"
class WebsiteDetector {
public:
WebsiteDetector()
{
AddValidURL("http://jwc.hit.edu.cn/", "http://jwts.hit.edu.cn/");
AddBlockedURL("http://xltj.hit.edu.cn/");
}
// 釣魚
std::string IsURLPhishing(const std::string& url) {
auto it = validURLs_.find(url);
if (it != validURLs_.end()) {
return it->second;
} else {
return "Phishing";
}
}
// 屏蔽
bool IsURLBlocked(const std::string& url) {
for (const std::string& blockedURL : blockedURLs_) {
if (url.find(blockedURL) != std::string::npos) {
return true;
}
}
return false;
}
private:
std::map<std::string, std::string> validURLs_;
std::vector<std::string> blockedURLs_;
void AddValidURL(const std::string& srcURL, const std::string& dstURL)
{
validURLs_[srcURL] = dstURL;
}
void AddBlockedURL(const std::string& url) {
blockedURLs_.push_back(url);
}
};
class Cache {
public:
Cache() : cacheDirectory_("H:\\cppwork\\CS-networking\\.cache") {
std::cout << cacheDirectory_ << std::endl;
std::system(("mkdir -p " + cacheDirectory_).c_str());
}
bool Check(const std::string& url) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = cacheMap_.find(url);
if (it != cacheMap_.end())
{
return true;
}
return false;
}
// 清空文件內容
bool ClearFileContent(const std::string& url) {
std::lock_guard<std::mutex> lock(mutex_);
// Generate a unique filename based on the URL
std::string fileName = GetFileNameFromUrl(url);
auto it = fileMap_.find(fileName);
std::string fileTag = it->second;
std::string filePath = cacheDirectory_ + "\\" + fileTag;
// 打開文件并使用 std::ios::trunc 模式來清空文件內容
std::ofstream file(filePath, std::ios::out | std::ios::trunc);
if (!file) {
std::cerr << "無法打開文件:" << filePath << std::endl;
return false;
}
// 關閉文件
file.close();
return true;
}
std::string GetDate(const std::string& url)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = cacheMap_.find(url);
return it->second;
}
bool Get(const std::string& url, char* response, size_t& start, size_t& responseSize) {
std::lock_guard<std::mutex> lock(mutex_);
// Generate a unique filename based on the URL
std::string fileName = GetFileNameFromUrl(url);
std::string fileTag = fileMap_[fileName];
std::cout << "Get() url: " << url << std::endl;
std::cout << "Get() fileTag: " << fileTag << std::endl;
// If found, read the response from the file
std::ifstream file(cacheDirectory_ + "\\" + fileTag, std::ios::binary);
if (file) {
file.seekg(start, std::ios::beg);
file.read(response, BUFSIZE);
// Get the number of bytes read in this chunk
size_t bytesRead = static_cast<size_t>(file.gcount());
start += bytesRead;
responseSize = bytesRead;
response[bytesRead] = '\0';
file.close();
return true;
}
return false; // URL not found in the cache
}
bool Put(const std::string& url, const char* response, size_t responseSize, size_t& start) {
std::lock_guard<std::mutex> lock(mutex_);
// Generate a unique filename based on the URL
std::string fileName = GetFileNameFromUrl(url);
auto it = fileMap_.find(fileName);
std::string fileTag;
if (it == fileMap_.end())
{
fileTag = std::to_string(cnt);
fileMap_[fileName] = fileTag;
cnt ++;
}else
{
fileTag = it->second;
}
// Store the response in a file
std::ofstream file(cacheDirectory_ + "\\" + fileTag, std::ios::binary | std::ios::app );
if (!file) {
fprintf(stderr, "file open error: %s(errno: %d)\n", strerror(errno),errno);
return false; // Unable to open file for writing
}
file.seekp(start);
file.write(response, responseSize);
file.close();
start += responseSize;
return true; // Failed to store response in the cache
}
void PutDate(const std::string& url)
{
std::lock_guard<std::mutex> lock(mutex_);
// Generate a unique filename based on the URL
std::string fileName = GetFileNameFromUrl(url);
std::string fileTag = fileMap_[fileName];
// 拼接完整的文件路徑
std::string filePath = cacheDirectory_ + "\\" + fileTag;
// 打開文件并讀取 Last-Modified 首部內容
std::ifstream file(filePath);
if (!file) {
fprintf(stderr, "file open error: %s(errno: %d)\n", strerror(errno),errno);
}
std::string line;
while (std::getline(file, line)) {
// 查找包含 Last-Modified 首部的行
if (line.find("Last-Modified:") != std::string::npos) {
// 提取 Last-Modified 的值并存儲到 cacheMap_
size_t startPos = line.find(":") + 2;
size_t endPos = line.find(MY_CRLF);
std::string date = line.substr(startPos, endPos);
// std::cout << "line: " << line << std::endl;
// std::cout << "date: " << date << std::endl;
cacheMap_[url] = date;
break; // 找到后可以退出循環
}else
{
if (line == MY_CRLF)
{
break;
}
}
}
file.close();
}
private:
std::string cacheDirectory_;
std::map<std::string, std::string> cacheMap_;
std::map<std::string, std::string> fileMap_;
std::mutex mutex_;
int cnt = 1;
std::string GetFileNameFromUrl(const std::string& url) {
// Replace characters in the URL to create a valid filename
std::string fileName = url;
for (char& c : fileName) {
if (c == '/' || c == '?' || c == '&' || c == '=') {
c = '_';
}
}
return fileName;
}
};
// 定義HTTP代理服務器類
class HttpProxyServer
{
public:
HttpProxyServer(int port) : port_(port)
{
// 初始化服務器
// 創建主套接字并綁定端口
serverSocket_ = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket_ == -1)
{
fprintf(stderr, "Constructor(): create socket error: %s(errno: %d)\n", strerror(errno),errno);
exit(EXIT_FAILURE);
}
// 設置服務器地址信息
// 初始化 serverAddr_
memset(&serverAddr_, 0, sizeof(serverAddr_));
serverAddr_.sin_family = AF_INET;
serverAddr_.sin_addr.s_addr = INADDR_ANY;
// serverAddr_.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //只允許本機用戶訪問服務器
serverAddr_.sin_port = htons(port_);
// 綁定套接字到指定端口
if (bind(serverSocket_, (struct sockaddr *)&serverAddr_, sizeof(serverAddr_)) == -1)
{
fprintf(stderr, "Constructor(): bind socket error: %s(errno: %d)\n",strerror(errno), errno);
closesocket(serverSocket_);
exit(EXIT_FAILURE);
}
// 開始監聽客戶端連接請求
if (listen(serverSocket_, MAX_CLIENTS) == -1)
{
fprintf(stderr, "Constructor(): listen socket error: %s(errno: %d)\n",strerror(errno),errno);
closesocket(serverSocket_);
exit(EXIT_FAILURE);
}
std::cout << "Proxy server started on port " << port_ << std::endl;
}
void Start()
{
// 啟動服務器,監聽客戶端連接請求
while (true)
{
struct sockaddr_in clientAddr;
int clientAddrLen = sizeof(struct sockaddr);
// 接受客戶端連接
int clientSocket = accept(serverSocket_, (struct sockaddr *)&clientAddr, &clientAddrLen);
if (clientSocket == INVALID_SOCKET)
{
fprintf(stderr, "Start(): accept socket error: %s(errno: %d)",strerror(errno),errno);
continue; // 繼續等待下一個連接
}
// std::cout << "Start(): Accepted a client connection" << std::endl;
// 創建子線程處理客戶端請求
std::thread clientThread(&HttpProxyServer::HandleClient, this, clientSocket);
clientThread.detach(); // 不等待
}
}
private:
int serverSocket_;
int port_;
struct sockaddr_in serverAddr_;
Cache cache_;
WebsiteDetector websiteDetector_;
void HandleClient(int clientSocket)
{
// 讀取客戶端的HTTP請求
char buffer[BUFSIZE];
memset(buffer, 0, BUFSIZE);
ssize_t bytesRead = recv(clientSocket, buffer, BUFSIZE - 1, 0);
if (bytesRead == -1)
{
perror("HandleClient(): Error reading from client socket");
closesocket(clientSocket);
return;
}
// 解析請求,提取URL
std::string request(buffer);
std::string url = ExtractUrl(request);
std::cout << "<" << url << ">" << std::endl;
// Website Filter; User Filter ; Website phishing
if (websiteDetector_.IsURLBlocked(url))
{
std::cout << "Url Blocked Success: " << url << std::endl;
}else
{
std::string newUrl = websiteDetector_.IsURLPhishing(url);
if (newUrl == "Phishing")
{
// 向服務端請求,向客戶端發送
if( ServerToClient(url, clientSocket) )
{
std::cout << "Transmit Success!" << std::endl;
}else
{
std::cout << "Transmit Fail!" << std::endl;
}
}else
{
std::cout << "Phishing" << std::endl;
std::string locationResponse = std::string("HTTP/1.1 302 Found") + MY_CRLF + "Location: " + newUrl + MY_CRLF + MY_CRLF;
std::cout << locationResponse << std::endl;
send(clientSocket, locationResponse.c_str(), locationResponse.size(), 0);
}
}
std::cout << "----------------------" << std::endl;
// 關閉連接
closesocket(clientSocket);
}
// 提取URL
std::string ExtractUrl(const std::string &httpRequest)
{
std::string url;
// Debug
// std::cout << "ExtractUrl(): httpRequest = " << std::endl << httpRequest << std::endl;
// 在HTTP請求中查找"GET ",通常URL緊隨其后
size_t getPos = httpRequest.find("GET ");
if (getPos != std::string::npos)
{
// 找到"GET "后,查找下一個空格,該空格之后是URL
size_t spacePos = httpRequest.find(' ', getPos + 4);
if (spacePos != std::string::npos)
{
url = httpRequest.substr(getPos + 4, spacePos - (getPos + 4));
}
}
return url;
}
void ParseUrl(const std::string &url, std::string &host, std::string &path)
{
// 查找 URL 中的 "http://",并獲取其后的部分
size_t httpPos = url.find("http://");
if (httpPos != std::string::npos)
{
std::string urlWithoutHttp = url.substr(httpPos + 7); // 7 是 "http://" 的長度
// 查找 "/",分隔主機名和路徑
size_t slashPos = urlWithoutHttp.find('/');
if (slashPos != std::string::npos)
{
host = urlWithoutHttp.substr(0, slashPos);
path = urlWithoutHttp.substr(slashPos);
}
else
{
// 如果沒有找到 "/",則整個剩余部分都是主機名
host = urlWithoutHttp;
path = "/";
}
}
else
{
// 如果沒有 "http://" 前綴,則默認協議為 HTTP,整個 URL 都是主機名
host = url;
path = "/";
}
// Debug
// std::cout << "url: " + url << std::endl;
// std::cout << "host: " + host << std::endl;
// std::cout << "path: " + path << std::endl;
}
int CreateServerSocket(const std::string &host)
{
// 域名解析
addrinfo* result = NULL;
addrinfo hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; // 使用IPv4地址
hints.ai_socktype = SOCK_STREAM;
if (getaddrinfo(host.c_str(), "http", &hints, &result) != 0)
{
fprintf(stderr, "CreateServerSocket(): Failed to resolve the host: %s\n", host.c_str());
return -1; // 返回-1表示連接失敗
}
// 創建Socket
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == -1)
{
fprintf(stderr, "CreateServerSocket(): create socket error: %s(errno: %d)\n", strerror(errno), errno);
freeaddrinfo(result); // 釋放內存
return -1; // 返回-1表示連接失敗
}
// 設置服務器地址信息
struct sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(80); // 設置端口號為80,可以根據需要修改
serverAddr.sin_addr.s_addr = ((struct sockaddr_in *)(result->ai_addr))->sin_addr.s_addr;
// 連接到原服務器
if (connect(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1)
{
fprintf(stderr, "CreateServerSocket(): connect error: %s(errno: %d)\n",strerror(errno),errno);
closesocket(serverSocket); // 在Windows中使用closesocket關閉套接字
freeaddrinfo(result); // 釋放內存
return -1; // 返回-1表示連接失敗
}
freeaddrinfo(result); // 釋放內存
return serverSocket; // 返回連接成功的套接字描述符
}
bool ServerToClient(const std::string &url, int clientSocket)
{
// 解析URL,獲取主機名和路徑
std::string host, path;
ParseUrl(url, host, path);
// 創建Socket連接到原服務器
int serverSocket = CreateServerSocket(host);
if (serverSocket == -1)
{
return FALSE; // 處理連接失敗的情況
}
// 構建HTTP請求
std::string httpRequest = "GET " + path + " HTTP/1.1" + MY_CRLF + "Host: " + host + MY_CRLF + "Connection: close" + MY_CRLF;
std::string date;
bool sel;
if (cache_.Check(url))
{
sel = false;
date = cache_.GetDate(url);
std::string cacheRequest = httpRequest + "If-Modified-Since: " + date + MY_CRLF + MY_CRLF;
// 發送HTTP, 帶有If-Modified-Since 首部行
if (send(serverSocket, cacheRequest.c_str(), cacheRequest.size(), 0) == -1)
{
perror("Error sending request to server");
closesocket(serverSocket);
return FALSE;
}
std::string cacheResponse;
char cacheBuffer[HEADSIZE];
ssize_t cacheBytesRead;
cacheBytesRead = recv(serverSocket, cacheBuffer, HEADSIZE - 1, 0);
std::string responseNotModified(cacheBuffer);
// std::cout << "responseNotModified: " << responseNotModified << std::endl;
if (IsResponseNotModified(responseNotModified) )
{
// std::cout << "304 Not Modified" << std::endl;
sel = false;
}else
{
cache_.ClearFileContent(url); //清空
sel = true;
}
}else
{
sel = true;
}
if (sel == false)
{
// std::cout << "cache hit!" << std::endl;
// 接收緩存,轉發到客戶端
char buffer[BUFSIZE];
size_t start = 0;
size_t bytesRead;
while (1)
{
if (cache_.Get(url, buffer, start, bytesRead) == false)
{
perror("Error sending response to client");
}
// std::cout << "bytesRead: " << bytesRead << std::endl;
if (bytesRead == 0) break;
if (send(clientSocket, buffer, bytesRead, 0) == -1)
{
perror("Error sending response to client");
closesocket(serverSocket);
return FALSE;
}
}
}else
{
httpRequest += MY_CRLF;
// 發送HTTP請求到原服務器
if (send(serverSocket, httpRequest.c_str(), httpRequest.size(), 0) == -1)
{
perror("Error sending request to server");
closesocket(serverSocket);
return FALSE;
}
// 接收原服務器的HTTP響應
char buffer[BUFSIZE];
size_t start = 0;
ssize_t bytesRead;
while ((bytesRead = recv(serverSocket, buffer, BUFSIZE - 1, 0)) > 0)
{
buffer[bytesRead] = '\0';
// 發送接收到的數據到客戶端
if (send(clientSocket, buffer, bytesRead, 0) == -1)
{
perror("Error sending response to client");
closesocket(serverSocket);
return FALSE;
}
if(cache_.Put(url, buffer, bytesRead, start) == false)
{
std::cerr << "Cache put error" << std::endl;
}
}
cache_.PutDate(url);
if (! cache_.Check(url))
{
cache_.ClearFileContent(url);
}
}
// 關閉原服務器連接
closesocket(serverSocket);
return TRUE;
}
bool IsResponseNotModified(const std::string& response) {
// 查找第一個空格,定位到狀態碼的開始
size_t spacePos = response.find(' ');
if (spacePos != std::string::npos) {
// 提取狀態碼部分
std::string statusCode = response.substr(spacePos + 1, 3);
// 檢查狀態碼是否為 "304"
return (statusCode == "304"); // HTTP/1.1 304 Not Modified
}
return false; // 未找到狀態碼
}
};
bool InitWinsock()
{
// 加載套接字庫(必須)
WORD wVersionRequested;
WSADATA wsaData;
// 套接字加載時錯誤提示
int err;
// 版本 2.2
wVersionRequested = MAKEWORD(2, 2);
// 加載 dll 文件 Scoket 庫
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
// 找不到 winsock.dll
printf("加載 winsock 失敗,錯誤代碼為: %d\n", WSAGetLastError());
return FALSE;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("不能找到正確的 winsock 版本\n");
return FALSE;
}
return TRUE;
}
int main()
{
if (!InitWinsock())
{
WSACleanup();
return -1; // 初始化失敗,退出程序
}
int port = 666; // 設置端口
HttpProxyServer proxyServer(port);
proxyServer.Start();
WSACleanup(); // 在程序結束時清理Winsock庫
return 0;
}
總結
以上是生活随笔為你收集整理的HTTP 代理服务器的设计与实现(C++)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringBoot 接口:响应时间优化
- 下一篇: 盘一盘这个没资格出现在面试环节的场景题。