C语言socket实例如何实现通信连接?

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

核心概念简介

在开始之前,我们先简单理解几个核心概念:

c语言 socket 实例
(图片来源网络,侵删)
  1. Socket (套接字):可以看作是两个程序之间进行通信的“端点”,它就像一个电话插座,你把电话(程序)插上去,就可以和其他插着电话的插座进行通信。
  2. IP 地址:网络中设备的唯一地址,就像你家的门牌号。0.0.1 是本地回环地址,代表本机。
  3. 端口号:一台机器上可以运行多个网络服务,端口号用来区分这些服务,它就像公寓里的房间号。80 通常用于 HTTP 服务。
  4. TCP (传输控制协议):一种面向连接的、可靠的通信协议,通信前必须先建立连接(三次握手),就像打电话前要先拨号并对方接听一样。
  5. 通信流程
    • 服务器:创建一个监听 Socket -> 绑定 IP 和端口 -> 开始监听 -> 等待并接受客户端连接 -> 与客户端收发数据 -> 关闭连接。
    • 客户端:创建一个 Socket -> 连接服务器的 IP 和端口 -> 与服务器收发数据 -> 关闭连接。

准备工作:编译环境

在 Linux 或 macOS 上,系统通常已经自带了必要的头文件和库,在 Windows 上,你可能需要一些额外的设置。

  • Linux/macOS: 直接使用 gcc 编译即可。
  • Windows:
    1. 你需要一个 C 语言编译器,MinGW(包含在 MSYS2 或 Git for Windows 中)。
    2. 在编译时,需要链接 ws2_32.lib 库,在命令行中,这通常通过 -lws2_32 参数实现。

实例代码

我们将创建两个文件:server.cclient.c

服务器端代码 (server.c)

服务器会启动并等待客户端连接,当客户端连接后,它会接收客户端发来的消息,然后回复一个固定的消息。

// server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // 用于 read, write, close
#include <sys/socket.h> // 用于 socket, bind, listen, accept
#include <netinet/in.h> // 用于 struct sockaddr_in 和 htons
#include <arpa/inet.h>  // 用于 inet_addr
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
    int server_fd, client_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    char *hello = "Hello from server";
    // 1. 创建 socket (文件描述符)
    // AF_INET: IPv4
    // SOCK_STREAM: TCP
    // 0: 默认协议 (IPPROTO_TCP)
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    // 设置 socket 选项,允许地址重用
    // 这在快速重启服务器时很有用,可以避免 "Address already in use" 错误
    int opt = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    address.sin_family = AF_INET; // IPv4
    address.sin_addr.s_addr = INADDR_ANY; // 绑定到所有可用的网络接口
    address.sin_port = htons(PORT); // 将端口号从主机字节序转换为网络字节序
    // 2. 绑定 socket 到 IP 和端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    // 3. 开始监听连接
    // SOMAXCONN: 系统允许的最大连接数
    if (listen(server_fd, SOMAXCONN) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    printf("Server listening on port %d...\n", PORT);
    // 4. 接受一个客户端连接
    // accept 会阻塞,直到有客户端连接
    if ((client_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }
    printf("Client connected: %s:%d\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));
    // 5. 从客户端读取数据
    int valread = read(client_socket, buffer, BUFFER_SIZE);
    printf("Client message: %s\n", buffer);
    // 6. 向客户端发送数据
    send(client_socket, hello, strlen(hello), 0);
    printf("Hello message sent\n");
    // 7. 关闭 socket
    close(client_socket);
    close(server_fd);
    return 0;
}

客户端代码 (client.c)

客户端会连接到指定的服务器 IP 和端口,发送一条消息,然后等待并接收服务器的回复。

c语言 socket 实例
(图片来源网络,侵删)
// client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // 用于 read, write, close
#include <sys/socket.h> // 用于 socket, connect
#include <netinet/in.h> // 用于 struct sockaddr_in 和 htons
#include <arpa/inet.h>  // 用于 inet_addr
#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; // IPv4
    serv_addr.sin_port = htons(PORT); // 端口号
    // 将 IPv4 地址从文本转换为二进制形式
    // 这里我们连接本机服务器,所以用 127.0.0.1
    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 message: %s\n", buffer);
    // 5. 关闭 socket
    close(sock);
    return 0;
}

如何编译和运行

注意:你必须先启动服务器,然后再启动客户端,因为服务器在 accept() 处是阻塞的,它会一直等待客户端的到来。

步骤 1:编译代码

打开两个终端窗口。

在第一个终端(用于编译服务器):

c语言 socket 实例
(图片来源网络,侵删)
gcc server.c -o server

这会生成一个名为 server 的可执行文件。

在第二个终端(用于编译客户端):

gcc client.c -o client

这会生成一个名为 client 的可执行文件。

步骤 2:运行服务器

在第一个终端中运行服务器程序:

./server

你会看到输出:

Server listening on port 8080...

服务器正在等待客户端连接,程序会停在这里。

步骤 3:运行客户端

在第二个终端中运行客户端程序:

./client

你会看到客户端的输出:

Connected to server.
Hello message sent
Server message: Hello from server

步骤 4:观察服务器的输出

切换回第一个终端(服务器的终端),你会看到它之前等待的地方现在有了输出:

Server listening on port 8080...
Client connected: 127.0.0.1:54321  // 端口号可能会不同
Client message: Hello from client
Hello message sent

之后,服务器和客户端的终端都会正常退出,程序结束。


Windows 平台的特殊说明

如果你在 Windows 上使用 MinGW (GCC) 进行编译,命令稍有不同:

  1. 编译时需要链接 ws2_32
    • 服务器编译:
      gcc server.c -o server.exe -lws2_32
    • 客户端编译:
      gcc client.c -o client.exe -lws2_32
  2. 头文件和函数
    • unistd.h 在 Windows 上不存在,其功能由 io.hsocket.h 等提供,现代 MinGW 通常提供了 unistd.h 的兼容层。
    • close() 函数在 Windows 上通常用 closesocket() 替代,为了更好的跨平台性,可以使用 #ifdef 来区分,但在简单示例中,直接使用 close() 通常也能工作,因为 MinGW 的 close closesocket 的一个包装。
    • 你可能需要包含 <winsock2.h><ws2tcpip.h>,而不是 <sys/socket.h><netinet/in.h>,但很多现代的 MinGW 工具链也提供了 POSIX 风格的头文件。

如果你使用 Visual Studio (MSVC),则项目设置和链接方式完全不同,需要配置项目的链接器依赖项。

代码中的关键函数解析

函数 作用 服务器端 客户端
socket() 创建一个通信端点(套接字)
bind() 将套接字与特定的 IP 地址和端口号绑定
listen() 将套接字设置为被动模式,准备接受连接请求
accept() 接受一个连接请求,并返回一个新的套接字用于通信
connect() 主动发起一个连接到指定的服务器
send() / write() 通过套接字发送数据
recv() / read() 通过套接字接收数据
close() / closesocket() 关闭套接字,释放资源
htons() Host to Network Short (16位)
inet_addr() / inet_pton() 将点分十进制 IP 地址转换为网络字节序的二进制格式

这个例子是 Socket 编程的基石,理解了它,你就可以在此基础上进行更复杂的开发,例如处理多个客户端(多线程、selectpollepoll)、处理更复杂的协议等。

-- 展开阅读全文 --
头像
Sublime C语言插件哪个最好用?
« 上一篇 今天
织梦伪静态后为何不能翻页?
下一篇 » 今天

相关文章

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

目录[+]