目录
- 核心概念:TCP 客户端/服务器模型
- Dev-C++ 准备工作
- C 语言 TCP 编程核心 API (Windows Sockets)
- 完整代码示例
- 服务器端代码
- 客户端代码
- 如何编译和运行
- 常见问题与调试技巧
核心概念:TCP 客户端/服务器模型
TCP 通信就像打电话,必须有一方先拨号(服务器监听),另一方拨打(客户端连接)。

(图片来源网络,侵删)
-
服务器:
- 创建套接字:建立一个通信端点。
- 绑定地址和端口:将套接字与一个 IP 地址和端口号绑定,这样客户端才能找到它。
- 监听:进入被动监听状态,等待客户端的连接请求。
- 接受连接:当有客户端连接时,服务器接受它,并创建一个新的套接字专门与这个客户端通信。
- 收发数据:通过新的套接字与客户端进行数据交换。
- 关闭套接字:通信结束后,关闭套接字。
-
客户端:
- 创建套接字:建立一个通信端点。
- 连接服务器:主动向服务器的 IP 地址和端口号发起连接请求。
- 收发数据:连接成功后,通过套接字与服务器进行数据交换。
- 关闭套接字:通信结束后,关闭套接字。
Dev-C++ 准备工作
标准的 Dev-C++ (5.11 版本) 默认不包含 Windows Sockets 的开发库头文件和链接库,你需要手动添加。
-
包含头文件: 在你的 C 源文件(
.c)开头,加入以下两行:
(图片来源网络,侵删)#include <winsock2.h> #include <ws2tcpip.h>
-
链接库文件: 这是最关键的一步,你需要告诉编译器链接
ws2_32.lib这个库。-
方法一(推荐,永久设置):
- 在 Dev-C++ 中,点击菜单
工具->编译选项。 - 在弹出的窗口中,切换到
代码生成/优化->链接器选项卡。 - 在
链接库输入框中,添加-lws2_32。 - 点击
确定保存。
- 在 Dev-C++ 中,点击菜单
-
方法二(临时设置,每次编译都要加): 在编译命令行中加入
-lws2_32,使用命令行编译:gcc your_file.c -o your_program -lws2_32。
-
C 语言 TCP 编程核心 API (Windows Sockets)
以下是 Windows Sockets (Winsock) 的核心函数,按执行顺序列出:

(图片来源网络,侵删)
服务器端
| 函数 | 作用 | 返回值 |
|---|---|---|
WSAStartup() |
初始化 Winsock 库。 | 成功返回 0,失败返回错误码。 |
socket() |
创建一个套接字。 | 成功返回套接字描述符,失败返回 INVALID_SOCKET。 |
bind() |
将套接字绑定到一个 IP 地址和端口。 | 成功返回 0,失败返回 SOCKET_ERROR。 |
listen() |
开始监听连接请求。 | 成功返回 0,失败返回 SOCKET_ERROR。 |
accept() |
接受一个客户端连接,并返回一个新的套接字用于通信。 | 成功返回新套接字,失败返回 INVALID_SOCKET。 |
recv() |
从已连接的套接字接收数据。 | 成功返回接收到的字节数,连接关闭返回 0,失败返回 SOCKET_ERROR。 |
send() |
通过已连接的套接字发送数据。 | 成功返回发送的字节数,失败返回 SOCKET_ERROR。 |
closesocket() |
关闭套接字。 | 成功返回 0,失败返回 SOCKET_ERROR。 |
WSACleanup() |
清理 Winsock 库。 | 成功返回 0,失败返回错误码。 |
客户端
| 函数 | 作用 | 返回值 |
|---|---|---|
WSAStartup() |
初始化 Winsock 库。 | 同上。 |
socket() |
创建一个套接字。 | 同上。 |
connect() |
主动连接到服务器。 | 成功返回 0,失败返回 SOCKET_ERROR。 |
send() |
通过已连接的套接字发送数据。 | 同上。 |
recv() |
从已连接的套接字接收数据。 | 同上。 |
closesocket() |
关闭套接字。 | 同上。 |
WSACleanup() |
清理 Winsock 库。 | 同上。 |
完整代码示例
下面是一个简单的 "Echo Server"(回声服务器)和对应的客户端,客户端发送一句话,服务器原样返回这句话。
服务器端代码 (server.c)
#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>
// 定义常用的常量
#define DEFAULT_PORT "8888"
#define BUFFER_SIZE 512
int main() {
// 1. 初始化 Winsock
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0) {
printf("WSAStartup failed: %d\n", result);
return 1;
}
// 2. 创建监听套接字
SOCKET ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ListenSocket == INVALID_SOCKET) {
printf("socket failed: %d\n", WSAGetLastError());
WSACleanup();
return 1;
}
// 3. 绑定地址和端口
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET; // IPv4
serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有可用的网络接口
serverAddr.sin_port = htons(atoi(DEFAULT_PORT)); // 端口,htons将主机字节序转换为网络字节序
result = bind(ListenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr));
if (result == SOCKET_ERROR) {
printf("bind failed: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// 4. 开始监听
result = listen(ListenSocket, SOMAXCONN);
if (result == SOCKET_ERROR) {
printf("listen failed: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
printf("Server is listening on port %s...\n", DEFAULT_PORT);
// 5. 接受客户端连接
sockaddr_in clientAddr;
int clientAddrLen = sizeof(clientAddr);
SOCKET ClientSocket = accept(ListenSocket, (SOCKADDR*)&clientAddr, &clientAddrLen);
if (ClientSocket == INVALID_SOCKET) {
printf("accept failed: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
printf("Client connected: %s:%d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
// 6. 关闭监听套接字,因为它已经不需要了
closesocket(ListenSocket);
// 7. 与客户端进行数据收发
char recvbuf[BUFFER_SIZE];
int recvbuflen = BUFFER_SIZE;
result = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (result > 0) {
printf("Bytes received: %d\n", result);
printf("Message from client: %s\n", recvbuf);
// 回显数据给客户端
result = send(ClientSocket, recvbuf, result, 0);
if (result == SOCKET_ERROR) {
printf("send failed: %d\n", WSAGetLastError());
}
printf("Bytes sent: %d\n", result);
} else if (result == 0) {
printf("Connection closing...\n");
} else {
printf("recv failed: %d\n", WSAGetLastError());
}
// 8. 关闭客户端套接字
closesocket(ClientSocket);
// 9. 清理 Winsock
WSACleanup();
printf("Server shutdown.\n");
return 0;
}
客户端代码 (client.c)
#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#define DEFAULT_PORT "8888"
#define DEFAULT_SERVER "127.0.0.1" // 本地回环地址,用于测试
#define BUFFER_SIZE 512
int main() {
// 1. 初始化 Winsock
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0) {
printf("WSAStartup failed: %d\n", result);
return 1;
}
// 2. 创建套接字
SOCKET ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ConnectSocket == INVALID_SOCKET) {
printf("socket failed: %d\n", WSAGetLastError());
WSACleanup();
return 1;
}
// 3. 连接服务器
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(atoi(DEFAULT_PORT));
// 将 IP 地址字符串转换为网络地址格式
inet_pton(AF_INET, DEFAULT_SERVER, &serverAddr.sin_addr);
result = connect(ConnectSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr));
if (result == SOCKET_ERROR) {
printf("connect failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
printf("Connected to server %s:%s\n", DEFAULT_SERVER, DEFAULT_PORT);
// 4. 发送数据
char sendbuf[BUFFER_SIZE] = "Hello from C TCP Client!";
int sendbuflen = strlen(sendbuf);
result = send(ConnectSocket, sendbuf, sendbuflen, 0);
if (result == SOCKET_ERROR) {
printf("send failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
printf("Bytes sent: %d\n", result);
// 5. 接收服务器返回的数据
char recvbuf[BUFFER_SIZE];
int recvbuflen = BUFFER_SIZE;
result = recv(ConnectSocket, recvbuf, recvbuflen, 0);
if (result > 0) {
printf("Bytes received: %d\n", result);
printf("Message from server: %s\n", recvbuf);
} else if (result == 0) {
printf("Connection closed by server.\n");
} else {
printf("recv failed: %d\n", WSAGetLastError());
}
// 6. 关闭套接字
closesocket(ConnectSocket);
// 7. 清理 Winsock
WSACleanup();
return 0;
}
如何编译和运行
-
编译服务器:
- 将服务器代码保存为
server.c。 - 在 Dev-C++ 中新建一个 C 项目,将
server.c添加到项目中。 - 确保你已经按照 第2步 设置好了链接器选项。
- 点击
编译->编译或运行,Dev-C++ 会自动编译并生成可执行文件(server.exe)。
- 将服务器代码保存为
-
编译客户端:
- 将客户端代码保存为
client.c。 - 新建另一个 C 项目,将
client.c添加进去。 - 同样确保链接器选项已设置。
- 编译并生成
client.exe。
- 将客户端代码保存为
-
运行:
- 重要:必须先运行服务器程序,再运行客户端程序。
- 打开一个命令行窗口(
cmd),使用cd命令进入到server.exe所在的目录。 - 输入
server.exe并回车,你会看到 "Server is listening on port 8888..." 的提示。 - 打开另一个命令行窗口,进入到
client.exe所在的目录。 - 输入
client.exe并回车。 - 观察两个窗口的输出,你将看到客户端发送消息,服务器接收并回显消息的过程。
常见问题与调试技巧
-
WSAStartup failed:- 通常是因为你的系统不支持你请求的 Winsock 版本(
MAKEWORD(2, 2)),可以尝试MAKEWORD(2, 0)或检查系统更新。
- 通常是因为你的系统不支持你请求的 Winsock 版本(
-
bind failed: 10048:- 最常见错误! 错误码
10048表示 "Address already in use"。 - 原因:你的服务器程序可能还在后台运行,或者上一次运行没有正常关闭,占用了
8888端口。 - 解决:
- 方法A:修改代码中的
DEFAULT_PORT为一个其他未被占用的端口(如8889)。 - 方法B:找到并结束占用该端口的进程,在命令行运行
netstat -ano | findstr "8888",找到最后一列是 PID 的那行,然后打开任务管理器,在 "详细信息" 标签页中找到该 PID 的进程并结束它。
- 方法A:修改代码中的
- 最常见错误! 错误码
-
connect failed: 10061:- 错误码
10061表示 "Connection refused"。 - 原因:客户端无法连接到服务器,通常是因为服务器程序没有运行,或者服务器地址/端口填写错误。
- 错误码
-
程序闪退:
- 在
main函数的末尾加上getchar();或system("pause");,这样程序在结束前会等待你按任意键,防止控制台窗口立即关闭。
- 在
-
#include <winsock2.h>找不到:- 这几乎可以肯定是 第2步 的链接器设置没有做好,请仔细检查
编译选项->链接器中的-lws2_32是否已经添加,这是 Dev-C++ 新手最容易犯的错误。
- 这几乎可以肯定是 第2步 的链接器设置没有做好,请仔细检查
