ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

C++手写HTTP GET客户端:Socket请求与响应解析入门

C++手写HTTP GET客户端:Socket请求与响应解析入门

简介:面向C++网络编程初学者与需要理解HTTP GET请求底层流程的开发者,这份轻量级示例以Windows环境为背景,演示如何用C++组织GET请求、获取响应并统计请求耗时,适合作为网络通信入门练习、课程实验或小型工具原型参考。压缩包共3个文件,由2个头文件和1个源文件构成,包体约2KB;头文件分别承载请求类接口与计时器接口,源文件给出请求执行及响应读取的基础实现,便于快速浏览类职责与代码骨架。已有268人学习,说明其作为轻量参考样例具有一定关注度。读者可从中了解HTTP请求类的职责划分、URL设置与响应访问方式、以计时器度量请求耗时的思路,并据此排查基础连接问题;也可把它当作扩展HTTPS、超时重定向、异常处理与代理支持等能力的起点,对照自身项目补齐健壮性设计。

1. 先搞懂一件事:HTTP GET 到底在干什么

HTTP GET 这个词听着像术语,其实它说的就是“我去那个地址,把东西拿回来”这一个动作。你在浏览器地址栏敲一串网址回车,浏览器干的事,和你在 C++ 里写几十行代码干的事,本质上是同一件:连上对方的服务器,递过去一句话——“我要这个路径上的东西”,然后等对方把内容吐回来。用 C++ 实现一个 HTTP GET,就是不用浏览器,自己动手把这几步写出来。

这套东西适合谁?会一点点 C++ 语法(知道什么是函数、字符串、头文件),想弄明白“网页到底是怎么被拿下来的”的人;或者要在自己写的小工具里顺手抓一个接口的数据,又不想为一个功能背一整套框架的人。它不需要你先懂网络原理,也不用啃厚书,只要你会编译、会开终端,就能在半小时内跑出第一个结果,看到服务器真实的回信躺在屏幕上。

2. 为什么值得亲手写一遍:把黑匣子拆开看

很多人学 C++ 网络编程,第一步就被劝退了:教程一上来就是一堆结构体、一堆错误码,看完不知道自己在干嘛。这不是你笨,是顺序不对。真正好上手的顺序,是先知道“一个请求从头到尾走了哪几步”,再让这几步在代码里一一对应上。等你亲手拼出那几行请求文本,并且真的收到了回应,网络这块的黑匣子就打开了——后面再看任何库的文档,你都能一眼看出它在替你做哪一步。

2.1 按下回车之后,其实只发生了四件事

第一步,把域名翻译成 IP 地址,也就是 DNS 解析。你可以理解成查电话簿:你只知道对方叫“某某公司”,电话机不认识名字,只认识号码。第二步,和这个号码建立一条 TCP 连接,类似拨号之后等对方接起来,接起来了才谈得上说话。第三步,把一小段纯文本发过去,内容大概就是“我要哪个路径的文件”,外加点说明自己是谁的信息。第四步,对方回一段纯文本,前面几行是“状态怎么样、内容多长、什么格式”,空一行之后才是正文。

这里有个特别关键、但很多教程不说破的事实:HTTP 是一个纯文本协议。你发出去的那段东西,和你在记事本里敲的几行字,没有本质区别,只是行尾要用\r\n结尾。知道这一点,你就不会再觉得它神秘——所谓“实现 HTTP 客户端”,说穿了就是拼一段字符串、发出去、把回来的字符串切开看。

第一次搞懂这个的时候,我盯着终端里打印出来的原始响应愣了半天:原来浏览器页面背后就是这么几行字。那种“罩子被掀开”的感觉,比跑通一个复杂框架爽得多。

2.2 手写一遍和直接用现成的库,差别在哪

市面上有非常成熟的传输库,一个函数调用就能把 URL 的内容拿回来,还顺便帮你处理了加密、重定向、超时。既然有现成的,为什么还要手写?因为现成的库对你来说是个黑匣子:它好用时你不知道它好在哪,它报错时你也只能瞎猜。

手写这几十行代码的价值不在性能,也不在“以后都这么写”,而在于建立因果感。你会亲眼看到 Host 头是怎么拼进去的、为什么请求结尾必须有两个换行、为什么有时候收一次数据收不全。有了这份因果感,将来你用库的时候,出问题能定位到“是 DNS 没解析出来”“是对面返回了 301”“是响应被压缩了”,而不是只能把代码翻来覆去地重跑。这是血泪经验:不懂原理的人调网络问题,基本等同于抽奖。

反过来说,什么时候该老老实实用库?如果你要访问的是加密站点、要跟随跳转、要并发抓几十个地址、要上传大文件,那就别自己造了,那些场景的边界情况多到你想不到。手写这一遍的意义是打底子,不是替代成熟方案。

2.3 开工前先备好三样东西

第一样是编译器。Linux 或 macOS 上打开终端敲g++ --version或clang++ --version,能打出版本号就行。Windows 用户最省事的做法是装一个带编译器的开发环境,或者用系统自带的 Linux 子系统,别一上来就跟编译环境较劲。

第二样是一个终端窗口。所有演示都是在终端里编译、运行、看输出,不需要图形界面。

第三样是一个自己能控制的本地测试站点。这一点新手最容易忽略:直接拿一个真实网站做实验,出错了你分不清是自己代码的问题,还是网络、对方服务器、跳转的问题。本地起一个服务,出错信息干净,还能随便改内容。如果你机器上有 Python,一行命令就能开:

# 在任意一个空目录里执行,把当前目录当成网站根目录,端口 8080 python3 -m http.server 8080

跑完这行,先在浏览器里访问http://127.0.0.1:8080/确认能看到文件列表。看到列表,说明你的“对方服务器”已经就位,接下来写的客户端就有东西可请求了。这三样东西备齐,后面所有代码都能直接复制粘贴跑通。

3. 第一次动手:六十行跑通一个 GET 请求

真正写起来,你会发现代码不长。整个程序就四段:解析地址、建立连接、发请求、收响应,一段对应上一章说的一个步骤。为了让你第一次就成功,这里刻意选了最简单的场景——不加密、不跳转、不等超时,能拿到内容就算赢。

3.1 先把网址拆成三块:主机、端口、路径

新手最常见的困惑是:代码里为什么没有“网址”这个参数?因为一个网址拆开之后,是三个独立的东西,代码里必须分开写。比如http://127.0.0.1:8080/index.html:

部分值代码里对应说明
协议http决定用不用加密先用 http,加密的事后面再说
主机127.0.0.1host变量也可以是域名,交给解析函数处理
端口8080port变量不写时默认 80,路径里不体现
路径/index.htmlpath变量必须以/开头,不能空着

这张表要记牢,因为它解释了后面 90% 的 400 错误:路径少写了开头的斜杠、端口和主机搞混、请求行里忘了写协议版本,都会让服务器直接拒收。

3.2 完整代码:一个能编译的最小客户端

下面这份代码在 Linux 和 macOS 上可以直接编译运行,六十行左右,四个步骤各自成段。

// mini_get.cpp —— 最小可用的 HTTP GET 客户端(POSIX socket) #include <cstdio> #include <cstring> #include <string> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> // getaddrinfo / freeaddrinfo #include <unistd.h> // close int main() { const char* host = "127.0.0.1"; // 目标主机,换成域名也可以 const char* port = "8080"; // 端口,注意这里必须是字符串 const char* path = "/"; // 请求路径,必须以 / 开头 // 第一步:把"主机 + 端口"翻译成一串可连接的地址 addrinfo hints{}; hints.ai_family = AF_UNSPEC; // IPv4 和 IPv6 都接受 hints.ai_socktype = SOCK_STREAM; // 流式套接字,HTTP 走的 TCP addrinfo* res = nullptr; if (getaddrinfo(host, port, &hints, &res) != 0) { std::printf("地址解析失败\n"); return 1; } // 第二步:逐个尝试地址,第一个连上的就用它 int fd = -1; for (addrinfo* p = res; p != nullptr; p = p->ai_next) { fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); if (fd < 0) continue; if (connect(fd, p->ai_addr, p->ai_addrlen) == 0) break; close(fd); fd = -1; } freeaddrinfo(res); // 用完必须释放,否则内存泄漏 if (fd < 0) { std::printf("连接失败\n"); return 1; } // 第三步:拼请求报文。请求行 + Host 头 + 一个空行,缺一不可 std::string req; req += std::string("GET ") + path + " HTTP/1.1\r\n"; req += std::string("Host: ") + host + ":" + port + "\r\n"; req += "Connection: close\r\n"; // 告诉对方:回完就断开 req += "\r\n"; // 空行表示"头说完了" send(fd, req.c_str(), req.size(), 0); // 第四步:循环接收,直到对方关连接 char buf[4096]; ssize_t n; while ((n = recv(fd, buf, sizeof(buf), 0)) > 0) { fwrite(buf, 1, n, stdout); // 按字节原样输出,不用 printf } close(fd); return 0; }

编译和运行:

# 编译(-std=c++17 保证语法特性,-O2 开常规优化) g++ -std=c++17 -O2 mini_get.cpp -o mini_get # 另开一个终端,在放有 index.html 的目录里起本地站点 python3 -m http.server 8080 # 回到第一个终端运行客户端 ./mini_get

如果屏幕上刷刷出现一段以HTTP/1.1 200 OK开头的内容,恭喜,你的第一个 C++ HTTP 客户端已经工作了。

3.3 逐行拆解:四个函数各自管什么事

getaddrinfo负责把名字变成地址。第一个参数是主机,第二个是端口,注意端口必须是字符串"8080"而不是数字 8080,这是新手第一个报错点。第三个参数hints是给它的偏好设置,AF_UNSPEC意思是不限 IPv4 还是 IPv6,SOCK_STREAM意思是“我要一条可靠的双向连接”。第四个参数是出口,函数成功后会把一张地址链表挂到res上。返回值 0 表示成功,非 0 表示失败,失败时不要用perror,改用gai_strerror拿到具体原因更准确。

为什么是链表而不是一个地址?因为一个域名可能同时对应好几个 IP。所以第二步要写个循环,挨个试。socket创建句柄,connect尝试连接,返回 0 代表连上了。这个循环的写法是有讲究的:连不上要先close再置回-1,否则会漏掉句柄。循环结束无论成败,freeaddrinfo(res)都要调用,这一步看起来无关紧要,但写工具类程序时不释放,跑久了就会被系统收拾。

第三步是整段代码的灵魂,也就是拼字符串。请求行GET / HTTP/1.1三部分用空格隔开,顺序固定;Host头是因为 HTTP/1.1 规定必须带,同一个 IP 上可能挂了好几个站点,服务器靠这个头知道你找的是哪一个;结尾那两个字节\r\n是空的头行,用来告诉服务器“我说完了”。少这一个空行,服务器就会一直等你说完,你会看到程序卡住不动,这是新手最典型的翻车方式。

第四步的recv返回值有三层含义,务必记牢:大于 0 表示这次收到了这么多字节;等于 0 表示对方正常关闭了连接;等于 -1 表示出错。所以while (n > 0)这个循环的意思是“只要还在收东西就一直收”。输出时用fwrite而不是printf("%s"),因为缓冲区里的内容不保证以\0结尾,用字符串方式打印有可能读到缓冲区外面去。

3.4 换成真实域名试一次

本地跑通之后,把host改成任意一个真实域名、port改成"80"、path改成"/",重新编译运行,看看能不能收到内容。这一步能验证你对Host头的理解:很多站点会检查Host和你实际连接的主机是否一致,写错了会直接给你一个 400 或者一个空白页。

如果你改完之后卡住没反应,先别急着怀疑代码,把第三章的四步回顾一遍:解析成功了吗(打印一句话确认)?连接成功了吗?请求拼出来长什么样(用fwrite把请求原文打出来看一眼)?回答完这三个问题,问题一般就自己浮出来了。

4. 从“能收到”到“能看懂”:把响应拆开

收到一大坨文本之后,新的问题来了:我怎么知道请求成功了没有?我怎么知道正文从哪开始?我怎么知道自己有没有收全?这一章解决的就是“读得懂”的问题——从“屏幕上有一堆字”,变成“我能取出状态码、取出正文、判断完整性”。

4.1 一封回信有三层:状态行、响应头、正文

服务器回给你的东西,结构非常规整,从上到下三段,段与段之间用空行分隔。第一行是状态行,告诉你结果;接着若干行是响应头,告诉你内容的元信息;一个空行之后,剩下的全是正文。看一个真实的例子:

HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 68 Connection: close <!doctype html> <html><body><h1>hello</h1></body></html>

状态行里最有用的是那个三位数字:200表示一切正常;301或302表示东西搬家了,请去新地址拿;404表示路径不存在;500表示对方内部出错。新手只需要先认这四个,遇到301不要慌,那不是你写错了,是对方让你换个地方。

响应头里最需要关注的是Content-Length,它是正文的字节数。有了这个数字,你就能判断“我收全了没有”,不用傻等对方断开连接。另一个会碰到的是Transfer-Encoding: chunked,意思是不告诉你总长度,而是分成一块一块发,每块前面标着自己的长度。这个先知道有这么回事就行,处理它的代码比现在讲的多一倍。

4.2 写一个解析函数:切出状态码和长度

解析的核心思路只有一句话:找到第一个空行,前面是头,后面是正文。代码不到二十行。

// 从原始响应字符串里切出状态码、正文长度和正文 #include <cctype> #include <cstdlib> struct Response { int status = 0; // 200 / 404 / 500 long length = -1; // Content-Length,-1 表示响应头里没写 std::string headers; // 头部原文,出问题时留着看 std::string body; // 正文 }; Response parse(const std::string& raw) { Response r; // 头部和正文的分界点就是第一个空行(\r\n\r\n 共四个字节) std::string::size_type split = raw.find("\r\n\r\n"); if (split == std::string::npos) return r; // 头都还没收全,直接返回空结果 r.headers = raw.substr(0, split); r.body = raw.substr(split + 4); // 状态行形如 "HTTP/1.1 200 OK",空格之后就是状态码 std::string::size_type sp = r.headers.find(' '); if (sp != std::string::npos) r.status = std::atoi(r.headers.c_str() + sp + 1); // 头部字段名大小写不敏感,先整体转小写再找,避免漏掉 std::string lower = r.headers; for (char& c : lower) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c))); std::string::size_type k = lower.find("content-length:"); if (k != std::string::npos) r.length = std::atol(lower.c_str() + k + 15); return r; }

几个细节值得说明。find("\r\n\r\n")找的是四个字节,不是两个,因为 HTTP 规定行尾是回车加换行两个字符;如果你只找"\n\n",在别的实现里可能碰巧能跑,但换个服务器就翻车。substr(split + 4)里的 4 就是这四个字节的长度,写错成 2 的话正文开头会多出两个乱码字符,这种错很难一眼看出来。

状态码是从第一个空格之后取整数,atoi会一直读到非数字为止,所以"200 OK"会被正确解析成 200,不用额外处理。找content-length之前把整个头转成小写,是因为不同服务器可能写成Content-Length、content-length甚至CONTENT-LENGTH,协议规定字段名不区分大小写。

k + 15里的 15 是"content-length:"本身的长度。这种魔法数字写成std::string("content-length:").size()更好,但入门阶段直接写数字更直观,你只要记住它对应冒号后面第一个字符就行。

4.3 为什么必须循环接收:一次收不完是常态

很多新手会写出这样的代码:recv一次,把结果当完整响应。它在你本地测试时可能一直好用,因为内容小、一次就收完了;换个大一点的页面立刻出问题。TCP 是流,不是消息,对方发的东西可能被切成任意多段到达,一次recv只保证“至少给你一个字节”,不保证“给你全部”。

所以正确的做法是循环,并且用Content-Length判断什么时候该停,比“傻等到对方断开”更稳:

std::string raw; char buf[4096]; std::string::size_type headerEnd = std::string::npos; long expect = -1; // -1 表示还不知道该收多少 while (true) { ssize_t n = recv(fd, buf, sizeof(buf), 0); if (n <= 0) break; // 0:对方关了;-1:出错 raw.append(buf, n); // 按长度追加,不能用 += buf if (headerEnd == std::string::npos) { // 头还没收全,试着找分界 headerEnd = raw.find("\r\n\r\n"); if (headerEnd != std::string::npos) { Response r = parse(raw); expect = r.length; // 拿到长度,后面据此判断 } } if (headerEnd != std::string::npos && expect >= 0 && raw.size() >= headerEnd + 4 + static_cast<size_t>(expect)) { break; // 头 + 正文都齐了,收工 } }

这段代码有两个容易忽略的地方。一是raw.append(buf, n),必须带长度,如果写成raw += buf,缓冲区里一旦出现字节 0,后面的内容就全丢了——抓图片、抓压缩内容时这个 bug 会让你怀疑人生。二是当expect仍然是 -1 时(响应头里没写长度),循环会退化成“收到对方关闭为止”,这不算错,只是慢一点、也更容易碰上不带Connection: close的情况。

4.4 先绕开的两件事:加密和跳转

写到这里,你已经能拿到明文页面的完整内容了。接下来最想干的事,多半是去请求那些真实网站。这时候会撞上两堵墙。

第一堵是 HTTPS。你现在连上去发的是明文,而加密站点在建立连接之后还要先做一次加密握手,你直接把 HTTP 请求明文丢过去,对方会认为这是无效数据,连接会被重置或者干脆没响应。这一块不适合在入门阶段手写,因为涉及证书校验、加密套件协商,代码量会从六十行变成六百行。合理的做法是:先把明文这条路走透,等你理解了请求和响应的结构,再换一个成熟的传输库去处理加密,那时候你就能看懂它在替你做什么。

第二堵是跳转。你请求http://某个站点/,它可能返回301,响应头里有一行Location: https://...。这不是错误,是对方在指路。手写客户端要跟随跳转,就得把Location里的新地址重新拆成主机、端口、路径,再从头走一遍四步流程。这件事本身不难,但它会让你第一次体会到“解析 URL 其实是个麻烦活”——用现成的库时,人家早把这些坑填好了。

我建议的顺序是:先在本地把明文收发和解析跑到闭着眼都能写,再考虑这两个。倒过来学,很容易变成复制一堆代码、报错一堆、原理一无所知,最后放弃。

5. 新手避坑:五个最容易翻车的地方

下面这五个坑,基本覆盖了新手在写 HTTP GET 时 90% 的失败场景。每个坑按“你会看到什么现象、真正的原因是什么、怎么改”的顺序写,遇到问题时可以直接对号入座。

5.1 现象:在 Windows 上照抄代码,报一堆看不懂的错

现象很典型:编译时报找不到sys/socket.h,或者链接时报一堆undefined reference to socket、connect、getaddrinfo。

原因是两个系统对网络接口的封装方式不同。上面这份代码用的是 POSIX 风格接口,Windows 上需要换成另一套头文件(winsock2.h),并且在程序最开始调用一次初始化函数WSAStartup,否则所有网络调用都会返回失败;编译时还要额外链接一个网络库(一般是加-lws2_32之类的链接参数),关闭连接也不能用close而是对应的closesocket。

解决办法有两个,选一个就行。省事的做法是用系统自带的 Linux 子系统,直接在 Linux 环境下跑,代码不用改一行。要在原生 Windows 上跑,就在文件开头加上初始化调用、把关闭句柄的函数换掉、把网络库链接参数补上,三处改完就能编译。别在这一步硬扛,环境不通畅的时候,什么原理都学不进去。

5.2 现象:程序跑起来就卡住,一直没输出,也不报错

现象是执行之后光标停在那里,等半分钟也没动静,按 Ctrl+C 才退出。

原因大概率是请求头没写完整。服务器收不到那个表示“头结束”的空行,就会一直认为你还没说完,双方就这么互相等着,专业说法叫死锁,说人话就是聊天时两个人都等着对方先开口。还有一种情况:你把Connection: close这行删了,HTTP/1.1 默认保持长连接,服务器回完内容不断开,而你的循环是“收到断开为止”,于是也卡住了。

解决办法是先打印请求原文,逐字节检查结尾是不是真的有两个\r\n。检查方法很简单,把请求字符串用fwrite输出一遍,看到最后是不是有一个空行。如果你确实不想写Connection: close,那就必须靠Content-Length判断收完,也就是第四章那个循环。

5.3 现象:拿到 400 或者 404,但路径明明是对的

现象是响应回来了,但状态行是HTTP/1.1 400 Bad Request或者404 Not Found,正文里可能还有一段错误提示页面。

原因是请求行或者路径的格式不符合规定。400 通常意味着请求本身格式有问题,最常见的是请求行写成了GET /(漏了HTTP/1.1)、三部分之间用了多个空格、或者行尾只用了\n。404 则说明请求格式没问题,但对面的服务器上确实没有这个路径,常见原因是路径没以/开头、多写或少写了结尾斜杠、或者你在本地起服务时把根目录设错了。

解决办法是把请求原文和响应原文并排打出来看一遍。GET / HTTP/1.1这一行要注意两个空格且前后各一个;Host头后面跟的主机名要和connect实际连的主机一致,不一致的话有些服务器会直接返回 400。如果你请求的是本地服务,去命令行看一下启动时的目录对不对,那个目录就是站点根目录。

5.4 现象:内容收到了,但只有一半,后面的正文没了

现象是响应头看起来正常,Content-Length写着几千字节,但你打印出来的内容明显短了一截,用编辑器打开也是残缺的,有时候连标签都没闭合。

原因几乎可以确定是只调用了一次recv,或者虽然循环了但退出条件写错了。小文件碰巧一次收到,大文件必然分段到达,这是 TCP 的工作方式决定的,不是 bug。还有一种隐蔽情况:你用了std::string拼接缓冲区,但写成了raw += buf,遇到内容中间有字节 0 就截断了。

解决办法是把第四章那个带长度的循环照抄进去,并且拼接时一定用append(buf, n)这种带长度的形式。验证是否收全,最简单的办法是比对长度:打印raw.size()和headerEnd + 4 + Content-Length,两个数字相等就说明齐了。不等就先看是不是chunked传输,那种响应本来就没有Content-Length。

5.5 现象:收到的正文是一堆看不懂的乱码

现象是头和正文分界都对,但正文部分打印出来全是些奇怪的符号,或者干脆是空白。

原因是对方把内容压缩了。请求时如果你(或者你用的库、你的浏览器习惯)带了声明支持压缩的信息,服务器就可能返回压缩过的数据,响应头里会出现一行说明编码方式的字段,比如Content-Encoding: gzip。你把这堆压缩数据当文本打印,当然是乱码。另一种乱码来源是编码不匹配,响应头里写着charset=utf-8,你却在按别的编码看。

解决办法分两步。入门阶段最简单的是在请求里明确告诉对方“不要压缩”,加一行Accept-Encoding: identity,这样服务器就会回明文。等你以后要抓大页面、在意流量了,再去接一个解压库,先解压再当文本处理。拿到内容之后先看响应头里的编码声明,再决定用什么方式展示,别直接往屏幕上一扔就说“是乱码”。

6. 下一步学什么:一条能照着走的顺序

跑通第一个例子之后,最容易犯的错是立刻去啃“高并发服务器”这类内容,结果被一堆新概念砸懵。我的建议是沿着一条很窄的路往前走,每一步都只在上一版代码上加一个功能,加完就跑一次,出问题就在这一小步里找原因。

可以按这个顺序推进:

  1. 加超时。给连接和接收都设一个上限,超过就放弃。这一步会让你第一次接触“阻塞”的本质,也是后面所有健壮性的基础。
  2. 把代码拆成函数。解析 URL、建立连接、发送请求、读取响应、解析响应,各写一个函数。拆的时候你会发现哪些状态需要传来传去,这就是“封装成类”的动机。
  3. 支持跟随一次跳转。解析Location头,重新走一遍流程,注意限制最多跟几次,否则可能遇上循环跳转。
  4. 处理chunked。不依赖Content-Length,按块读,读到一个长度为 0 的块就结束。
  5. 换成成熟的传输库来做加密请求。到这一步你已经有能力看懂它的文档了,重点看它怎么处理证书校验和超时设置。
  6. 做一个真能用的小工具。比如批量检查一组地址是否可访问、把某个接口返回的内容存成文件、定时拉一次数据写进本地记录。

这六步里,前四步都是在你现有代码上做加法,总代码量可能从六十行涨到三百行左右,但每一步单独看都不难。第五步是分水岭,之前的代码是学习用的,之后的代码可以拿去干正事。

有两个小技巧能让这条路顺很多。一是养成“打印原文”的习惯,请求发出去之前打一遍,响应收到之后打一遍,绝大多数问题看一眼就知道;二是每加一个功能就单独测一次失败场景,比如故意把端口写错、故意请求一个不存在的路径,看自己的代码报错信息够不够清楚。

我自己在这上面栽过最惨的一次,是自信满满地删掉了Connection: close那行,觉得长连接更“专业”,结果程序在本地一直好好跑,换到别的机器上就时不时卡死,查了大半天才想起来是自己把判断收完的依据也一起删了。从那以后我每次动这类“看起来可有可无”的一行,都会先把它注释掉、故意跑一遍失败场景,确认自己真的知道它在干什么,再决定要不要删。

希望这篇能帮你把第一步迈出去,别在环境上纠结太久,先让那六十行代码在你自己机器上跑出200 OK,剩下的都是在这一步之上慢慢加的。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进