在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。
根据具体需求选择合适的方案。

被折叠的 条评论
为什么被折叠?



