C语言客户端socket编程

99ANYc3cd6
预计阅读时长 24 分钟
位置: 首页 C语言 正文
  1. 核心概念:简要解释 Socket、IP 地址、端口号等。
  2. 客户端编程流程:一个清晰的、分步的步骤图。
  3. 完整代码示例:一个简单的 TCP 客户端,可以连接到服务器并发送/接收消息。
  4. 代码详解:对代码中的关键函数和步骤进行深入解释。
  5. 编译与运行:如何编译和测试你的客户端程序。
  6. 常见错误与调试:新手常遇到的问题和解决方法。

核心概念

在开始编码前,理解几个基本概念非常重要:

C语言客户端socket编程
(图片来源网络,侵删)
  • Socket (套接字):可以看作是网络通信的“端点”,它是一个文件描述符,程序可以通过它来发送和接收数据,就像你打电话需要一个电话号码一样,网络通信需要一个 Socket。
  • IP 地址:网络上设备的唯一标识,0.0.1 (本机地址) 或 8.8.8 (Google DNS)。
  • 端口号:同一台计算机上,可能有多个程序在进行网络通信,端口号用于区分这些程序,范围是 0-65535,Web 服务通常使用 80 或 443 端口。
  • 协议:通信的规则,我们主要讨论两种:
    • TCP (传输控制协议):面向连接的、可靠的协议,通信前需要先建立连接(三次握手),数据传输有确认、重传机制,保证数据不丢失、不重复、按序到达,适合要求高可靠性的场景,如文件传输、网页浏览。
    • UDP (用户数据报协议):无连接的、不可靠的协议,发送数据前不需要建立连接,发送速度快,但不保证数据一定能到达或按序到达,适合对实时性要求高、能容忍少量丢包的场景,如视频会议、在线游戏。

本教程以最常用的 TCP 为例。


客户端编程流程

一个典型的 TCP 客户端程序遵循以下步骤:

graph TD
    A[创建 Socket] --> B{是否成功?};
    B -- 否 --> C[打印错误, 退出];
    B -- 是 --> D[设置服务器地址信息];
    D --> E[连接服务器];
    E --> F{是否连接成功?};
    F -- 否 --> C;
    F -- 是 --> G[发送数据];
    G --> H[接收数据];
    H --> I[关闭 Socket];
    I --> J[结束];

完整代码示例 (TCP 客户端)

这是一个功能完整的 TCP 客户端,它会连接到指定的服务器,等待用户输入并发送消息,同时打印从服务器返回的消息。

// tcp_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // 用于 read, write, close
#include <sys/socket.h> // 用于 socket, connect, AF_INET, SOCK_STREAM
#include <netinet/in.h> // 用于 struct sockaddr_in, htons, INADDR_ANY
#include <arpa/inet.h> // 用于 inet_addr
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
    int sock_fd = 0;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};
    char message[BUFFER_SIZE];
    // 1. 创建 socket
    // AF_INET: IPv4
    // SOCK_STREAM: TCP
    // 0: 自动选择合适的协议
    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation error");
        exit(EXIT_FAILURE);
    }
    // 2. 设置服务器地址信息
    serv_addr.sin_family = AF_INET; // IPv4
    serv_addr.sin_port = htons(PORT); // 端口号,htons 将主机字节序转换为网络字节序
    // 将 IP 地址从字符串转换为网络字节序的地址
    // 如果连接本机,可以使用 "127.0.0.1"
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        perror("Invalid address/ Address not supported");
        exit(EXIT_FAILURE);
    }
    // 3. 连接服务器
    if (connect(sock_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("Connection Failed");
        exit(EXIT_FAILURE);
    }
    printf("Connected to server!\n");
    // 4. 通信循环
    while (1) {
        printf("Enter message to send (or 'exit' to quit): ");
        fgets(message, BUFFER_SIZE, stdin); // 从标准输入读取一行
        // 检查是否输入了退出命令
        if (strncmp(message, "exit", 4) == 0) {
            break;
        }
        // 发送数据到服务器
        send(sock_fd, message, strlen(message), 0);
        printf("Message sent: %s", message);
        // 清空缓冲区
        memset(buffer, 0, BUFFER_SIZE);
        // 从服务器接收数据
        int valread = read(sock_fd, buffer, BUFFER_SIZE);
        if (valread > 0) {
            printf("Server replied: %s\n", buffer);
        } else if (valread == 0) {
            // 服务器关闭了连接
            printf("Server closed the connection.\n");
            break;
        } else {
            // 发生错误
            perror("read error");
            break;
        }
    }
    // 5. 关闭 socket
    close(sock_fd);
    return 0;
}

代码详解

头文件

#include <sys/socket.h>   // 核心 Socket 函数
#include <netinet/in.h>   // IP 地址和端口号结构体
#include <arpa/inet.h>    // IP 地址转换函数
#include <unistd.h>       // read, write, close
#include <string.h>       // memset, strlen, strncmp
#include <stdio.h>        // perror, printf
#include <stdlib.h>       // exit

步骤 1: 创建 Socket

sock_fd = socket(AF_INET, SOCK_STREAM, 0);
  • socket() 函数创建一个新的通信端点。
  • 返回一个文件描述符 sock_fd,后续所有操作都通过这个描述符进行。
  • AF_INET: 指定使用 IPv4 地址族。
  • SOCK_STREAM: 指定使用 TCP 协议。
  • 返回值 < 0 表示创建失败,perror() 会打印出具体的错误信息。

步骤 2: 设置服务器地址

serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
  • struct sockaddr_in 是一个结构体,用于存储 IPv4 地址信息。
  • sin_family: 地址族,设为 AF_INET
  • sin_port: 16 位的端口号。重要:网络字节序是大端序,而 x86/x64 架构的计算机是小端序。htons() (host to network short) 函数将主机字节序转换为网络字节序,这是必须的。
  • sin_addr: 32 位的 IP 地址。
  • inet_pton() (presentation to network) 将点分十进制的字符串 IP 地址(如 "127.0.0.1")转换成网络字节序的二进制格式,并存入 sin_addr 结构中。

步骤 3: 连接服务器

connect(sock_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
  • connect() 函数用于发起一个到指定服务器的连接请求。
  • 参数:
    1. sock_fd: 客户端的 socket 描述符。
    2. &serv_addr: 指向服务器地址结构体的指针,注意,connect 函数期望的是 struct sockaddr 类型的指针,所以我们进行了强制类型转换。
    3. sizeof(serv_addr): 地址结构体的大小。
  • 如果连接失败(例如服务器未启动或地址错误),函数返回 -1

步骤 4: 数据交换

// 发送
send(sock_fd, message, strlen(message), 0);
// 接收
int valread = read(sock_fd, buffer, BUFFER_SIZE);
  • send(): 通过已连接的 socket 发送数据。

    参数:socket 描述符、要发送的数据、数据长度、标志位(通常为 0)。

    C语言客户端socket编程
    (图片来源网络,侵删)
  • read(): 从已连接的 socket 读取数据。
    • 参数:socket 描述符、接收数据的缓冲区、缓冲区大小。
    • 返回值:实际读取到的字节数。
      • > 0: 成功读取。
      • == 0: 对方关闭了连接。
      • < 0: 发生错误。
  • memset(buffer, 0, BUFFER_SIZE);: 在每次接收数据前清空缓冲区,避免上次的数据残留。

步骤 5: 关闭 Socket

close(sock_fd);
  • 当通信结束时,调用 close() 函数关闭 socket,释放系统资源。
  • 在 TCP 中,关闭 socket 会发送一个 FIN 包给对方,表示数据传输结束。

编译与运行

要运行这个客户端,你需要一个对应的服务器程序,你可以自己写一个简单的服务器,或者使用一些现成的工具,netcat (nc)。

使用 netcat 作为简易服务器

netcat 是一个强大的网络工具,可以很方便地用作临时服务器。

  1. 打开一个终端,启动服务器

    # -l 表示监听, -p 指定端口
    nc -l -p 8080

    这个终端会等待客户端的连接。

    C语言客户端socket编程
    (图片来源网络,侵删)
  2. 打开另一个终端,编译并运行客户端

    # 编译
    gcc tcp_client.c -o tcp_client
    # 运行
    ./tcp_client
  3. 测试

    • 在客户端终端输入任意消息,"Hello, Server!",然后按回车。
    • 你会在客户端终端看到 "Server replied: Hello, Server!"。
    • 在服务器(netcat)终端,你会看到你发送的消息 "Hello, Server!"。
    • 在客户端输入 "exit" 并回车,客户端程序会退出,服务器端也会收到连接关闭的通知。

使用 C 语言服务器(推荐)

为了完整地理解,你可以同时编写一个 C 语言的服务器程序,客户端和服务器代码可以放在同一个项目中,服务器流程如下:

  1. socket()
  2. bind() (将 socket 绑定到本地 IP 和端口)
  3. listen() (开始监听连接)
  4. accept() (阻塞等待客户端连接,返回一个新的 socket 用于与该客户端通信)
  5. read() / write() (与新 socket 通信)
  6. close()

常见错误与调试

错误信息 可能原因 解决方法
Connection refused 服务器程序没有运行。
2. 服务器运行的端口号与客户端指定的不同。
3. 防火墙阻止了连接。
启动服务器程序。
2. 检查 PORT 宏定义是否正确。
3. 检查服务器所在机器的防火墙设置。
Address already in use 服务器程序已经崩溃,但端口没有被释放(处于 TIME_WAIT 状态)。 等待一段时间(1-2 分钟),或者在服务器代码的 bind() 之前设置 SO_REUSEADDR 选项。
Invalid address inet_pton() 失败,可能是因为 IP 地址字符串格式错误(如 "127.0.0.1.1")。 检查 inet_pton() 的第二个参数,确保 IP 地址格式正确。
read 返回 0 服务器主动关闭了连接。 这是正常行为,客户端也应该关闭连接并退出。
Segmentation fault 通常是使用了未初始化的指针或访问了非法内存。 检查 serv_addr 等结构体是否正确初始化,指针是否有效。

希望这份详细的教程能帮助你掌握 C 语言的 Socket 客户端编程!

-- 展开阅读全文 --
头像
dede首页如何调用文章缩略图?
« 上一篇 02-07
织梦发布文章,编织器如何编织内容?
下一篇 » 02-07

相关文章

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

目录[+]