You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言服务器终止相关客户端代码解析及技术咨询

服务器终止场景下的C语言客户端代码解析与问题解答

首先先把你提供的(不完整的)代码贴出来,方便咱们分析:

int main(void) { 
    int sock_fd = connect_to_server(PORT, "127.0.0.1"); 
    char username[50]; 
    // 提示输入用户名 
    printf("Username: \n"); 
    // 从标准输入读取用户名 
    scanf("%s", username); 
    // 将用户名写入套接字 
    write(sock_fd, username, 50); 
    char buf[BUF_SIZE + 1]; 
    // 初始化文件描述符集 
    fd_set read_fds; 
    // 将所有文件描述符位清零 
    FD_ZERO(&read_fds); 
    while (1) { 
        int num_read; 
        char myString[10]; 
        myString[0] = '\0'; 
        if (write(sock_fd, myStri... 
    }

一、现有代码的核心逻辑拆解

咱们一步一步捋这段代码的意图:

  1. 服务器连接:调用封装好的connect_to_server函数,连接本地回环地址(127.0.0.1)的指定端口PORT,拿到套接字描述符sock_fd——这里要注意,代码没做连接失败的判断,如果connect_to_server返回-1,后续的读写操作直接就炸了。
  2. 用户名交互:提示用户输入用户名,用scanf读取后,固定写50字节到套接字。这里有两个小问题:
    • scanf("%s", username)没有限制输入长度,要是用户输入超过49个字符,直接就缓冲区溢出了,属于安全隐患。
    • 固定写50字节,不管用户名实际长度,会把数组里未初始化的垃圾数据也发给服务器,完全没必要。
  3. IO多路复用准备:初始化了fd_set并清零,这明显是打算用select之类的工具同时监听用户输入和服务器消息——毕竟是聊天类客户端的常见写法,循环里应该是要处理双向通信。
  4. 未完成的循环:代码到write(sock_fd, myStri...就截断了,推测是想把用户输入的myString发给服务器,但逻辑没写完。

二、服务器终止场景下的核心问题与处理方案

当服务器突然终止(比如进程被kill、崩溃、网络断开),客户端会遇到几个典型问题,咱们逐个说:

1. 服务器主动断开连接(FIN包)

服务器终止时,会给客户端发FIN包,此时客户端的sock_fd处于可读状态,调用read会返回0——这是TCP协议里明确的“连接关闭”信号。如果代码没处理这个情况,客户端会一直循环,以为没收到消息,其实连接已经断了。

2. 客户端尝试写已关闭的连接

如果客户端在服务器断开后还继续写数据,第一次write可能会返回正常(因为TCP还有缓冲区),但第二次写就会触发SIGPIPE信号,默认情况下这个信号会直接杀死客户端进程。要避免这个情况,要么给write加MSG_NOSIGNAL标志,要么提前捕捉SIGPIPE信号。

3. 现有代码的优化与修复

咱们把这段代码补全,加上服务器终止的处理逻辑:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <errno.h>

#define PORT 8080
#define BUF_SIZE 1024

// 假设这是封装好的连接函数
int connect_to_server(int port, const char* ip) {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    inet_pton(AF_INET, ip, &serv_addr.sin_addr);
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("connect failed");
        return -1;
    }
    return sock;
}

int main(void) { 
    int sock_fd = connect_to_server(PORT, "127.0.0.1"); 
    if (sock_fd == -1) {
        exit(EXIT_FAILURE);
    }

    char username[50]; 
    printf("Username: "); 
    // 限制输入长度,避免缓冲区溢出
    scanf("%49s", username); 
    // 只发送实际需要的字节(包含字符串终止符)
    write(sock_fd, username, strlen(username) + 1); 

    char buf[BUF_SIZE + 1]; 
    fd_set read_fds; 

    while (1) { 
        FD_ZERO(&read_fds); 
        // 同时监听标准输入和套接字
        FD_SET(STDIN_FILENO, &read_fds);
        FD_SET(sock_fd, &read_fds);

        // 阻塞等待事件发生
        int activity = select(sock_fd + 1, &read_fds, NULL, NULL, NULL);
        if (activity < 0 && errno != EINTR) {
            perror("select error");
            break;
        }

        // 处理服务器消息
        if (FD_ISSET(sock_fd, &read_fds)) {
            int num_read = read(sock_fd, buf, BUF_SIZE);
            if (num_read == 0) {
                printf("⚠️ Server has terminated the connection.\n");
                close(sock_fd);
                break;
            } else if (num_read < 0) {
                perror("read error");
                close(sock_fd);
                break;
            }
            buf[num_read] = '\0';
            printf("Server: %s\n", buf);
        }

        // 处理用户输入
        if (FD_ISSET(STDIN_FILENO, &read_fds)) {
            char myString[10]; 
            if (fgets(myString, sizeof(myString), stdin) == NULL) {
                break;
            }
            // 去掉换行符
            size_t len = strlen(myString);
            if (len > 0 && myString[len-1] == '\n') {
                myString[len-1] = '\0';
            }
            // 发送消息,处理EPIPE错误
            ssize_t bytes_written = write(sock_fd, myString, strlen(myString) + 1);
            if (bytes_written < 0) {
                if (errno == EPIPE) {
                    printf("❌ Server connection closed, cannot send message.\n");
                    close(sock_fd);
                    break;
                }
                perror("write error");
                break;
            }
        }
    }
    return 0;
}

这段修复后的代码做了这些关键优化:

  • 增加了连接失败的判断,避免无效的后续操作。
  • 用scanf("%49s", username)限制输入长度,解决缓冲区溢出问题。
  • 发送用户名时只发实际长度的字节,避免垃圾数据。
  • 用select同时监听用户输入和套接字,及时响应服务器断开事件。
  • 处理read返回0的情况,提示用户服务器已断开并退出循环。
  • 处理write时的EPIPE错误,避免进程被SIGPIPE杀死。

内容的提问来源于stack exchange,提问作者static

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:32:51