C语言TCP编程在Dev-C++中如何实现?

99ANYc3cd6
预计阅读时长 31 分钟
位置: 首页 C语言 正文

目录

  1. 核心概念:TCP 客户端/服务器模型
  2. Dev-C++ 准备工作
  3. C 语言 TCP 编程核心 API (Windows Sockets)
  4. 完整代码示例
    • 服务器端代码
    • 客户端代码
  5. 如何编译和运行
  6. 常见问题与调试技巧

核心概念:TCP 客户端/服务器模型

TCP 通信就像打电话,必须有一方先拨号(服务器监听),另一方拨打(客户端连接)。

c语言 tcp dev-c
(图片来源网络,侵删)
  • 服务器

    1. 创建套接字:建立一个通信端点。
    2. 绑定地址和端口:将套接字与一个 IP 地址和端口号绑定,这样客户端才能找到它。
    3. 监听:进入被动监听状态,等待客户端的连接请求。
    4. 接受连接:当有客户端连接时,服务器接受它,并创建一个新的套接字专门与这个客户端通信。
    5. 收发数据:通过新的套接字与客户端进行数据交换。
    6. 关闭套接字:通信结束后,关闭套接字。
  • 客户端

    1. 创建套接字:建立一个通信端点。
    2. 连接服务器:主动向服务器的 IP 地址和端口号发起连接请求。
    3. 收发数据:连接成功后,通过套接字与服务器进行数据交换。
    4. 关闭套接字:通信结束后,关闭套接字。

Dev-C++ 准备工作

标准的 Dev-C++ (5.11 版本) 默认不包含 Windows Sockets 的开发库头文件和链接库,你需要手动添加。

  1. 包含头文件: 在你的 C 源文件(.c)开头,加入以下两行:

    c语言 tcp dev-c
    (图片来源网络,侵删)
    #include <winsock2.h>
    #include <ws2tcpip.h>
  2. 链接库文件: 这是最关键的一步,你需要告诉编译器链接 ws2_32.lib 这个库。

    • 方法一(推荐,永久设置)

      1. 在 Dev-C++ 中,点击菜单 工具 -> 编译选项
      2. 在弹出的窗口中,切换到 代码生成/优化 -> 链接器 选项卡。
      3. 链接库 输入框中,添加 -lws2_32
      4. 点击 确定 保存。
    • 方法二(临时设置,每次编译都要加): 在编译命令行中加入 -lws2_32,使用命令行编译:gcc your_file.c -o your_program -lws2_32


C 语言 TCP 编程核心 API (Windows Sockets)

以下是 Windows Sockets (Winsock) 的核心函数,按执行顺序列出:

c语言 tcp dev-c
(图片来源网络,侵删)

服务器端

函数 作用 返回值
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;
}

如何编译和运行

  1. 编译服务器

    • 将服务器代码保存为 server.c
    • 在 Dev-C++ 中新建一个 C 项目,将 server.c 添加到项目中。
    • 确保你已经按照 第2步 设置好了链接器选项。
    • 点击 编译 -> 编译运行,Dev-C++ 会自动编译并生成可执行文件(server.exe)。
  2. 编译客户端

    • 将客户端代码保存为 client.c
    • 新建另一个 C 项目,将 client.c 添加进去。
    • 同样确保链接器选项已设置。
    • 编译并生成 client.exe
  3. 运行

    • 重要:必须先运行服务器程序,再运行客户端程序。
    • 打开一个命令行窗口(cmd),使用 cd 命令进入到 server.exe 所在的目录。
    • 输入 server.exe 并回车,你会看到 "Server is listening on port 8888..." 的提示。
    • 打开另一个命令行窗口,进入到 client.exe 所在的目录。
    • 输入 client.exe 并回车。
    • 观察两个窗口的输出,你将看到客户端发送消息,服务器接收并回显消息的过程。

常见问题与调试技巧

  1. WSAStartup failed

    • 通常是因为你的系统不支持你请求的 Winsock 版本(MAKEWORD(2, 2)),可以尝试 MAKEWORD(2, 0) 或检查系统更新。
  2. bind failed: 10048

    • 最常见错误! 错误码 10048 表示 "Address already in use"。
    • 原因:你的服务器程序可能还在后台运行,或者上一次运行没有正常关闭,占用了 8888 端口。
    • 解决
      • 方法A:修改代码中的 DEFAULT_PORT 为一个其他未被占用的端口(如 8889)。
      • 方法B:找到并结束占用该端口的进程,在命令行运行 netstat -ano | findstr "8888",找到最后一列是 PID 的那行,然后打开任务管理器,在 "详细信息" 标签页中找到该 PID 的进程并结束它。
  3. connect failed: 10061

    • 错误码 10061 表示 "Connection refused"。
    • 原因:客户端无法连接到服务器,通常是因为服务器程序没有运行,或者服务器地址/端口填写错误。
  4. 程序闪退

    • main 函数的末尾加上 getchar();system("pause");,这样程序在结束前会等待你按任意键,防止控制台窗口立即关闭。
  5. #include <winsock2.h> 找不到

    • 这几乎可以肯定是 第2步 的链接器设置没有做好,请仔细检查 编译选项 -> 链接器 中的 -lws2_32 是否已经添加,这是 Dev-C++ 新手最容易犯的错误。
-- 展开阅读全文 --
头像
dede友情链接被挂马怎么办?
« 上一篇 02-27
C语言与C++设计差异核心是什么?
下一篇 » 02-27

相关文章

取消
微信二维码
支付宝二维码

目录[+]