C语言socket通信编程如何实现高效数据传输?

99ANYc3cd6
预计阅读时长 28 分钟
位置: 首页 C语言 正文
  1. 核心概念:理解 Socket 的基本原理。
  2. Socket 编程流程:服务器和客户端通信的基本步骤。
  3. 关键 API 函数:详细介绍每个步骤会用到的核心函数。
  4. 完整代码示例:提供一个最经典、最完整的 TCP 服务器和客户端代码,并附上详细注释。
  5. 编译与运行:说明如何在 Linux 和 Windows 上编译和运行这些程序。
  6. UDP Socket 简介:简要介绍 UDP 编程与 TCP 的区别。
  7. 总结与注意事项

核心概念

在开始编码前,我们需要理解几个基本概念:

c语言socket通信编程
(图片来源网络,侵删)
  • IP 地址:网络中设备的唯一标识,就像家庭住址一样。168.1.100
  • 端口号:同一台主机上,不同应用程序的标识,就像房间号一样,一个 IP 地址可以提供多个服务(如 Web 服务、FTP 服务),端口号用来区分这些服务,范围是 0-65535,0-1023 是系统保留端口。
  • 协议:通信双方必须遵守的规则,Socket 编程主要涉及两种协议:
    • TCP (Transmission Control Protocol)面向连接的协议,通信前需要先建立连接(三次握手),数据传输可靠、有序,但开销较大,适用于要求高可靠性的场景,如文件传输、网页浏览。
    • UDP (User Datagram Protocol)无连接的协议,直接发送数据,不保证顺序和可靠性,但开销小、速度快,适用于实时性要求高的场景,如视频会议、在线游戏。
  • Socket:它是网络编程的 API,可以看作是“通信端点”,应用程序通过操作 Socket,将数据发送到网络或从网络接收数据。

Socket 编程流程

TCP 通信是“客户端-服务器”模式,流程如下:

TCP 服务器端 流程

  1. 创建 Socket:使用 socket() 函数创建一个套接字描述符。
  2. 绑定地址和端口:使用 bind() 函数将 Socket 与一个 IP 地址和端口号绑定,这样客户端才能找到它。
  3. 监听连接:使用 listen() 函数让 Socket 进入被动监听状态,等待客户端的连接请求。
  4. 接受连接:使用 accept() 函数从等待队列中取出一个客户端连接请求,并创建一个新的 Socket 与该客户端进行通信,原 Socket 继续监听新的连接。
  5. 收发数据:使用 send()recv() (或 read()/write()) 函数与客户端进行数据交换。
  6. 关闭连接:通信结束后,使用 close() 关闭与客户端的 Socket,并最终关闭监听 Socket。

TCP 客户端 流程

  1. 创建 Socket:同样使用 socket() 函数创建一个套接字描述符。
  2. 连接服务器:使用 connect() 函数主动向服务器的 IP 地址和端口号发起连接请求。
  3. 收发数据:连接建立后,使用 send()recv() (或 read()/write()) 函数与服务器进行数据交换。
  4. 关闭连接:通信结束后,使用 close() 关闭 Socket。

关键 API 函数

这些函数在 <sys/socket.h> (Linux) 或 <winsock2.h> (Windows) 中定义。

函数 功能 服务器/客户端
socket() 创建一个 Socket,返回一个描述符。 服务器 & 客户端
bind() 将 Socket 与指定的 IP 地址和端口号绑定。 服务器
listen() 将 Socket 设为监听状态,等待客户端连接。 服务器
accept() 接受客户端的连接请求,返回一个新的通信 Socket。 服务器
connect() 主动向服务器发起连接请求。 客户端
send() / write() 通过已连接的 Socket 发送数据。 服务器 & 客户端
recv() / read() 通过已连接的 Socket 接收数据。 服务器 & 客户端
close() / closesocket() 关闭 Socket,释放资源。 服务器 & 客户端

重要提示 (Windows vs. Linux):

  • 头文件:Linux 用 <sys/socket.h>,Windows 用 <winsock2.h>
  • 库文件:Linux 默认链接,Windows 需要额外链接 ws2_32.lib 库。
  • 初始化与清理:Windows 使用 Socket 前,必须先调用 WSAStartup() 初始化;程序结束时,必须调用 WSACleanup() 清理,Linux 不需要。

完整代码示例 (TCP)

下面是一个经典的“回显服务器” (Echo Server) 的完整代码,服务器接收客户端发来的任何消息,然后原样发回。

c语言socket通信编程
(图片来源网络,侵删)

1 服务器端代码 (server.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // 用于 read, write, close
#include <sys/socket.h>
#include <netinet/in.h> // 用于 sockaddr_in 结构体
#include <arpa/inet.h>  // 用于 inet_addr 函数
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    // 1. 创建 Socket (AF_INET for IPv4, SOCK_STREAM for TCP)
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    // 设置 Socket 选项,允许地址重用
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    // 2. 绑定 地址和端口
    address.sin_family = AF_INET; // IPv4
    address.sin_addr.s_addr = INADDR_ANY; // 绑定到所有可用的网络接口
    address.sin_port = htons(PORT); // 将端口号从主机字节序转换为网络字节序
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    // 3. 监听 连接
    if (listen(server_fd, 3) < 0) { // 3 是最大等待连接数
        perror("listen");
        exit(EXIT_FAILURE);
    }
    printf("Server listening on port %d...\n", PORT);
    // 4. 接受 连接
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }
    printf("Connection accepted from %s:%d\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));
    // 5. 收发数据
    int valread;
    while ((valread = read(new_socket, buffer, BUFFER_SIZE)) > 0) {
        printf("Received from client: %s", buffer);
        // 将收到的数据回显给客户端
        send(new_socket, buffer, valread, 0);
        memset(buffer, 0, BUFFER_SIZE); // 清空缓冲区
    }
    if (valread == 0) {
        printf("Client disconnected\n");
    } else if (valread < 0) {
        perror("read error");
    }
    // 6. 关闭连接
    close(new_socket);
    close(server_fd);
    return 0;
}

2 客户端代码 (client.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char *hello = "Hello from client";
    char buffer[BUFFER_SIZE] = {0};
    // 1. 创建 Socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return -1;
    }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    // 将 IPv4 地址从文本转换为二进制形式
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        printf("\nInvalid address/ Address not supported \n");
        return -1;
    }
    // 2. 连接 服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        printf("\nConnection Failed \n");
        return -1;
    }
    printf("Connected to server.\n");
    // 3. 发送数据
    send(sock, hello, strlen(hello), 0);
    printf("Hello message sent\n");
    // 4. 接收数据
    int valread = read(sock, buffer, BUFFER_SIZE);
    printf("Server response: %s\n", buffer);
    // 5. 关闭连接
    close(sock);
    return 0;
}

编译与运行

在 Linux/macOS 上

  1. 编译: 打开两个终端,分别编译服务器和客户端。

    # 终端 1: 编译服务器
    gcc server.c -o server
    # 终端 2: 编译客户端
    gcc client.c -o client
  2. 运行

    • 在一个终端中运行服务器:
      ./server

      你会看到 Server listening on port 8080...

    • 在另一个终端中运行客户端:
      ./client

      客户端会输出:

      c语言socket通信编程
      (图片来源网络,侵删)
      Connected to server.
      Hello message sent
      Server response: Hello from client
    • 服务器终端会显示:
      Server listening on port 8080...
      Connection accepted from 127.0.0.1:54321 (端口号可能不同)
      Received from client: Hello from client
      Client disconnected

在 Windows 上 (使用 MinGW/GCC)

  1. 编译: 打开两个命令提示符 (CMD) 或 PowerShell 窗口。

    # 窗口 1: 编译服务器 (需要链接 ws2_32.lib)
    gcc server.c -o server.exe -lws2_32
    # 窗口 2: 编译客户端
    gcc client.c -o client.exe -lws2_32
  2. 运行

    • 在一个窗口中运行服务器:
      server.exe
    • 在另一个窗口中运行客户端:
      client.exe

      输出结果与 Linux 类似。


UDP Socket 简介

UDP 编程比 TCP 简单,因为它没有连接的概念。

服务器端流程 (UDP)

  1. socket(): 创建 Socket (类型为 SOCK_DGRAM)。
  2. bind(): 绑定 IP 和端口。
  3. recvfrom(): 接收数据,并获取客户端的地址信息。
  4. sendto(): 使用 sendto() 将数据发送回给客户端(需要客户端的地址)。
  5. close(): 关闭 Socket。

客户端流程 (UDP)

  1. socket(): 创建 Socket。
  2. sendto(): 直接使用 sendto() 发送数据给服务器的 IP 和端口。
  3. recvfrom(): 接收服务器的响应。
  4. close(): 关闭 Socket。

核心区别:TCP 是 read/write,而 UDP 是 recvfrom/sendto,因为 UDP 需要知道数据包从哪里来,要发到哪里去。


总结与注意事项

  • 字节序:网络字节序是大端序,在调用 bind(), connect() 等函数前,必须使用 htons(), htonl() 等函数将主机字节序的端口号和地址转换为网络字节序。ntohs(), ntohl() 则用于转换回来。
  • 错误处理:Socket 函数很多情况下都会返回错误(如返回 -1),务必检查每个函数的返回值,并根据 perror()strerror(errno) 打印错误信息,这是调试的关键。
  • 阻塞与非阻塞:默认情况下,accept(), read(), recv() 等函数是阻塞的,即如果没有数据到达,程序会一直等待在这里,更高级的程序会使用 select(), poll(), epoll (Linux) 或 I/O 完成端口 (Windows) 来实现 I/O 多路复用,以处理多个连接。
  • 资源释放:程序退出前,一定要调用 close() 关闭所有打开的 Socket,否则会造成文件描述符泄漏。

希望这份详细的教程能帮助你理解 C 语言的 Socket 编程!从 TCP 开始是最好的起点,因为它更稳定和可靠。

-- 展开阅读全文 --
头像
dede如何添加幻灯片?
« 上一篇 03-04
Linux C图形编程如何实现?
下一篇 » 03-04

相关文章

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

目录[+]