C++中HTTP?代理服務器的設計與實現(xiàn)詳解
實驗內容
設計并實現(xiàn)一個基本 HTTP 代理服務器。要求在指定端口(例如 8080)接收來自客戶的 HTTP 請求并且根據(jù)其中的 URL 地址訪問該地址 所指向的 HTTP 服務器(原服務器),接收 HTTP 服 務器的響應報文,并 將響應報文轉發(fā)給對應的客戶進行瀏覽。
設計并實現(xiàn)一個支持 Cache 功能的 HTTP 代理服務器。要求能緩 存原服務器響應的對象,并 能夠通過修改請求報文(添加 if-modified-since 頭行),向原服務器確認緩存對象是否是最新版本。 (選作內容)
擴展 HTTP 代理服務器,支持如下功能: (選作內容)
網站過濾:允許/不允許訪問某些網站;
用戶過濾:支持/不支持某些用戶訪問外部網站;
網站引導:將用戶對某個網站的訪問引導至一個模擬網站(釣魚)。
代理服務器的概念
代理服務器,允許一個網絡終端(一般為客戶端)通過這個服務與另一 個網絡終端(一般為服務器)進行非直接的連接。普通 Web 應用通信方式與采用代理服務器的 通信方式的對比如下圖所示:
代理服務器在指定端口(本實驗中所指定的是666端口)監(jiān)聽瀏覽器的訪問請求(需要在客戶端瀏覽器進行相應的設置),接收到瀏覽器對遠程網站的瀏覽請求時,代理服務器開始在代理服務器的緩存中檢索 URL 對應的對象(網頁、 圖像等對象),找到對象文件后,提取該對象文件的最新被修改時間;代理服務器程序在客戶的請求報文首部插入,并向原 Web 服務器轉發(fā)修改后的請求報文。如果代理服務器沒有該對象的緩存,則會直接向原服務器轉發(fā)請求報文,并將原服務器返回的響應直接轉發(fā)給客戶端,同時將對象緩存到代理服務器中。代理服務器程序會根據(jù)緩存的時間、大小和提取記錄等對緩存進行清理。
代碼結構
代碼中共實現(xiàn) 3個類,分別為WebsiteDetector類、Cache類和HttpProxyServer類。
WebsiteDetector類
該類實現(xiàn)了網站過濾和網站引導功能。通過構造函數(shù)直接靜態(tài)設置了釣魚網站和屏蔽的網站:
highlighter- reasonml
WebsiteDetector::WebsiteDetector() { AddValidURL("http://jwc.hit.edu.cn/","http://jwts.hit.edu.cn/"); AddBlockedURL("http://xltj.hit.edu.cn/"); }
可知,屏蔽了心理網站。將教務處網站引導到本科教學管理與服務平臺。
Cache類
該類在當前目錄下創(chuàng)建文件夾.cache/
,在其中存儲瀏覽緩存對象。同時該類中,保存著對象與文件名的映射關系,對象和LastModified字段的映射關系。
highlighter- cpp
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類
該類是代理服務器的實現(xiàn)類。是一個多用戶代理服務器。首先該類創(chuàng)建HTTP代理服務的TCP主套接字,該套接字監(jiān)聽等待客戶端的連接請求。當客戶端連接之后,創(chuàng)建一個子線程,由子線程行上述一對一的代理過程,服務結束之后子線程終止。
highlighter- reasonml
class HttpProxyServer { public: HttpProxyServer(int port); // 構造函數(shù),參數(shù)為端口號 void Start(); // 監(jiān)聽客戶端連接請求 private: int serverSocket_; // 代理服務Socket int port_; // 端口號 struct sockaddr_in serverAddr_; // 代理服務地址 Cache cache_; // Cache類 WebsiteDetector websiteDetector_; // websiteDetector類 void HandleClient(int clientSocket); // 子線程調用函數(shù) std::string ExtractUrl(const std::string &httpRequest); // 解析URL int CreateServerSocket(const std::string &host); // 創(chuàng)建與原服務器連接的Socket bool ServerToClient(const std::string &url, int clientSocket); // 轉發(fā)數(shù)據(jù) void ParseUrl(const std::string &url, std::string &host, std::string &path); // 解析主機名與路徑名 };
程序基本流程
(1) 初始化服務器Socket,監(jiān)聽等待客戶端的連接請求。
(2) 當客戶端連接后,創(chuàng)建子線程處理請求。
(3) 子線程接收請求,解析HTTP請求的首部行和請求頭。然后提取Url,Url作為參數(shù)通過websiteDetector類判斷是否屏蔽或者引導。
(4) 然后進入轉發(fā)的過程,首先進行域名解析,然后創(chuàng)建Socket先原服務器發(fā)送請求,接收響應,將數(shù)據(jù)轉發(fā)到客戶端。
(5) 在轉發(fā)的過程中,涉及保存緩存和讀取緩存。
網站引導功能
利用首部行中的location字段,實現(xiàn)引導。
highlighter- arduino
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);
用戶過濾功能
設置服務器地址信息時實現(xiàn)。
highlighter- awk
serverAddr_.sin_addr.s_addr = INADDR_ANY; // serverAddr_.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //只允許本機用戶訪問服務器
Cache功能
1. 代理服務器處理客戶端請求時,對于第一次出現(xiàn)的對象,會保存下。當客戶端再次請求時,代理服務器就會在請求中添加If-Modified-Since首部行。
highlighter- qml
date = cache_.GetDate(url); std::string cacheRequest = httpRequest + "If-Modified-Since: " + date + MY_CRLF + MY_CRLF;
發(fā)送該請求后,等待原服務器響應,并判斷是否回應304狀態(tài)碼。
highlighter- reasonml
if (IsResponseNotModified(responseNotModified) ) { ? // std::cout << "304 Not Modified" << std::endl; ? sel = false; }else { ? cache_.ClearFileContent(url); //清空 ? sel = true; }
sel為false時,則讀取Cache轉發(fā)到客戶端。若為true,則發(fā)送HTTP請求到原服務器,再接收響應,轉發(fā)到客戶端,再保存到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/
可以看到,字體顏色變?yōu)榧t色。可知,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; // 找到后可以退出循環(huán) }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) { // 初始化服務器 // 創(chuàng)建主套接字并綁定端口 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); } // 開始監(jiān)聽客戶端連接請求 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() { // 啟動服務器,監(jiān)聽客戶端連接請求 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; // 繼續(xù)等待下一個連接 } // std::cout << "Start(): Accepted a client connection" << std::endl; // 創(chuàng)建子線程處理客戶端請求 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") { // 向服務端請求,向客戶端發(fā)送 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://" 前綴,則默認協(xié)議為 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表示連接失敗 } // 創(chuàng)建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,可以根據(jù)需要修改 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); // 創(chuàng)建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; // 發(fā)送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; // 接收緩存,轉發(fā)到客戶端 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; // 發(fā)送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'; // 發(fā)送接收到的數(shù)據(jù)到客戶端 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) { // 查找第一個空格,定位到狀態(tài)碼的開始 size_t spacePos = response.find(' '); if (spacePos != std::string::npos) { // 提取狀態(tài)碼部分 std::string statusCode = response.substr(spacePos + 1, 3); // 檢查狀態(tài)碼是否為 "304" return (statusCode == "304"); // HTTP/1.1 304 Not Modified } return false; // 未找到狀態(tài)碼 } }; 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; }
以上就是C++中HTTP 代理服務器的設計與實現(xiàn)詳解的詳細內容,更多關于C++ HTTP 代理服務器的資料請關注腳本之家其它相關文章!
相關文章
C++使用boost::lexical_cast進行數(shù)值轉換
這篇文章介紹了C++使用boost::lexical_cast進行數(shù)值轉換的方法,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06win10+VS2017+Cuda10.0環(huán)境配置詳解
這篇文章主要介紹了win10+VS2017+Cuda10.0環(huán)境配置詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-08-08C++編程異常處理中try和throw以及catch語句的用法
這篇文章主要介紹了C++編程異常處理中try和throw以及catch語句的用法,包括對Catch塊的計算方式的介紹,需要的朋友可以參考下2016-01-01基于QT和百度云api實現(xiàn)批量獲取PDF局部文字內容
這篇文章將為大家介紹如何使用 QT 構建圖形用戶界面,結合百度云 OCR API 實現(xiàn)批量獲取 PDF 局部文字內容并對文件進行改名的功能,需要的可以參考下2025-03-03