メインコンテンツへスキップ
検索

タイトルとは裏腹に、数学オタクモードには戻っていないのでご安心を。話題は数論ではなく、ソケットプログラミングと、私がよく見かけるコーディングミスです。

図1のコードの断片は、その間違いを示しています。メインのwhileループがあり、selectを呼び出して文字を受信できるようになるのを待つというループが永遠に繰り返されています。selectが文字が利用可能であることを示すと、コードは10文字を受信するまで別のループを繰り返します。recv 関数が呼び出された後、コードはリモートピアがソケットを閉じたことを示す 0 が返ってくるかどうかを正しくチェックします。errno が 0 であるかどうかをチェックし、0 であれば、受信した文字をアプリケーションバッファに連結し、受信した文字数を現在のメッセージの受信文字数に加算します。最後に、 errno に EWOULDBLOCK という値があるかどうかをチェックし、それ以外の値であればエラーでプログラムを終了します。この時点で内部ループは終了し、メッセージ内の文字数が 10 未満の場合は再び recv を呼び出します。

 while (1)
   {
   FD_ZERO (&fdsetREAD);
   FD_ZERO (&fdsetNULL);
   FD_SET (sockAccepted, &fdsetREAD);
   iNumFDS = sockAccepted + 1;

/* wait for the start of a message to arrive */
   iSelected = select (iNumFDS,
                      &fdsetREAD, &fdsetNULL, &fdsetNULL, &timevalTimeout);
   if (iSelected < 0) /* Error from select, report and abort */
      {
      perror ("minus1: error from select");
      exit (errno);
      }
/* select indicates something to be read. Since there is only 1 socket
   there is no need to figure out which socket is ready. Note that if
   select returns 0 it just means that it timed out, we will just go around
   the loop again.*/
   else if (iSelected > 0)
        {
        szAppBuffer [0] = 0x00;       /* "zero out" the application buffer */
        iTotalCharsRecv = 0;          /* zero out the total characters count */
        while (iTotalCharsRecv < 10)  /* loop until all 10 characters read */
           {                          /* now read from socket */
           iNumCharsRecv = recv (sockAccepted, szRecvBuffer,
                                   10 - iTotalCharsRecv, 0);
           if (iDebugFlag)            /* debug output show */
              {                       /* value returned from recv and errno */
              printf ("%d  %d     ", iNumCharsRecv, errno);
              if (iNumCharsRecv > 0)  /* also received characters if any */
                 {
                 szRecvBuffer [iNumCharsRecv] = 0x00;
                 printf ("[%s]n", szRecvBuffer);
                 }
              else printf ("n");
              }
           if (iNumCharsRecv == 0)   /* If 0 characters received exit app */
              {
              printf ("minus1: socket closedn");
              exit (0);
              }
           else if (errno == 0)      /* if "no error" accumulate received */
              {                      /* chars into an applictaion buffer */
              szRecvBuffer [iNumCharsRecv] = 0x00;
              strcat (szAppBuffer, szRecvBuffer);
              iTotalCharsRecv = iTotalCharsRecv + iNumCharsRecv;
              szRecvBuffer [0] = 0x00;
              }
           else if (errno != EWOULDBLOCK) /* Ignore an EWOULDBLOCK error */
              {                           /* anything else report and abort */
              perror ("minus1: Error from recv");
              exit (errno);
              }
           if (iDebugFlag) sleep (1); /* this prevents the output from */
           }                          /* scrolling off the window */

        sprintf (szOut, "Message [%s] processedn", szAppBuffer);
        if (iDebugFlag) printf ("%sn", szOut);
        if (send (sockAccepted, szOut , strlen (szOut), 0) < 0)
           perror ("minus1: error from send");
        }
   }
図 1 - 不正なコードの断片

図2にセッションの例を示す。サーバに送信された文字は 黄色いの場合、返された処理済みメッセージは強調表示されません。送信される文字には改行終了文字が含まれており、1 つの TCP セグメントで送信されます。きっかり 10 文字が送信されると、すべてが動作します。しかし、TCP セグメント内で 6 文字だけが送信されると、サーバは応答しなくなります。

123456789
メッセージ [123456789
処理済み
アベノミクス
メッセージ [abcdefghi
処理済み
一二三四五ABCD
メッセージ [12345abcd
処理済み
12345
789
アベノミクス
123456789
図 2 - クライアントセッション

Figure 3 shows the server session with debug turned on. You can see that after the “12345<new line>” characters are received the next recv returns -1 and sets the errno to 5011, which is EWOULDBLOCK. The code then loops and the next recv returns the characters “789<new line>” but the errno value is still set to 5011. In fact every recv after that regardless of whether there are characters received or not has errno set to 5011.

受け入れられた接続
10  0     [123456789
]
メッセージ [123456789
処理済み

10 0 [abcdefghi
]
メッセージ [abcdefghi
処理済み

10 0 [12345abcd
]
メッセージ [12345abcd
処理済み

6  0     [12345
]
-1  5011
4  5011     [789
]
-1  5011 
4 5011 [abcd]
4 5011 [EFGH]
4  5011     [i
12]
4  5011     [3456]
4  5011     [789
]
-1  5011
-1  5011
-1  5011
図 3 - サーバのデバッグ出力

errnoの値が0ではないので、受信した文字はアプリケーションバッファに連結されず、コードは永遠にループします。

これはソケットコードのバグではありません。ソケットAPIでは、関数が-1を返さない限り、 errnoの値は未定義であることが明示されています。 未定義は値が設定されていないことを意味し、 errnoはそれまでの値を保持します。

10 文字のメッセージを 2 つに分割する人はいないだろうと思うかもしれませんし、その通りかもしれません。また、TCP はメッセージではなくバイトのストリームであることを覚えておいてください。TCP スタックはいつでもアプリケーションメッセージを複数の TCP セグメントに分割することができます。TCP スタックはいつでもアプリケーション・メッセージを複数の TCP セグメントに分割することができますが、これを可能にする条件があります。適切な条件の下では、このサーバコードはすべての受入テストに合格し、少なくともしばらくの間は本番環境で正常に動作する可能性があります。

良いニュースは、非常に簡単な修正があるということです。 errno == 0 をテストする代わりに、戻り値が 0 より大きいかどうかをテストするだけです。また、"errno != EWOULDBLOCK" テストのコメントでは、 recv が負の値を返した場合にのみ if 文に到達する方法があることが指摘されていることにも注意してください。このテストが返す負の値は -1 だけです。

 while (1)
   {
   FD_ZERO (&fdsetREAD);
   FD_ZERO (&fdsetNULL);
   FD_SET (sockAccepted, &fdsetREAD);
   iNumFDS = sockAccepted + 1;

/* wait for the start of a message to arrive */
   iSelected = select (iNumFDS,
                      &fdsetREAD, &fdsetNULL, &fdsetNULL, &timevalTimeout);
   if (iSelected < 0) /* Error from select, report and abort */
      {
      perror ("minus1: error from select");
      exit (errno);
      }
/* select indicates something to be read. Since there is only 1 socket
   there is no need to figure out which socket is ready. Note that if
   select returns 0 it just means that it timed out, we will just go around
   the loop again.*/
   else if (iSelected > 0)
        {
        szAppBuffer [0] = 0x00;       /* "zero out" the application buffer */
        iTotalCharsRecv = 0;          /* zero out the total characters count */
        while (iTotalCharsRecv < 10)  /* loop until all 10 characters read */
           {                          /* now read from socket */
           iNumCharsRecv = recv (sockAccepted, szRecvBuffer,
                                   10 - iTotalCharsRecv, 0);
           if (iDebugFlag)            /* debug output show */
              {                       /* value returned from recv and errno */
              printf ("%d  %d     ", iNumCharsRecv, errno);
              if (iNumCharsRecv > 0)  /* also received characters if any */
                 {
                 szRecvBuffer [iNumCharsRecv] = 0x00;
                 printf ("[%s]n", szRecvBuffer);
                 }
              else printf ("n");
              }
           if (iNumCharsRecv == 0)   /* If 0 characters received exit app */
              {
              printf ("minus1: socket closedn");
              exit (0);
              }
           else if (iNumCharsRecv > 0) /* if no error accumulate received */
              {                        /* chars into an applictaion buffer */
              szRecvBuffer [iNumCharsRecv] = 0x00;
              strcat (szAppBuffer, szRecvBuffer);
              iTotalCharsRecv = iTotalCharsRecv + iNumCharsRecv;
              szRecvBuffer [0] = 0x00;
              }
           else if (errno != EWOULDBLOCK) /* if we get here iNumCharsRecv */
              {                           /* must be -1 so errno is defined */
              perror                      /* Ignore an EWOULDBLOCK error */
               ("minus1: Error from recv"); /* anything else report */
              exit (errno);               /* and abort */
              }
           if (iDebugFlag) sleep (1); /* this prevents the output from */
           }                          /* scrolling off the window */

        sprintf (szOut, "Message [%s] processedn", szAppBuffer);
        if (iDebugFlag) printf ("%sn", szOut);
        if (send (sockAccepted, szOut , strlen (szOut), 0) < 0)
           perror ("minus1: error from send");
        }
   }
図4 - 修正されたコードフラグメント

 

メニューを閉じる

© 2024 Stratus Technologies.