C套接字:FD_ISSET返回true,没有数据等待

时间:2017-05-29 15:05:56

标签: c sockets select

作为一个学校项目,我正在实施一个IRC服务器,我一直坚持这个问题。 我的服务器使用select来选择发送数据的客户端,然后从该用户读取一个命令(命令是\ r \ n分开),解析并执行它然后传递给下一个用户。 用户可以像这样一次发送多个命令:

"command1\r\ncommand2\r\n"

如果发生这种情况,我希望第一个命令被执行,第二个命令保留在流中,以便在下一个select()调用时读取它。 (这样,每个用户每次“转”只执行一次comamnd)。 为此,我为每个客户端提供了一个FILE *流,只要它已连接就保持打开状态。

这项工作非常完美(如果我发送上面指定的双comamnd,这两个命令会一个接一个地执行)。

之后问题就是在那之后,select()继续告诉我套接字中有东西要读(fd返回1的FD_ISSET),所以我的接收函数被调用为fd和getline()在它失败(没有设置errno)并且它永远像这样循环。

我不明白为什么选择仍然认为套接字中有东西要读取以及为什么getline失败而不是阻塞。

有什么想法吗?

编辑:我不允许对此项目使用非阻塞套接字或fork()

“主要”循环:

  while (g_run_server)                                                                
    { 
      fd_max = g_socket_fd;                                                                                                                                                
      FD_ZERO(fds);                                                                       
      FD_SET(g_socket_fd, fds);                                                           
      tmp = users;                                                                        
      while (tmp)                                                                         
        {                                                                                 
          FD_SET(tmp->fd, fds);                                                           
          fd_max = (tmp->fd > fd_max ? tmp->fd : fd_max);                              
          tmp = tmp->next;                                                                
        }
      if (select(fd_max + 1, &fds, NULL, NULL, NULL) < 0)                             
         break;                                                                 
      if (FD_ISSET(g_socket_fd, &fds))                                                
        accept_new_user(&hdl, &users);                                                
      handle_clients(&hdl, &fds);                                                     
    } 

处理和读取客户端输入的函数:

static bool recv_and_execute(t_handle *hdl)
{
  char      *raw;
  size_t    len;

  len = 0;
  raw = NULL;
  if (!hdl->sender->stream &&
      (hdl->sender->stream = fdopen(dup(hdl->sender->fd), "r")) == NULL)
    return (false);
  if (getline(&raw, &len, hdl->sender->stream) != -1)
    {
      printf("Received \"%s\"\n", raw);
      parse_cmd(hdl, raw);
      exec_cmd(hdl);
    }
  else
    printf("Getline failed %s\n", strerror(errno));
  free(raw);
  return (true);
}

int         handle_clients(t_handle *hdl, fd_set *fds)
{
  t_user        *tmp;

  tmp = *hdl->users;
  while (tmp)
    {
      if (FD_ISSET(tmp->fd, fds))
        {
          printf("fd %d is ready to be read\n", tmp->fd);
          hdl->sender = tmp;
          recv_and_execute(hdl);
          FD_CLR(tmp->fd, fds);
      tmp = tmp->next;
      if (hdl->sender->status == DEAD)
        del_user(hdl->users, hdl->sender);
        }
      else
        tmp = tmp->next;
    }
  return (0);
}

当我连接一个客户端并发送“USER foo \ r \ nUSER no bo dy:wa \ r \ n”时,这是输出:

fd 4 is ready to be read
Received "NICK foo
"
[DEBUG] Executing "NICK" with params "foo" "(null)" "(null)" "(null)"
[INFO] Nickname successfully changed.
fd 4 is ready to be read
Received "USER no bo dy :wa
"
[DEBUG] Executing "USER" with params "no" "bo" "dy" ":wa"
[INFO] User registered.
fd 4 is ready to be read
Getline failed Success
fd 4 is ready to be read
Getline failed Success
fd 4 is ready to be read
Getline failed Success
fd 4 is ready to be read
Getline failed Success
fd 4 is ready to be read
Getline failed Success
continue like this....

编辑:我根据alk的评论编辑了我的接收功能:

static bool     recv_and_execute(t_handle *hdl)                                               
{                                                                                             
  char          *raw;                                                                         
  size_t        len;                                                                          
  ssize_t       nread;                                                                        

  len = 0;                                                                                    
  raw = NULL;                                                                                 
  if (!hdl->sender->stream &&                                                                 
      (hdl->sender->stream = fdopen(dup(hdl->sender->fd), "r")) == NULL)                      
    return (false);                                                                           
  errno = 0;                                                                                  
  if ((nread = getline(&raw, &len, hdl->sender->stream)) > 0)                                 
    {                                                                                         
      printf("Received \"%s\"\n", raw);                                                       
      parse_cmd(hdl, raw);                                                                    
      exec_cmd(hdl);                                                                          
    }                                                                                         
  else if (errno != 0)                                                                        
    printf("getline failed %s\n", strerror(errno));                                           
  else {                                                                                      
    printf("EOF reached\n");                                                                  
    fclose(hdl->sender->stream);                                                              
    hdl->sender->stream = NULL;                                                               
  }                                                                                           
  printf("nread = %zd\n", nread);                                                             
  free(raw);                                                                                  
  return (true);                                                                              
}

所以这次,当EOF到达时(getline返回-1),我关闭流并将其设置为NULL,以便下次在套接字fd上选择查找数据时重新打开。但即使我关闭流,选择仍然检测到有可用数据并且无限循环继续:/

fd 4 is ready to be read                                                                       
Received "NICK foo^M                                                                           
"                                                                                              
nread = 10                                                                                     
fd 4 is ready to be read                                                                       
Received "USER no bo dy :wa^M                                                                  
"                                                                                              
nread = 19                                                             
fd 4 is ready to be read                                                                       
EOF reached                                                                                    
nread = -1                                                                                     
fd 4 is ready to be read                                                                       
EOF reached                                                                                    
nread = -1                                                                                     
fd 4 is ready to be read                                                                       
EOF reached                                                                                    
nread = -1
and so on... 

1 个答案:

答案 0 :(得分:0)

我很确定您使用的是select错误。我向您展示了一个关于如何使用它的简单代码示例(我没有处理很多错误),您可以根据需要进行编辑。

/*
 * If you read at man select bugs you can see
 * that select could return that someone is 
 * ready but it isn't true
 */
    int fd_c;
    fd_set rdset;
    fd_set set; /*That's the mask you'll use when new requests arrive*/
    FD_ZERO(&set);  /*Clears the mask*/
    FD_SET(g_socket_fd,&set); /*Set the listening socket as ready*/
    while(g_run_server){
        /*
        * YOU MUST INITIALIZATE IT EVERY LOOP
        * read @ man select
        */
        rdset = set;
        if(select((fd_num_max+1),(&rdset),NULL,NULL,NULL) < 0){
            perror("Failed on select\n");
            exit(EXIT_FAILURE);
        }
        /*You go through the ready clients in the rdset*/
        for(fd=0;fd<=fd_num_max;fd++) {
            if(FD_ISSET(fd,&rdset)) { /*If the bit is set*/
                if(fd == fd_skt) { /*If it's a new client*/
                    /*You can handle the new client here or delegete it to someone*/
                    fd_c=accept(fd_skt,NULL,0); /*File descriptor of new client*/
                    FD_SET(fd_c,&set); 
                    if(fd_c > fd_num_max) fd_num_max = fd_c;
                }else { /*If it's a request from an existing client*/
                    /*You can handle the new request here or delegete it to someone*/
                    FD_SET(fd,&set);
                }
            }
        }
    }

您还应该以这种方式修改static bool recv_and_execute(t_handle *hdl)

errno = 0;
if ((nread = getline(&raw, &len, hdl->sender->stream)) != -1){                                                                                         
      printf("Received \"%s\"\n", raw);                                                       
      parse_cmd(hdl, raw);                                                                    
      exec_cmd(hdl);                                                                          

}else{
    if( errno == 0){
        printf("EOF reached\n");                                                                  
        fclose(hdl->sender->stream);                                                              
        hdl->sender->stream = NULL;
    }else{
        printf("getline failed %s\n", strerror(errno)); 
        exit(EXIT_FAILURE); /*You must handle it in some way, exiting or doing something*/
    }
}