1

我正在尝试一个 UDP 应用程序,C其中应用程序必须同时发送和接收数据。类似于我们的聊天应用程序。当 1 个人输入数据时,他应该能够接收到它。

我正在计划如何实现这一点。如果我的想法是正确的,请帮助我。

  1. 同一程序将充当客户端和服务器。
  2. 两台服务器都将监听不同的端口
  3. 创建一个发送线程和一个接收线程。( pthread )
  4. 我在这一步遇到了问题。我希望 recvfrom 是非阻塞的。所以我想我可以设置 E_WOULDBLOK 并且当 errno 设置为 EAGAIN 时,我可以产生接收线程。但是当该套接字可读时会发生什么。我应该如何让发送线程让接收线程读取数据。如果我在用户键入时仅仅因为我的接收套接字是可读的而产生发送线程,它也不会影响用户吗?

否则请建议我实现这一目标的方法。pthread 条件变量是否适合这种情况。也欢迎其他想法。

4

1 回答 1

5

使用传输和接收线程的方法应该可以工作,但是您稍后提到您想在线程上使用非阻塞 IO。这很好,但我有点疑惑为什么要同时使用非阻塞IO线程。

如果您使用线程,则无需将套接字标记为非阻塞 - 只需使用标准阻塞recvfrom()read()调用即可。当您从read()发送线程中的标准输入获得输入时,您会发送一条消息,sendto()并且当您在接收线程中收到一条消息时,您可以正常将其显示到标准输出。

除非您在线程之间传递信息,否则您不应该需要 pthreads 条件变量 - 如果您想要多于 2 路聊天(即多个用户),那么您可能需要它。

如果您使用的是非阻塞 IO(我认为这是更简洁的解决方案 - 最好尽可能避免使用线程),您通常不需要使用线程 - 您可以使用select()和之类的函数poll()来管理文件描述符。我会推荐poll(),因为我认为它有一个更方便的界面,但两者都可以。也有潜在的更有效的等价物,例如epoll()在 Linux 上,但通常您可以忽略这些,除非您的应用程序将处理大量流量。

您通常有一个带循环的线程。在循环开始时,您调用poll()orselect()并且它将阻塞,直到读取文件描述符之一(可以是套接字)以进行读取或写入。您可以侦听多个文件描述符,但在您的情况下,它也可以与单个文件描述符一起正常工作。

poll()一旦函数返回,您可以从套接字中读取信息,如果它被or指示为“read-ready” select(),然后根据需要发送信息。如果这是一个面向连接的套接字,您需要缓冲输出并监视套接字是否“可写”,并根据需要从输出缓冲区刷新数据。然而,对于 UDP,“写就绪”的概念并没有真正有意义的概念,因此只要有可用数据,您就可以发送数据。

事实上,如果您正在使用poll()或类似这样,您甚至不需要将套接字标记为非阻塞。请参阅下面的示例代码,它实现了一个非常简单的 UDP 聊天客户端。您必须在命令行上手动指定目标 IP 地址和端口,并且一些错误检查非常简单,因为它只是示例代码,但它应该足以适应您自己的目的。

您可以在 Unix 机器上通过编译为名为“chat”的二进制文件然后打开两个终端窗口并在一个运行中进行测试:

chat 8888 127.0.0.1 9999

...在另一个运行中:

chat 9999 127.0.0.1 8888

请注意,第一个端口是侦听端口,其余两个参数指定要连接到远程对等方的 IP 地址和端口。

代码在这里:

#include <sys/types.h>
#include <sys/socket.h>

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


void start_chat(int sock_fd, struct sockaddr_in *peer)
{
  int ret;
  ssize_t bytes;
  char input_buffer[1024];
  char output_buffer[1024];
  struct pollfd fds[2];

  /* Descriptor zero is stdin */
  fds[0].fd = 0;
  fds[1].fd = sock_fd;
  fds[0].events = POLLIN | POLLPRI;
  fds[1].events = POLLIN | POLLPRI;

  /* Normally we'd check an exit condition, but for this example
   * we loop endlessly.
   */
  while (1) {
    /* Call poll() */
    ret = poll(fds, 2, -1);

    if (ret < 0) {
      printf("Error - poll returned error: %s\n", strerror(errno));
      break;
    }
    if (ret > 0) {

      /* Regardless of requested events, poll() can always return these */
      if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
        printf("Error - poll indicated stdin error\n");
        break;
      }
      if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) {
        printf("Error - poll indicated socket error\n");
        break;
      }

      /* Check if data to read from stdin */
      if (fds[0].revents & (POLLIN | POLLPRI)) {
        bytes = read(0, output_buffer, sizeof(output_buffer));
        if (bytes < 0) {
          printf("Error - stdin error: %s\n", strerror(errno));
          break;
        }
        printf("Sending: %.*s\n", (int)bytes, output_buffer);
        bytes = sendto(sock_fd, output_buffer, bytes, 0,
                       (struct sockaddr *)peer, sizeof(struct sockaddr_in));
        if (bytes < 0) {
          printf("Error - sendto error: %s\n", strerror(errno));
          break;
        }
      }

      /* Check if data to read from socket */
      if (fds[1].revents & (POLLIN | POLLPRI)) {
        bytes = recvfrom(sock_fd, input_buffer, sizeof(input_buffer),
                         0, NULL, NULL);
        if (bytes < 0) {
          printf("Error - recvfrom error: %s\n", strerror(errno));
          break;
        }
        if (bytes > 0) {
          printf("Received: %.*s\n", (int)bytes, input_buffer);
        }
      }
    }
  }
}


int main(int argc, char *argv[])
{
  unsigned long local_port;
  unsigned long remote_port;
  int sock_fd;
  struct sockaddr_in server_addr;
  struct sockaddr_in peer_addr;

  /* Parse command line arguments for port numbers */
  if (argc < 4) {
    printf("Usage: %s <local port> <remote host> <remote port>\n", argv[0]);
    return 1;
  }
  local_port = strtoul(argv[1], NULL, 0);
  if (local_port < 1 || local_port > 65535) {
    printf("Error - invalid local port '%s'\n", argv[1]);
    return 1;
  }
  remote_port = strtoul(argv[3], NULL, 0);
  if (remote_port < 1 || remote_port > 65535) {
    printf("Error - invalid remote port '%s'\n", argv[3]);
    return 1;
  }

  /* Parse command line argument for remote host address */
  peer_addr.sin_family = AF_INET;
  peer_addr.sin_port = htons(remote_port);
  if (inet_aton(argv[2], &peer_addr.sin_addr) == 0) {
    printf("Error - invalid remote address '%s'\n", argv[2]);
    return 1;
  }

  /* Create UDP socket */
  sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
  if (sock_fd < 0) {
    printf("Error - failed to open socket: %s\n", strerror(errno));
    return 1;
  }

  /* Bind socket */
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  server_addr.sin_port = htons(local_port);
  if (bind(sock_fd, (struct sockaddr *)(&server_addr),
           sizeof(server_addr)) < 0) {
    printf("Error - failed to bind socket: %s\n", strerror(errno));
    return 1;
  }

  /* Call chat handler to loop */
  start_chat(sock_fd, &peer_addr);

  close(sock_fd);

  return 0;
}
于 2013-02-22T15:13:11.257 回答