1

Cアプリケーションがデータの送信と受信の両方を同時に行う必要がある UDP アプリケーションを使用しようとしています。チャット アプリケーションに似ています。1 人がデータを入力しているとき、その人はそれを受け取ることができるはずです。

これをどのように実装するかを計画しています。私の考えが正しいかどうか助けてください。

  1. 同じプログラムがクライアントとしてもサーバーとしても機能します。
  2. 両方のサーバーが異なるポートでリッスンします
  3. 送信用のスレッドと受信用のスレッドを作成します。(pスレッド)
  4. このステップで問題に直面しています。recvfrom をノンブロッキングにしたい。だから私は E_WOULDBLOK を設定できると思いました.errno が EAGAIN に設定されている場合、受信スレッドを譲ることができます。しかし、そのソケットが読み取り可能な場合はどうなりますか。受信スレッドがデータを読み取れるようにするには、送信スレッドをどのように生成する必要がありますか。また、受信ソケットが読み取り可能であるという理由だけでユーザーが入力しているときに送信スレッドを譲っても、ユーザーには影響しませんか?

または、これを達成する方法を提案してください。pthread 条件変数はこのシナリオに適していますか? 他のアイデアも大歓迎です。

4

1 に答える 1

5

送信スレッドと受信スレッドを使用するアプローチは機能するはずですが、後でスレッドで非ブロッキング IO を使用したいと述べています。これは問題ありませんが、ノンブロッキング IOスレッドの両方を同時に使用する理由が少しわかりません。

スレッドを使用している場合は、ソケットを非ブロッキングとしてマークする必要はありません。標準のブロッキングrecvfrom()read()呼び出しを使用するだけです。read()送信スレッドから stdin に入力を取得すると、メッセージをsendto()送信し、受信スレッドでメッセージを受信すると、通常どおり stdout に表示できます。

スレッド間で情報をやり取りする場合を除いて、pthreads 条件変数は必要ありません。2 者間チャット (つまり、複数のユーザー) 以上が必要な場合は、これが必要になることがあります。

ノンブロッキング IO を使用している場合 (これがよりクリーンなソリューションだと思います - スレッドは可能な限り避けるのが最善です)、通常はスレッドを使用する必要はありません -select()やなどの関数を使用poll()してファイル記述子を管理できます。poll()より便利なインターフェースがあると思うのでお勧めしますが、どちらでも機能します。Linuxなどには、より効率的な同等のものもある可能性がありepoll()ますが、通常、アプリケーションが大量のトラフィックを処理する場合を除き、これらは無視できます。

通常、ループを持つ単一のスレッドがあります。poll()or andを呼び出すループの開始時にselect()、ファイル記述子の 1 つ (ソケットである可能性があります) が読み取りまたは書き込みのために読み取られるまでブロックされます。複数のファイル記述子をリッスンできますが、あなたの場合、単一のファイル記述子でも問題なく動作します。

poll()関数が戻ると、またはで「読み取り可能」と示されているソケットから情報を読み取り、必要に応じてselect()情報を送信できます。これが接続指向のソケットである場合は、出力をバッファリングし、ソケットが「書き込み可能」であるかどうかを監視し、必要に応じて出力バッファからデータをフラッシュする必要があります。ただし、UDP の場合、「書き込み可能」という意味のある概念は実際には存在しないため、データが利用可能になったときにいつでもデータを送信できます。

実際、poll()このようなものを使用している場合、ソケットを非ブロックとしてマークする必要さえありません。非常に単純な UDP チャット クライアントを実装する以下のサンプル コードを参照してください。これを使用してコマンドラインで宛先IPアドレスとポートを手動で指定する必要があり、一部のエラーチェックは非常に単純です。これは単なるサンプルコードであるためですが、独自の目的に適応するのに十分なものになるはずです.

Unix マシンでこれをテストするには、「chat」と呼ばれるバイナリにコンパイルし、2 つのターミナル ウィンドウを開いて 1 つを実行します。

chat 8888 127.0.0.1 9999

...そして、実行中の他のもので:

chat 9999 127.0.0.1 8888

最初のポートはリスニング ポートであり、残りの 2 つの引数は、リモート ピアの接続先の 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 に答える