使用 libcurl 库(最推荐、最简单)
libcurl 是一个功能强大且广泛使用的开源客户端 URL 传输库,它支持 HTTP, HTTPS, FTP, SMTP 等多种协议,是进行网络请求的首选。

安装 libcurl
在 Linux (Ubuntu/Debian) 上:
sudo apt-get update sudo apt-get install libcurl4-openssl-dev
在 Linux (CentOS/RHEL) 上:
sudo yum install libcurl-devel
在 Windows 上:
- 访问 libcurl 官网 下载预编译的二进制包(
curl-8.x.x-win64-mingw.zip)。 - 解压文件,并将
bin目录(curl-8.x.x-win64-mingw\bin)添加到系统的PATH环境变量中。 - 在你的 IDE(如 Visual Studio)中,需要配置包含目录(
include)和库目录(lib),并链接libcurl.lib。
示例代码
这个示例演示如何向一个测试网站发送一个简单的 POST 请求,并打印响应。

#include <stdio.h>
#include <curl/curl.h> // 引入 libcurl 头文件
// 回调函数,用于处理服务器返回的数据
// 它会 libcurl 会被重复调用,直到所有数据接收完毕
size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) {
// contents: 服务器返回的数据块
// size: 每个数据块中单个元素的大小 (通常是1)
// nmemb: 每个数据块中元素的数量
// userp: 我们在 CURLOPT_WRITEFUNCTION 中设置的指针 (这里是 stdout)
size_t total_size = size * nmemb;
// 将数据直接写入标准输出
fwrite(contents, 1, total_size, (FILE *)userp);
// 返回实际处理的数据大小,libcurl 需要这个值来判断是否成功
return total_size;
}
int main(void) {
CURL *curl; // libcurl easy handle 指针
CURLcode res; // 操作结果码
// 1. 初始化 libcurl 全局环境
curl_global_init(CURL_GLOBAL_ALL);
// 2. 获取一个 easy handle
curl = curl_easy_init();
if (curl) {
// 3. 设置 easy handle 的选项
// 目标 URL
curl_easy_setopt(curl, CURLOPT_URL, "https://httpbin.org/post");
// 设置为 POST 请求
curl_easy_setopt(curl, CURLOPT_POST, 1L);
// 要 POST 的数据
const char *post_data = "name=John Doe&message=Hello from C";
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);
// 设置回调函数来处理响应数据
// 我们将响应直接打印到屏幕上
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, stdout);
// (可选) 设置 User-Agent
curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
// (可选) 如果需要发送 JSON 数据,可以设置 Content-Type
// curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
// struct curl_slist *headers = NULL;
// headers = curl_slist_append(headers, "Content-Type: application/json");
// curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
// curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{\"key\":\"value\"}");
// 4. 执行请求
printf("Sending POST request to https://httpbin.org/post...\n");
res = curl_easy_perform(curl);
// 5. 检查执行结果
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
} else {
// 获取响应状态码
long response_code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
printf("\nRequest finished with HTTP status code: %ld\n", response_code);
}
// 6. 清理 easy handle
curl_easy_cleanup(curl);
}
// 7. 清理 libcurl 全局环境
curl_global_cleanup();
return 0;
}
如何编译
在 Linux/macOS 上:
gcc -o http_post http_post.c -lcurl
-lcurl 表示链接 libcurl 库。
在 Windows (使用 MinGW/g++) 上:
gcc -o http_post.exe http_post.c -lcurl
在 Windows (使用 Visual Studio) 上: 确保在项目属性中正确配置了 libcurl 的包含目录、库目录和依赖项。
使用 WinINet API(仅限 Windows)
如果你的程序只在 Windows 平台上运行,并且不想引入第三方库,可以使用 Windows 自带的 WinINet API,但它不如 libcurl 灵活,且仅限 Windows。
示例代码
#include <windows.h>
#include <wininet.h>
#include <stdio.h>
#pragma comment(lib, "wininet.lib")
int main(void) {
HINTERNET hInternet = NULL;
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;
// 1. 初始化 WinINet 会话
hInternet = InternetOpenA("My HTTP Client", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (hInternet == NULL) {
printf("InternetOpen failed with error: %lu\n", GetLastError());
return 1;
}
// 2. 连接到服务器
hConnect = InternetConnectA(hInternet, "httpbin.org", INTERNET_DEFAULT_HTTPS_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
if (hConnect == NULL) {
printf("InternetConnect failed with error: %lu\n", GetLastError());
InternetCloseHandle(hInternet);
return 1;
}
// 3. 创建一个 HTTP 请求
const char* accept_types[] = {"*/*", NULL};
hRequest = HttpOpenRequestA(hConnect, "POST", "/post", NULL, NULL, (LPCSTR*)accept_types, INTERNET_FLAG_RELOAD, 0);
if (hRequest == NULL) {
printf("HttpOpenRequest failed with error: %lu\n", GetLastError());
InternetCloseHandle(hConnect);
InternetCloseHandle(hInternet);
return 1;
}
// 4. 添加请求头 (可选)
const char* headers = "Content-Type: application/x-www-form-urlencoded";
if (!HttpAddRequestHeadersA(hRequest, headers, -1, HTTP_ADDREQ_FLAG_ADD)) {
printf("HttpAddRequestHeaders failed with error: %lu\n", GetLastError());
InternetCloseHandle(hRequest);
InternetCloseHandle(hConnect);
InternetCloseHandle(hInternet);
return 1;
}
// 5. 准备要发送的数据
const char* post_data = "name=John Doe&message=Hello from WinINet";
DWORD data_size = strlen(post_data);
// 6. 发送请求和数据
if (!HttpSendRequestA(hRequest, NULL, 0, (LPVOID)post_data, data_size)) {
printf("HttpSendRequest failed with error: %lu\n", GetLastError());
InternetCloseHandle(hRequest);
InternetCloseHandle(hConnect);
InternetCloseHandle(hInternet);
return 1;
}
// 7. 读取响应
char buffer[4096];
DWORD bytes_read;
printf("Response from server:\n");
while (InternetReadFile(hRequest, buffer, sizeof(buffer) - 1, &bytes_read) && bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("%s", buffer);
}
// 8. 清理资源
InternetCloseHandle(hRequest);
InternetCloseHandle(hConnect);
InternetCloseHandle(hInternet);
printf("\nRequest completed.\n");
return 0;
}
如何编译
在 Visual Studio 中创建一个 C++ 控制台项目,直接编译即可(因为 #pragma comment(lib, "wininet.lib") 会自动链接),或者使用 MinGW:
gcc -o wininet_post wininet_post.c -lwininet
手动实现 Socket(最复杂,不推荐)
这种方法完全不依赖任何库,直接通过操作系统底层的 Socket API 来实现,它能让你完全理解 HTTP 协议的工作原理,但代码量巨大,容易出错,且需要处理各种网络细节(如 DNS 解析、TCP 连接、SSL/TLS 加密等)。
强烈建议初学者不要尝试这种方法,除非你有特殊的学习目的。
核心思路
-
DNS 解析:将域名(如
httpbin.org)转换为 IP 地址。 -
建立 TCP 连接:使用
socket(),connect()等函数与服务器的 80 (HTTP) 或 443 (HTTPS) 端口建立连接。 -
构造 HTTP POST 请求:手动拼接一个符合 HTTP 协议的请求字符串。
POST /post HTTP/1.1 Host: httpbin.org Content-Type: application/x-www-form-urlencoded Content-Length: 27 name=John Doe&message=Hello from C
-
发送请求:通过
send()函数将构造好的请求字符串发送到服务器。 -
接收响应:通过
recv()函数接收服务器返回的 HTTP 响应数据。 -
解析响应:手动解析响应头和响应体。
-
关闭连接:使用
close()或closesocket()关闭 Socket。
总结与对比
| 特性 | libcurl (推荐) | WinINet (仅限 Windows) | 手动 Socket |
|---|---|---|---|
| 易用性 | 非常高,API 简洁 | 中等,API 较旧 | 非常低,代码复杂 |
| 跨平台 | 是 (Windows, Linux, macOS 等) | 否 (仅 Windows) | 是 |
| 功能 | 强大,支持多种协议、代理、SSL、上传下载等 | 基本功能,主要支持 HTTP/HTTPS | 仅实现你编写的功能 |
| 依赖 | 需要安装第三方库 | 无,Windows 自带 | 无,但需要自己实现所有功能 |
| 稳定性 | 非常高,经过长期考验 | 较高,但 API 较老 | 取决于你的实现能力 |
对于绝大多数 C 语言项目,使用 libcurl 是最佳选择,它平衡了易用性、功能性和跨平台性,如果你确定项目只在 Windows 上运行,并且希望避免第三方依赖,WinINet 也是一个可行的选项,手动实现 Socket 则只适合学习网络协议或有特殊定制化需求的场景。
