目录用 C 写 HTTP 客户端为什么 select 还要配合非阻塞 IO用 C 写 HTTP 客户端为什么 select 还要配合非阻塞 IO学习代码http/http_request.cHTTP 项目表面上是写一个 GET/POST 客户端本质上是在练 TCP 客户端的完整流程域名解析、建立连接、构造请求、发送数据、循环接收响应。和浏览器相比自己用 C 写一遍会发现HTTP 文本格式很清楚但网络 IO 的边界情况一点都不少。一个最基础的 GET 请求可以这样构造snprintf(request,sizeof(request),GET %s HTTP/1.1\r\nHost: %s\r\nUser-Agent: SimpleCClient/1.0\r\nAccept: */*\r\nConnection: close\r\n\r\n,path,host);这里要注意\r\nHTTP 请求行和请求头都用回车换行结束头部结束后还要有一个空行。Connection: close的作用是告诉服务器响应完成后关闭连接这样客户端可以通过recv返回 0 判断对端关闭简化读取逻辑。项目里更关键的是select接收模板。刚开始我以为select返回可读之后recv就一定不会阻塞。后来看笔记才明白select只能说明“当时看起来可读”它不是永久保证。多线程抢读、连接异常、协议状态变化都可能让后续阻塞调用卡住。因此更稳妥的方式是把 fd 设置成非阻塞intflagsfcntl(sockfd,F_GETFL,0);fcntl(sockfd,F_SETFL,flags|O_NONBLOCK);同时每轮select前都要重新设置fd_set和timevalFD_ZERO(read_fds);FD_SET(sock,read_fds);tv.tv_sectimeout_sec;tv.tv_usec0;retselect(sock1,read_fds,NULL,NULL,tv);这是一个很容易踩的坑。因为select会修改fd_set有些系统还会把timeval改成剩余时间。如果循环里不重置后面的判断就可能被污染出现空转或漏读。我的理解是HTTP 客户端练的不只是协议文本更是“可靠地读完不确定长度的数据”。网络编程里读到一半、超时、对端关闭、暂时无数据都是正常情况。代码必须把这些状态写清楚程序才不会在真实网络环境里表现得忽好忽坏。学习链接: https://github.com/0voice