元旦前后北京降温,我突然想看看自己能不能用纯C写一个命令行天气查询工具,不依赖任何第三方网络库,只靠操作系统提供的Socket接口。折腾了两个晚上,工具能跑了,输入城市名就能拿到实时温度、体感温度和天气描述,过程比我想象中更能暴露问题。今天把整个项目的代码结构和HTTP请求部分拆开讲一遍,包括每行代码为什么那样写,以及我真实踩过的坑。如果你也是那种喜欢把网络协议用手动挡过一遍的人,这篇应该能帮你省不少调试时间。
1. 为什么我要用C语言写一个天气查询工具
这年头用Python写天气查询只要一句 requests.get(),用Shell更简单,curl wttr.in/beijing 就完事了。但那样写完之后,你对网络通信的理解还是停留在“调库”层面。用C语言写这个工具,除了能拿到天气数据,更重要的是把TCP连接建立、HTTP报文格式、响应解析这些原来被封装得看不见的东西,一层层亲手拆开看清楚。
1.1 选 C 语言而不是 Python/Shell 的原因
C语言标准库里没有HTTP客户端,Socket操作也全部暴露在底层。socket()、connect()、send()、recv() 这些函数就是你唯一能用的工具。这个过程很像开车,自动挡当然舒服,但只有开过手动挡,才真正明白离合器和换挡逻辑是怎么回事。
具体到网络编程里,你必须搞清楚几个问题:
- 域名是怎么变成IP地址的
- TCP连接到底是怎么建立的
- HTTP请求的文字排版长什么样
- 服务端返回的数据什么时候算结束
- 收到一段响应之后,怎么把JSON内容抠出来
这些知识点在高级语言里全被封装掉了,但在C语言里每个都得自己处理。一次项目下来,你会对计算机网络教材里的那些概念有真实的体感,而不是只会背概念。
另外还有一个很实际的价值:编译产物是一个不依赖解释器的二进制文件,部署到只有 gcc 和基础库的精简Linux服务器上也能跑。这对某些受限环境下的日常小工具需求很友好。
1.2 工具的最终能力与使用场景
这个天气查询工具的实际使用方式是这样的:
bash复制./weather beijing
终端返回北京的天气描述、当前温度、体感温度、湿度和风速,整个过程大概一两秒。它没有任何图形界面,输出就是纯文本,非常适合放在终端里快速查看。
我选择 wttr.in 作为数据源,根本原因有两个:
- 它提供纯HTTP接口,不需要API Key,也没有繁琐的注册流程
- 返回的JSON结构对新手足够友好,字段命名清晰,比如
temp_C、FeelsLikeC、humidity
这里要说明一件事:因为C语言标准Socket不支持TLS加密,这个工具只演示HTTP接口调用。如果你要接真实商业天气服务,一般走HTTPS,那还需要在Socket之上加一层OpenSSL或者做TLS握手,那是另一个层级的话题。这篇文章先把HTTP这条主线讲透。
整个项目适合什么人群?学过C语言基础语法,知道指针、结构体、文件操作,但还没接触过网络编程的开发者。也适合那些用过 curl 但想知道底层发生了什么的人。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 项目文件怎么拆:一个适合练手的模块划分
写这个小工具的时候,我给自己定的规矩是:哪怕项目再小,也要按职责拆文件。原因是网络编程天然分三层,TCP连接管连通,HTTP报文管格式,JSON解析管数据提取,如果全塞在 main.c 里,用不了多久就成一团乱麻。
2.1 目录结构与模块职责
最终的项目结构是这样的:
text复制weather/
├── main.c
├── tcp.h
├── tcp.c
├── http.h
├── http.c
├── json.h
├── json.c
└── Makefile
每个文件的定位非常明确:
| 文件 | 职责 | 核心内容 |
|---|---|---|
main.c |
命令行入口、流程编排 | 接收城市参数,调用HTTP层,解析并打印天气 |
tcp.h / tcp.c |
TCP连接管理 | DNS解析、创建socket、connect、设置超时 |
http.h / http.c |
HTTP请求与响应接收 | 构造GET请求,发送,接收完整响应 |
json.h / json.c |
轻量JSON字段提取 | 在响应体中找到指定键名并取值 |
Makefile |
构建脚本 | 编译链接生成 weather 可执行文件 |
这样拆的好处是,出了问题能快速定位:网络连不上就看 tcp.c,请求被拒绝就看 http.c,数据解析不对就看 json.c。模块之间的依赖关系也简单清晰:main 调 http,http 调 tcp,json 被 main 使用。
2.2 各模块之间的数据流
整个程序的数据流可以用一条链路描述:
text复制main(城市名) -> 拼接HTTP路径 -> http_get() -> tcp_connect() 建立连接
-> send() 发送HTTP GET请求 -> recv() 循环接收响应
-> 返回完整响应字符串给 mai
