2

次のように動作する必要があるCでTCPクライアントを実装しようとしています:

  • 特定のサーバーへの接続を開く機能
  • 確立された接続を介してサーバーに任意のデータを送信する機能、および
  • サーバーから任意のデータを受信する機能 (クライアントがサーバーに送信した「質問」への応答と考えてください)。

たとえば、クライアントは任意の HTTP サーバーへの接続を開き、「HEAD」メッセージを送信し、HTTP サーバーから到着した応答を出力できる必要があります。

(私の目標は、日常業務に使用していて、ネットワーク機能が不足している特定のソフトウェア環境用の汎用の「TCP クライアント プラグイン」を作成することです。自分の環境の SDK についてはよく知っていますが、実際には持っていません。ソケットプログラミングの深い経験。)

現在、データの送受信用に 2 つのスレッドがあります。レシーバー スレッド (サーバー アドレスとポートがユーザーによって設定されるとすぐに自動的に開始される) のワークフローは次のとおりです (ここでは、ソケット呼び出しの主なシーケンスについてのみ説明します)。

globalSocket = socket(); // Create socket and store it globally
bind(); // Bind the local port
connect(); // Connect to remote host & port
listen(); // Listen to the socket
while (isAlive) {
  select(... &readfds ...); // Check for ready reader descriptors
  accept(); // Accept the incoming connection
  recv(); // Receive data from server
}
close(); // End the connection

送信側スレッドは非常に単純でglobalSocket、受信側スレッドによって作成されたを使用してsend()コマンドを実行します。

ここで問題があります。リモート サーバーへの接続を問題なく開くことができます。任意のデータも問題なく送信できます(実際に送信したデータが問題なくサーバー側に届くことを確認しました)。ただし、サーバーからデータを取得できません。いくつかのテストの後select、正の値を返すことはないようです。

私は自分のコードで多くの変更を試みました (パラメーターを に変更するselect、省略するlistenなど)。Beej のガイドをこの日のうちに少なくとも 10 回読み、想像できるすべてのアドホックな変更を試みましたが、動作はまだです。同じ。したがって、特定のコードの抜粋で特定の質問をする前に、この問題に対する私のアプローチが正しかったのか、それとも深刻な概念上の問題を抱えているのかを知りたいと思います。

回答ありがとうございます。

アダム

PS このコード部分は長すぎるため、コメントに投稿できませんでした。select--サイクルを管理するコード スニペットは次acceptのとおりです。recv

while ( thread->isActive ) {
   // Accept connection
   timeVal.tv_sec = TIMEOUT_SEC;
   timeVal.tv_usec = TIMEOUT_USEC;
   FD_ZERO( & fileDescriptor );
   FD_SET( socketDescriptor, & fileDescriptor ); // socketDescriptor is the global socket
   result = select ( FD_SETSIZE, & fileDescriptor, NULL, NULL, & timeVal );
   if ( result > 0 ) {
      post ( "select" ); // This line is actually never reached
      connectionDescriptor = accept ( socketDescriptor, ( struct sockaddr * ) & clientAddress, & clientLength ); // connectionDescriptor is the local socket created by accept
      if ( connectionDescriptor < 0 ) { // Some error happened
         outlet_int ( thread->parent->statusOutlet, errno );
      } else {
         // Receive data
         data.clear ( ); // 'data' is an std::vector of chars that stores the incoming data and makes it accessible for the rest of the environment
         size = thread->parent->bufferSize;
         buffer = new unsigned char [ size ]; // this buffer is used for receiving the data from 'recv'
         receivedBytes = 1;
         while ( receivedBytes > 0 ) {
            receivedBytes = recv ( connectionDescriptor, ( char * ) buffer, size, 0 );
            if ( receivedBytes < 0 ) { // Socket error
               outlet_int ( thread->parent->statusOutlet, errno );
            }
            data.insert ( data.end ( ), buffer, buffer + receivedBytes );
         }
         delete [ ] buffer;
#ifdef WIN_VERSION
         closesocket ( connectionDescriptor );
#else
         close ( connectionDescriptor );
#endif
         // Output received data
         ... blah ... blah ... blah
      }
   }
}
4

3 に答える 3

2

connect(2)他の人は、との非互換性についてすでにコメントしていlisten(2)ます。そこも必要ありませんbind(2)

使用に関する最も一般的な間違いselect(2)は、反復ごとにファイル記述子セットを再初期化しないことです。select(2)の 2 番目から 4 番目の引数はinput-outputであるため、毎回やり直す必要があります。

編集 0:

コードを投稿した後、I/O デマルチプレックスの恩恵をまったく受けていないことを付け加えさせてselect(2)ください。またselect(2)、新しい接続試行によってウェイクアップされてから、ブロッキングに入る前にクライアントがその接続をドロップするまでの間に、よく知られた競合がありますaccept(2)

ノンブロッキング ルート ( 、 、 、 などを使用する推奨方法) を使いselect(2)たくpoll(2)ないepoll(7)場合kqueue(2)は、 を削除して、select(2)クライアント接続をループで受け入れて処理することもできます。

于 2012-07-26T01:21:08.643 に答える
2

Firstly, are you physically receiving the packets from the remote server? I ask because I ran into a similar problem during an assignment and when I ran tcpdump, it did not show any received packets either. The issue turned out to be a firewall which obviously allows outgoing traffic but blocks all incoming packets...

于 2012-07-26T01:38:18.530 に答える
0

したがって、ここにはいくつかの独立した問題があり、それらはいくつかの回答とコメントに分かれているため、それらを要約します。

送信スレッドは OK です。ただし、コネクタ/リスナー スレッドは次のようになります。

socket();
connect();
while (isAlive) {
   if (select(... &readfds ...) > 0) {
      recv();
   }
}
close();

最初に投稿されたコードの抜粋に関して、while処理に使用されていたサイクルrecvは概念的なエラーであり、スレッドをブロックするため、削除する必要があります。これにより、selectステートメントが不要になります (その場合、select実際には効果がないため)。

また、ifrecvが 0 を返す場合は、ソケットがリモート ピアによって閉じられたことを意味することにも注意してください。その場合、親スレッドによって false に設定されていなくwhile (isAlive)ても、ループを中止する必要があります。isAliveただし、ループ内に余分なclose ステートメントは必要ありません (これも元のコードの概念的なエラーでした)。

于 2012-07-26T09:13:27.053 に答える