Linux 用c语言实现TCP 客户端读取数据,如何保证既不阻塞,又能稳定获取数据


在Linux下使用C语言实现TCP客户端时,既要避免阻塞,又要稳定地获取数据,通常可以采用以下几种方法:

1. 使用非阻塞模式(Non-blocking Mode)
将套接字设置为非阻塞模式后,recv函数在没有数据可读时会立即返回,而不是阻塞等待。可以通过fcntl函数设置套接字为非阻塞模式。


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

void set_nonblocking(int sockfd) {
    int flags = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
}

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    // 连接服务器等操作...

    set_nonblocking(sockfd);

    char buffer[1024];
    while (1) {
        ssize_t n = recv(sockfd, buffer, sizeof(buffer), 0);
        if (n > 0) {
            // 处理接收到的数据
            buffer[n] = '\0';
            printf("Received: %s\n", buffer);
        } else if (n == 0) {
            // 连接关闭
            printf("Connection closed\n");
            break;
        } else {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                // 没有数据可读,继续循环
                usleep(100000); // 避免忙等待,适当休眠
            } else {
                // 其他错误
                perror("recv");
                break;
            }
        }
    }

    close(sockfd);
    return 0;
}
2. 使用select函数
select函数可以同时监控多个文件描述符的状态(可读、可写、异常等),并设置超时时间,避免阻塞。


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

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    // 连接服务器等操作...

    fd_set read_fds;
    struct timeval timeout;

    char buffer[1024];
    while (1) {
        FD_ZERO(&read_fds);
        FD_SET(sockfd, &read_fds);

        timeout.tv_sec = 1;  // 设置超时时间为1秒
        timeout.tv_usec = 0;

        int ret = select(sockfd + 1, &read_fds, NULL, NULL, &timeout);
        if (ret > 0) {
            if (FD_ISSET(sockfd, &read_fds)) {
                ssize_t n = recv(sockfd, buffer, sizeof(buffer), 0);
                if (n > 0) {
                    // 处理接收到的数据
                    buffer[n] = '\0';
                    printf("Received: %s\n", buffer);
                } else if (n == 0) {
                    // 连接关闭
                    printf("Connection closed\n");
                    break;
                } else {
                    perror("recv");
                    break;
                }
            }
        } else if (ret == 0) {
            // 超时,继续循环
            printf("Timeout, no data received\n");
        } else {
            perror("select");
            break;
        }
    }

    close(sockfd);
    return 0;
}
3. 使用poll函数
poll函数与select类似,但可以处理更多的文件描述符,并且没有文件描述符数量的限制。
#include <poll.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    // 连接服务器等操作...

    struct pollfd fds[1];
    fds[0].fd = sockfd;
    fds[0].events = POLLIN;

    char buffer[1024];
    while (1) {
        int ret = poll(fds, 1, 1000);  // 设置超时时间为1000毫秒
        if (ret > 0) {
            if (fds[0].revents & POLLIN) {
                ssize_t n = recv(sockfd, buffer, sizeof(buffer), 0);
                if (n > 0) {
                    // 处理接收到的数据
                    buffer[n] = '\0';
                    printf("Received: %s\n", buffer);
                } else if (n == 0) {
                    // 连接关闭
                    printf("Connection closed\n");
                    break;
                } else {
                    perror("recv");
                    break;
                }
            }
        } else if (ret == 0) {
            // 超时,继续循环
            printf("Timeout, no data received\n");
        } else {
            perror("poll");
            break;
        }
    }

    close(sockfd);
    return 0;
}
4. 使用epoll(适用于高并发场景)
epoll是Linux特有的高效I/O多路复用机制,适用于高并发场景。


#include <sys/epoll.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    // 连接服务器等操作...

    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1");
        return 1;
    }

    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = sockfd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &event) == -1) {
        perror("epoll_ctl");
        return 1;
    }

    struct epoll_event events[1];
    char buffer[1024];
    while (1) {
        int ret = epoll_wait(epoll_fd, events, 1, 1000);  // 设置超时时间为1000毫秒
        if (ret > 0) {
            if (events[0].data.fd == sockfd) {
                ssize_t n = recv(sockfd, buffer, sizeof(buffer), 0);
                if (n > 0) {
                    // 处理接收到的数据
                    buffer[n] = '\0';
                    printf("Received: %s\n", buffer);
                } else if (n == 0) {
                    // 连接关闭
                    printf("Connection closed\n");
                    break;
                } else {
                    perror("recv");
                    break;
                }
            }
        } else if (ret == 0) {
            // 超时,继续循环
            printf("Timeout, no data received\n");
        } else {
            perror("epoll_wait");
            break;
        }
    }

    close(epoll_fd);
    close(sockfd);
    return 0;
}
总结
非阻塞模式:简单直接,但需要手动控制循环和休眠。

select:适合处理少量文件描述符,跨平台兼容性好。

poll:与select类似,但可以处理更多文件描述符。

epoll:适合高并发场景,性能最好,但仅适用于Linux。

根据具体需求选择合适的方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值