2013-02-21 117 views
1

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

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

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

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

回答

5

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

如果使用线程,则不需要将套接字标记为非阻塞 - 只需使用标准阻塞recvfrom()read()调用即可。当你在发送线程中从read()得到stdin的输入时,那么你发送了一个带有sendto()的消息,当你在接收线程中收到一条消息时,你可以像往常一样将它显示到标准输出。

除非您在线程之间传递信息,否则不应该需要pthreads条件变量 - 如果您不仅需要双向聊天(即多个用户),则可能需要此选项。

如果您使用的是非阻塞IO(我认为这是更干净的解决方案 - 尽可能避免使用线程),您通常不需要使用线程 - 您可以使用诸如select()poll()之类的函数来管理你的文件描述符。我建议poll(),因为我认为它有一个更方便的界面,但要么会工作。在Linux上也可能有更高效的对等项,例如epoll(),但通常情况下,您可以忽略这些,除非您的应用程序将处理大量流量。

您通常会有一个带有循环的单个线程。在循环开始时,您可以调用poll()select(),这会阻塞,直到读取或写入文件描述符(可以是套接字)之一为止。你可以听多个文件描述符,但在你的情况下,它也可以很好地工作。

函数返回后,如果通过poll()select()指示为“已准备就绪”,则可以从套接字读取信息,然后根据需要发送信息。如果这是一个面向连接的套接字,您希望缓冲输出并监视套接字是否“写入就绪”,并根据需要从输出缓冲区中清空数据。然而,对于UDP来说,并不是真正意义上的“准备就绪”概念,所以只要有数据,就可以发送数据。

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

您可以通过编译到一个名为“聊天”二进制文件,然后打开两个终端窗口,并在一个运行测试这是UNIX系统:

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; 
} 
+0

+ 1.I花了近3个月建设的epoll服务器(以及在上面的HTTP服务器)。这是不平凡的。 – 2013-02-22 15:25:12

+0

事情当然与TCP有一点关系。我认为很多人犯的主要错误是试图偷工减料(这个例子可能是有罪的,但这只是一个例子)。只要你将你的投票层分成一个单独的模块并且有一个明确的API围绕它,它肯定是易于处理的。有很多问题需要追查,但是,像处理连接关闭是奇怪的,而不会进入严密的poll()循环。在那里,做到了,我感到你的痛苦。如果我必须再做一次,我很想用[libev](http://libev.schmorp.de)。 – Cartroo 2013-02-22 15:38:57

+0

@Cartoo,非常感谢您的建议。由于没有人回复,我深深地看到了这一点。你花了这么多的痛苦来解释。万分感谢 – CHID 2013-04-02 10:52:32