【问题标题】:select() and non-blocking recv with dynamic buffer on Cselect() 和 C 上带有动态缓冲区的非阻塞 recv
【发布时间】:2016-03-26 01:13:45
【问题描述】:

我试图弄清楚为什么 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");
                }   
            }   
        }   
    }
}

【问题讨论】:

  • 我没有看到任何对 setsockopt(SOL_SOCKET, SO_NONBLOCK, ...) 的调用会将您的套接字设置为非阻塞。您的示例中是否省略了该内容,或者您​​的程序中实际上缺少该内容?
  • 它没有设置为SO_NONBLOCK,我认为select() 会处理它。谢谢。
  • 如果select() 有任何这种神奇的效果,它会在文档中说明。

标签: c sockets blocking nonblocking


【解决方案1】:

首先,如果您不想阻塞,则必须将套接字设置为非阻塞。无论您做什么,如果您的套接字操作被阻塞,都无法保证您永远不会阻塞。这是一个常见的错误,已导致严重的错误,具有重大的安全隐患。如果一定不能阻塞,则必须将套接字设置为非阻塞。

据我所知,此时 select() 处于准备阅读状态。

您的意思是 处于准备阅读状态。 select 函数是一个状态报告函数,与其他状态报告函数一样,它报告您调用它和返回之间某个时间点的状态。不能保证在以后的某个时间仍然是状态。

永远不要认为您可以排除状态可能发生变化的所有其他可能方式。历史上不乏有这种想法并被烧死的人。

但是你阻塞的具体原因是你在没有先调用select的情况下调用recv(因为你有一个do循环再次调用recv)。

无论如何,您必须正确处理来自recvWOULDBLOCK 指示。这是必不可少的。

此外,您可能希望每次select 命中只调用一次recv。如果你解决了其他问题,那就没关系了。

【讨论】:

  • 感谢您指出这一点,现在更清楚了。我仍然不明白的一件事是,为什么即使在通过 telnet 发送 GET / HTTP/1.1 之后,recv 也会挂起?这不应该告诉 recv 数据可用吗?
  • 确实如此,然后您调用recv 并获取数据。但随后您的do 循环再次调用recv。由于套接字是阻塞的,它会阻塞直到数据可用。您必须将套接字设置为非阻塞并处理来自recvWOULDBLOCK 指示,这将在所有情况下解决问题。但是您可能还希望消除do 循环并在每次select 命中时仅调用一次recv 以解决常见情况下的问题。
猜你喜欢
  • 2010-10-29
  • 2016-06-04
  • 1970-01-01
  • 2011-03-03
  • 2011-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多