C++综合实战:网络编程入门与HTTP客户端
本文摘要
Socket 基础(客户端/服务器模型与 TCP 通信四步)、HTTP 协议(请求/响应格式、方法、状态码)、迷你 HTTP 客户端(构造 GET 请求、解析响应头与…
本文是 C++ 系列教程的第 29 篇。上一篇完成了面向对象图书管理系统,本篇实战网络编程:Socket 基础与 TCP 通信、HTTP 协议解析、迷你 HTTP 客户端实现与简易服务器,覆盖 9 个完整示例代码。
一、网络编程基础
1.1 客户端/服务器模型与 Socket
网络通信的核心模型:服务器监听端口等待连接,客户端主动发起连接。**Socket(套接字)**是操作系统提供的网络编程接口,屏蔽底层协议细节:
客户端 服务器 | | |---- connect() -------->| 监听 listen() | | 接受 accept() |<-- send/recv 数据 ---->| | | |---- close() ---------->| close()TCP 提供可靠、有序、面向连接的字节流传输,HTTP、FTP、SSH 等协议都建立在 TCP 之上。
1.2 TCP 客户端完整示例(Windows + Linux 通用思路)
// tcp_client.cpp —— 简易 TCP 客户端#include <iostream>#include <cstring>using namespace std;
// 说明:Windows 用 Winsock,Linux 用 POSIX socket,头文件不同。// 为便于演示,这里给出 Linux/POSIX 版本核心代码。
#include <sys/socket.h>#include <arpa/inet.h>#include <unistd.h>
int main() { int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { cerr << "创建 socket 失败" << endl; return 1; }
sockaddr_in server{}; server.sin_family = AF_INET; server.sin_port = htons(8080); inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
if (connect(sock, (sockaddr*)&server, sizeof(server)) < 0) { cerr << "连接失败" << endl; return 1; } cout << "已连接服务器!" << endl;
const char* msg = "Hello Server!"; send(sock, msg, strlen(msg), 0);
char buffer[1024] = {}; int n = recv(sock, buffer, sizeof(buffer), 0); if (n > 0) cout << "收到回复: " << buffer << endl;
close(sock); return 0;}关键步骤:socket() 创建套接字 → connect() 连接服务器 → send()/recv() 收发数据 → close() 关闭。htons 把端口转为网络字节序。
二、HTTP 协议
2.1 HTTP 请求与响应格式
HTTP 是请求-响应模型:客户端发请求,服务器回响应。请求由「请求行 + 请求头 + 空行 + 请求体」组成 :
GET /index.html HTTP/1.1 ← 请求行:方法 路径 协议版本Host: www.example.com ← 请求头(键: 值)User-Agent: MyCppClient/1.0Accept: */* ← 空行(必须)(请求体,GET 通常为空)响应由「状态行 + 响应头 + 空行 + 响应体」组成:
HTTP/1.1 200 OK ← 状态行:版本 状态码 原因短语Content-Type: text/htmlContent-Length: 1256
<html><body>...</body></html> ← 响应体2.2 常用 HTTP 方法与状态码
| 方法 | 用途 | 状态码 | 含义 |
|---|---|---|---|
| GET | 获取资源 | 200 OK | 成功 |
| POST | 提交数据 | 201 Created | 已创建 |
| PUT | 更新资源 | 301 Moved | 永久重定向 |
| DELETE | 删除资源 | 404 Not Found | 资源不存在 |
| HEAD | 只取响应头 | 500 Internal Server Error | 服务器错误 |
三、迷你 HTTP 客户端
3.1 构造请求并解析响应
写一个简化版 HTTP 客户端:连接服务器、发送 GET 请求、读取并解析响应头与响应体:
// mini_http_client.cpp —— 迷你 HTTP 客户端(Linux 版核心逻辑)#include <iostream>#include <string>#include <cstring>using namespace std;
#include <sys/socket.h>#include <arpa/inet.h>#include <unistd.h>
// 解析响应头中的 Content-Lengthint extractContentLength(const string& header) { size_t pos = header.find("Content-Length:"); if (pos == string::npos) return -1; pos += 15; // 跳过 "Content-Length: " size_t end = header.find("\r\n", pos); return stoi(header.substr(pos, end - pos));}
int main() { int sock = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in server{}; server.sin_family = AF_INET; server.sin_port = htons(80); // HTTP 默认端口 inet_pton(AF_INET, "93.184.216.34", &server.sin_addr); // example.com
if (connect(sock, (sockaddr*)&server, sizeof(server)) < 0) { cerr << "连接失败" << endl; return 1; }
// 构造 GET 请求 string request = "GET / HTTP/1.1\r\n" "Host: example.com\r\n" "User-Agent: MiniCppClient/1.0\r\n" "Connection: close\r\n" // 响应后关闭连接 "\r\n"; send(sock, request.c_str(), request.size(), 0);
// 读取响应 string response; char buffer[4096]; int n; while ((n = recv(sock, buffer, sizeof(buffer), 0)) > 0) { response.append(buffer, n); } close(sock);
// 分离响应头与响应体 size_t headerEnd = response.find("\r\n\r\n"); string header = response.substr(0, headerEnd); string body = response.substr(headerEnd + 4);
cout << "===== 状态行 =====" << endl; cout << header.substr(0, header.find("\r\n")) << endl; cout << "===== 响应体前 200 字符 =====" << endl; cout << body.substr(0, 200) << endl;
int contentLen = extractContentLength(header); cout << "Content-Length: " << contentLen << "(实际收到 " << body.size() << " 字节)" << endl; return 0;}Connection: close 告诉服务器响应后关闭连接,客户端循环 recv 直到收到 0 字节即响应结束。
3.2 用系统命令快速验证
# 命令行验证 HTTP(Linux/macOS)curl -v http://example.com/curl -I http://example.com/ # 只取响应头
# Windows PowerShellInvoke-WebRequest -Uri http://example.com/ | Select-Object -ExpandProperty Content用 curl 对照自己实现的客户端输出,能直观确认请求格式是否正确。
四、简易 TCP 服务器
4.1 单线程服务器
// tcp_server.cpp —— 简易 TCP 服务器(Linux 版)#include <iostream>#include <cstring>using namespace std;
#include <sys/socket.h>#include <arpa/inet.h>#include <unistd.h>
int main() { int server = socket(AF_INET, SOCK_STREAM, 0);
// 允许端口复用(避免 TIME_WAIT 导致 bind 失败) int opt = 1; setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡 addr.sin_port = htons(8080);
bind(server, (sockaddr*)&addr, sizeof(addr)); listen(server, 5); // 最大等待队列 5 cout << "服务器监听 8080 端口..." << endl;
sockaddr_in client{}; socklen_t len = sizeof(client); int conn = accept(server, (sockaddr*)&client, &len); cout << "客户端已连接!" << endl;
char buffer[1024] = {}; int n = recv(conn, buffer, sizeof(buffer), 0); if (n > 0) { cout << "收到: " << buffer << endl; const char* reply = "Hello from server!"; send(conn, reply, strlen(reply), 0); }
close(conn); close(server); return 0;}服务器四步:socket() → bind() 绑定端口 → listen() 开始监听 → accept() 接受连接。之后与客户端收发数据。
4.2 简易 HTTP 服务器
把响应改为 HTTP 格式,就是一个迷你 Web 服务器:
// mini_http_server.cpp —— 返回固定 HTML 页面的 HTTP 服务器#include <iostream>#include <cstring>using namespace std;
#include <sys/socket.h>#include <arpa/inet.h>#include <unistd.h>
int main() { int server = socket(AF_INET, SOCK_STREAM, 0); int opt = 1; setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(8080);
bind(server, (sockaddr*)&addr, sizeof(addr)); listen(server, 5); cout << "HTTP 服务器: http://127.0.0.1:8080/" << endl;
while (true) { // 循环接受多个客户端 sockaddr_in client{}; socklen_t len = sizeof(client); int conn = accept(server, (sockaddr*)&client, &len);
char buffer[4096] = {}; recv(conn, buffer, sizeof(buffer), 0); // 读取请求(此处忽略内容)
string body = "<html><body><h1>Hello C++!</h1>" "<p>这是一个 C++ HTTP 服务器</p></body></html>"; string response = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html; charset=utf-8\r\n" "Content-Length: " + to_string(body.size()) + "\r\n" "Connection: close\r\n" "\r\n" + body;
send(conn, response.c_str(), response.size(), 0); close(conn); } close(server); return 0;}浏览器访问 http://127.0.0.1:8080/ 即可看到返回的页面。Content-Length 必须与实际 body 字节数一致,否则浏览器可能显示不完整。
五、实战:带参数的 HTTP 请求
5.1 GET 带查询参数
// http_get_with_query.cpp —— 带查询参数的 GET(核心片段)#include <iostream>#include <string>using namespace std;
#include <sys/socket.h>#include <arpa/inet.h>#include <unistd.h>
// URL 编码(简单版):空格转 %20string urlEncode(const string& s) { string result; for (char c : s) { if (c == ' ') result += "%20";
else result += c; } return result;}
int main() { string keyword = "C++ 教程"; string path = "/search?q=" + urlEncode(keyword);
string request = "GET " + path + " HTTP/1.1\r\n" "Host: example.com\r\n" "Connection: close\r\n" "\r\n";
cout << "发送请求:" << endl; cout << request << endl; cout << "(完整客户端逻辑同 3.1 节,此处演示请求构造)" << endl; return 0;}查询参数放在 ? 之后、& 分隔多个键值对;特殊字符(空格、中文)需 URL 编码。
5.2 常见问题排查
| 问题 | 可能原因 | 排查方法 |
|---|---|---|
| 连接失败 | 端口/地址错误、服务器未启动 | `netstat -an |
| 响应为空 | 请求格式错误、未发空行 | 用 curl 对照请求文本 |
| 中文乱码 | 编码未指定 | 响应头加 charset=utf-8 |
| Content-Length 不匹配 | body 计算错误 | 用 body.size() 而不是手写数字 |
| 端口占用 | 前次连接未释放 | SO_REUSEADDR 或等 TIME_WAIT 结束 |
总结
本篇讲解了 C++ 网络编程入门:Socket 基础(客户端/服务器模型与 TCP 通信四步)、HTTP 协议(请求/响应格式、方法、状态码)、迷你 HTTP 客户端(构造 GET 请求、解析响应头与 Content-Length)、简易 TCP/HTTP 服务器(bind/listen/accept 循环)与带查询参数的请求构造及常见问题排查。建议用 curl 对照验证,并在本机 8080 端口跑通客户端-服务器闭环。
下一篇将是系列终章 C++ 综合实战:数据结构与算法实现及系列总结(手写容器、排序算法与 30 篇回顾),敬请期待!
C++综合实战:网络编程入门与HTTP客户端
作者:Wang Shijie
本文链接:https://wsj-blog.pages.dev/posts/c-zong-he-shi-zhan-wang-luo-bian-cheng-ru-men-yu-http-ke-hu/
本文采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
留言评论
评论资源加载失败,请重试。