【问题标题】:Why is the select call not blocking on a unix domain socket?为什么选择调用不会在 unix 域套接字上阻塞?
【发布时间】:2014-09-19 16:55:21
【问题描述】:

我用谷歌搜索了很多,但没有得到答案,因此在这里发布。

在下面的 C 程序(服务器代码)中,我想要一个监听 /tmp/unix-test-socket 的 Unix 域套接字服务器。我的问题是客户端代码能够成功连接到服务器。但是,一旦它连接并且我“接受”了连接,select 调用就不会阻塞。

让我解释一下。

最初 unix_domain_socket = 3

一旦我收到第一个请求,就接受连接并将其存储在 unix_domain_socket_connections[max_unix_domain_socket_connections] 中。套接字fd的值为4。

当我运行服务器代码时,会进入循环,因为 select 调用认为套接字 4 中始终存在数据。

我将 CLIENT 端运行为:

./unix-client "/tmp/unix-test-socket" SEND_DATA

服务器端的输出:

Client sent us a message!

Successfully accepted the new ION connection with fd 4!

[program_select_to_look_at_right_sockets]: Storing fd 4

Data Arrived on UNIX domain socket 4

length 10 SEND_DATA  <-- I get the data sent by the client

[program_select_to_look_at_right_sockets]: Storing fd 4 *<-- Why isnt select blocking and why does it think there is still data on socket 4*

Data Arrived on UNIX domain socket 4

[program_select_to_look_at_right_sockets]: Storing fd 4

Data Arrived on UNIX domain socket 4

服务器代码:

int unix_domain_socket = 0;

int max_unix_domain_socket_connections;
int unix_domain_socket_connections[2];

char *unix_domain_socket_name = "/tmp/unix-test-socket";

int open_unix_domain_server()
{
  int socket_fd, result;
  struct sockaddr_un name;
  int client_sent_quit_message;
  socklen_t socket_length;

  max_unix_domain_socket_connections = 0;
  memset((void *) &name, 0, sizeof(name));

  socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
  name.sun_family = AF_UNIX;
  strcpy(name.sun_path, unix_domain_socket_name);
  socket_length = strlen(name.sun_path) + sizeof(name.sun_family);

  /* Remove this socket if it already exists */
  unlink(name.sun_path);

  result = bind(socket_fd, (struct sockaddr *) &name, socket_length);

  if (result < 0)
    goto Error;

  result = listen(socket_fd, MAX_UNIX_DOMAIN_SOCKETS);

  return socket_fd;

  Error:

  printf("[%s] Error in either listen or bind!\n", __FUNCTION__);

  return -1;

}

int accept_new_unix_domain_connection()
{
  int client_fd;
  struct sockaddr_un new_connection;
  socklen_t new_conn_length = sizeof(new_connection);

  memset((void *) &new_connection, 0, sizeof(new_connection));

  client_fd = accept(unix_domain_socket, (struct sockaddr *) &new_connection,
      &new_conn_length);

  if (client_fd < 0)
  {
    printf("The following error occurred accept failed %d %d\n", errno,
        unix_domain_socket);
  }

  unix_domain_socket_connections[max_unix_domain_socket_connections] =
      client_fd;

  max_unix_domain_socket_connections++;

  return client_fd;
}

int check_if_new_client_is_unix_domain(fd_set readfds)
{
  int unix_fd = 0;

  for (unix_fd = 0; unix_fd < 2; unix_fd++)
  {
    if (FD_ISSET(unix_domain_socket_connections[unix_fd], &readfds))
    {
      printf("Data Arrived on UNIX domain socket %d\n",
          unix_domain_socket_connections[unix_fd]);
      return 1;
    }
  }

  return 0;
}

int process_data_on_unix_domain_socket(int unix_socket)
{
  int length = 0;
  char* data_from_gridFtp;

  /* First, read the length of the text message from the socket. If
   read returns zero, the client closed the connection. */

  if (read(unix_socket, &length, sizeof(length)) == 0)
    return 0;

  /* Allocate a buffer to hold the text. */
  data_from_gridFtp = (char*) malloc(length + 1);

  /* Read the text itself, and print it. */
  recv(unix_socket, data_from_gridFtp, length, 0);

  printf("length %d %s\n", length, data_from_gridFtp);

  return length;
}

void program_select_to_look_at_right_sockets(fd_set *readfds, int *maxfds)
{
  int unix_fd = 0;

  FD_ZERO(readfds);

  FD_SET(unix_domain_socket, readfds);

  for (unix_fd = 0; unix_fd < 2; unix_fd++)
  {
    if (unix_domain_socket_connections[unix_fd])
    {
      printf("[%s]: Storing fd %d\n", __FUNCTION__,
          unix_domain_socket_connections[unix_fd]);

      FD_SET(unix_domain_socket_connections[unix_fd], readfds);

      if (*maxfds < unix_domain_socket_connections[unix_fd])
        *maxfds = unix_domain_socket_connections[unix_fd];
    }

  }
}

int main(int argc, char**argv)
{
  int result, maxfds, clientfd, loop;
  fd_set readfds;
  int activity;
  socklen_t client_len;
  struct sockaddr_in client_address;

  FD_ZERO(&readfds);

  unix_domain_socket = open_unix_domain_server();

  if (unix_domain_socket < 0)
    return -1;

  maxfds = unix_domain_socket;

  FD_SET(unix_domain_socket, &readfds);

  for (loop = 0; loop < 4; loop++)
  {
    program_select_to_look_at_right_sockets(&readfds, &maxfds);

    activity = select(maxfds + 1, &readfds, NULL, NULL, NULL);

    if (FD_ISSET(unix_domain_socket, &readfds))
    {
      printf("client sent us a message!\n");
      clientfd = accept_new_unix_domain_connection();

      if (clientfd < 0)
        break;
    }
    else if (check_if_new_client_is_unix_domain(readfds))
    {
      process_data_on_unix_domain_socket(clientfd);
    }
  }
}

客户代码:

/* Write TEXT to the socket given by file descriptor SOCKET_FD. */
void write_text(int socket_fd, const char* text)
{
  /* Write the number of bytes in the string, including
   NUL-termination. */
  int length = strlen(text) + 1;
  send(socket_fd, &length, sizeof(length), 0);
  /* Write the string. */
  send(socket_fd, text, length, 0);
}

int main(int argc, char* const argv[])
{
  const char* const socket_name = argv[1];
  const char* const message = argv[2];
  int socket_fd;
  struct sockaddr_un name;
  /* Create the socket. */
  socket_fd = socket(PF_LOCAL, SOCK_STREAM, 0);
  /* Store the server’s name in the socket address. */
  name.sun_family = AF_UNIX;
  strcpy(name.sun_path, socket_name);
  /* Connect the socket. */
  connect(socket_fd, (struct sockaddr *) &name, SUN_LEN(&name));
  /* Write the text on the command line to the socket. */
  write_text(socket_fd, message);
  close(socket_fd);
  return 0;
}

【问题讨论】:

  • 您需要压缩代码量 - 没有人愿意通读您的所有代码来找出问题所在。
  • program_select_to_look_at_right_sockets(打印“Storing fd”消息)在调用select之前之前调用。它只是在描述符集中设置描述符。如果select 会阻塞,它将在该调用之后

标签: c++ c sockets unix-socket


【解决方案1】:

如果远端已关闭,您会发现select() 将返回“ready for reading”...“ready for reading”的规则是,如果read() 不会阻塞,则它是真的。 read() 如果返回 0 则不会阻塞。

【讨论】:

  • 这有帮助。我通过检查读取的返回值解决了这个问题,如果它为 0,我关闭套接字并将其从传递给 select 调用的 readfs 列表中删除。
【解决方案2】:

根据select linux 手册页,存在与此行为相关的错误:

在 Linux 下,select() 可能会将套接字文件描述符报告为“准备读取”,但随后的读取会阻塞。例如,当数据到达但检查时校验和错误并被丢弃时,可能会发生这种情况。可能存在文件描述符被虚假报告为就绪的其他情况。

另一方面,我建议您考虑处理活动并将数据处理到循环中的策略(删除不相关的部分):

for (loop = 0; loop<4; loop++)
{
    // ...
    activity = select( maxfds + 1 , &readfds , NULL , NULL , NULL);
    // ...
}    

这将阻塞第一个套接字,而第二个、第三个和第四个可能已准备好。至少使用超时并检查errno 处理超时事件。有关详细信息,请参阅选择手册页。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-10
    • 2012-06-13
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    • 2011-08-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多