select() 和 C 上带有动态缓冲区的非阻塞 recv
select() and non-blocking recv with dynamic buffer on C
我试图弄清楚为什么 recv 在下面的代码中被阻塞,如果我 telnet 并发送 'GET / HTTP/1.1',recv 一直在等待数据并阻塞另一个 telnet 连接。但是,如果我只使用固定缓冲区而不是 do{} while
,即
,它工作正常并且不会阻塞
char buffer[1024];
nbytes = recv(i, buffer, sizeof buffer, 0);
据我所知,select()
此时处于可以阅读的状态。我需要将 recv
设置为 O_NONBLOCK
吗?
char *buffer = NULL;
unsigned long LEN = 200;
unsigned long bytes_received = 0;
unsigned long cur_size = 0;
int status = 0;
FD_SET(listener, &master);
fdmax = listener;
for(;;){
read_fds = master; // copy it
if(select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1){
exit(4);
}
for(i = 0; i <= fdmax; i++){
if(FD_ISSET(i, &read_fds)){
if(i == listener){
// handle new connections
addrlen = sizeof remoteaddr;
newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);
if(newfd == -1){
perror("accept");
}else{
FD_SET(newfd, &master);
if(newfd > fdmax) {
fdmax = newfd;
}
}
}else{
do{
if(bytes_received >= cur_size){
char * tmp;
cur_size += LEN;
tmp = realloc(buffer, cur_size);
buffer = tmp;
}
status = recv(i, buffer + bytes_received, LEN, 0);
if(status == 0){
printf("Done\n");
}
else if(status > 0){
bytes_received += status;
}
else{
fprintf(stderr, "socket error\n");
}
} while (status > 0);
if(send(i, buffer, strlen(buffer), 0) == -1){
perror("send");
}
}
}
}
}
首先,如果不想阻塞,就必须将套接字设置为非阻塞。无论您做什么,如果您的套接字操作阻塞,则无法确保您永远不会阻塞。这是一个常见的错误,它导致了具有重大安全隐患的严重错误。如果一定不能阻塞,就必须把socket设置为非阻塞。
What I understand so far, select() at that point is in a ready-to-read state.
您的意思是处于可以阅读的状态。 select
函数是一个状态报告函数,与其他状态报告函数一样,它报告调用它和返回时之间某个时间点的状态。不能保证在以后的某个时间点仍然是状态。
永远不要认为您可以排除状态可能发生变化的所有其他可能方式。历史上到处都是这样想并被烧毁的人。
但是您阻塞的具体原因是您在调用 recv
之前没有先调用 select
(因为您有一个 do
循环再次调用 recv
).
无论如何,你必须正确处理来自recv
的WOULDBLOCK
指示。这很重要。
此外,您可能希望每次 select
命中仅调用一次 recv
。如果你解决了其他问题,那就没关系了。
我试图弄清楚为什么 recv 在下面的代码中被阻塞,如果我 telnet 并发送 'GET / HTTP/1.1',recv 一直在等待数据并阻塞另一个 telnet 连接。但是,如果我只使用固定缓冲区而不是 do{} while
,即
char buffer[1024];
nbytes = recv(i, buffer, sizeof buffer, 0);
据我所知,select()
此时处于可以阅读的状态。我需要将 recv
设置为 O_NONBLOCK
吗?
char *buffer = NULL;
unsigned long LEN = 200;
unsigned long bytes_received = 0;
unsigned long cur_size = 0;
int status = 0;
FD_SET(listener, &master);
fdmax = listener;
for(;;){
read_fds = master; // copy it
if(select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1){
exit(4);
}
for(i = 0; i <= fdmax; i++){
if(FD_ISSET(i, &read_fds)){
if(i == listener){
// handle new connections
addrlen = sizeof remoteaddr;
newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);
if(newfd == -1){
perror("accept");
}else{
FD_SET(newfd, &master);
if(newfd > fdmax) {
fdmax = newfd;
}
}
}else{
do{
if(bytes_received >= cur_size){
char * tmp;
cur_size += LEN;
tmp = realloc(buffer, cur_size);
buffer = tmp;
}
status = recv(i, buffer + bytes_received, LEN, 0);
if(status == 0){
printf("Done\n");
}
else if(status > 0){
bytes_received += status;
}
else{
fprintf(stderr, "socket error\n");
}
} while (status > 0);
if(send(i, buffer, strlen(buffer), 0) == -1){
perror("send");
}
}
}
}
}
首先,如果不想阻塞,就必须将套接字设置为非阻塞。无论您做什么,如果您的套接字操作阻塞,则无法确保您永远不会阻塞。这是一个常见的错误,它导致了具有重大安全隐患的严重错误。如果一定不能阻塞,就必须把socket设置为非阻塞。
What I understand so far, select() at that point is in a ready-to-read state.
您的意思是处于可以阅读的状态。 select
函数是一个状态报告函数,与其他状态报告函数一样,它报告调用它和返回时之间某个时间点的状态。不能保证在以后的某个时间点仍然是状态。
永远不要认为您可以排除状态可能发生变化的所有其他可能方式。历史上到处都是这样想并被烧毁的人。
但是您阻塞的具体原因是您在调用 recv
之前没有先调用 select
(因为您有一个 do
循环再次调用 recv
).
无论如何,你必须正确处理来自recv
的WOULDBLOCK
指示。这很重要。
此外,您可能希望每次 select
命中仅调用一次 recv
。如果你解决了其他问题,那就没关系了。