本文是 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>usingnamespacestd;// 说明:Windows 用 Winsock,Linux 用 POSIX socket,头文件不同。// 为便于演示,这里给出 Linux/POSIX 版本核心代码。#include<sys/socket.h>#include<arpa/inet.h>#include<unistd.h>intmain(){intsock=socket(AF_INET,SOCK_STREAM,0);if(sock<0){cerr<<"创建 socket 失败"<<endl;return1;}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;return1;}cout<<"已连接服务器!"<<endl;constchar*msg="Hello Server!";send(sock,msg,strlen(msg),0);charbuffer[1024]={};intn=recv(sock,buffer,sizeof(buffer),0);if(n>0)cout<<"收到回复: "<<buffer<<endl;close(sock);return0;}关键步骤: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.0 Accept: */* ← 空行(必须) (请求体,GET 通常为空)响应由「状态行 + 响应头 + 空行 + 响应体」组成:
HTTP/1.1 200 OK ← 状态行:版本 状态码 原因短语 Content-Type: text/html Content-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>usingnamespacestd;#include<sys/socket.h>#include<arpa/inet.h>#include<unistd.h>// 解析响应头中的 Content-LengthintextractContentLength(conststring&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);returnstoi(header.substr(pos,end-pos));}intmain(){intsock=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.comif(connect(sock,(sockaddr*)&server,sizeof(server))<0){cerr<<"连接失败"<<endl;return1;}// 构造 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;charbuffe r[4096];intn;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;intcontentLen=extractContentLength(header);cout<<"Content-Length: "<<contentLen<<"(实际收到 "<<body.size()<<" 字节)"<<endl;return0;}Connection: close告诉服务器响应后关闭连接,客户端循环recv直到收到 0 字节即响应结束。
3.2 用系统命令快速验证
# 命令行验证 HTTP(Linux/macOS)curl-vhttp://example.com/curl-Ihttp://example.com/# 只取响应头# Windows PowerShellInvoke-WebRequest-Urihttp://example.com/|Select-Object-ExpandPropertyContent用curl对照自己实现的客户端输出,能直观确认请求格式是否正确。
四、简易 TCP 服务器
4.1 单线程服务器
// tcp_server.cpp —— 简易 TCP 服务器(Linux 版)#include<iostream>#include<cstring>usingnamespacestd;#include<sys/socket.h>#include<arpa/inet.h>#include<unistd.h>intmain(){intserver=socket(AF_INET,SOCK_STREAM,0);// 允许端口复用(避免 TIME_WAIT 导致 bind 失败)intopt=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);// 最大等待队列 5cout<<"服务器监听 8080 端口..."<<endl;sockaddr_in client{};socklen_t len=sizeof(client);intconn=accept(server,(sockaddr*)&client,&len);cout<<"客户端已连接!"<<endl;charbuffer[1024]={};intn=recv(conn,buffer,sizeof(buffer),0);if(n>0){cout<<"收到: "<<buffer<<endl;constchar*reply="Hello from server!";send(conn,reply,strlen(reply),0);}close(conn);close(server);return0;}服务器四步:socket()→bind()绑定端口 →listen()开始监听 →accept()接受连接。之后与客户端收发数据。
4.2 简易 HTTP 服务器
把响应改为 HTTP 格式,就是一个迷你 Web 服务器:
// mini_http_server.cpp —— 返回固定 HTML 页面的 HTTP 服务器#include<iostream>#include<cstring>usingnamespacestd;#include<sys/socket.h>#include<arpa/inet.h>#include<unistd.h>intmain(){intserver=socket(AF_INET,SOCK_STREAM,0);intopt=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);intconn=accept(server,(sockaddr*)&client,&len);charbuffer[4096]={};recv(c onn,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);return0;}浏览器访问http://127.0.0.1:8080/即可看到返回的页面。Content-Length必须与实际 body 字节数一致,否则浏览器可能显示不完整。
五、实战:带参数的 HTTP 请求
5.1 GET 带查询参数
// http_get_with_query.cpp —— 带查询参数的 GET(核心片段)#include<iostream>#include<string>usingnamespacestd;#include<sys/socket.h>#include<arpa/inet.h>#include<unistd.h>// URL 编码(简单版):空格转 %20stringurlEncode(conststring&s){string result;for(charc:s){if(c==' ')result+="%20";elseresult+=c;}returnresult;}intmain(){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;return0;}查询参数放在?之后、&分隔多个键值对;特殊字符(空格、中文)需 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 篇回顾),敬请期待!