TCPサーバへの接続失敗?

18 Apr 2013

#Questionsに投降したのですが、こちらのほうが日本語での回答が得られそうということでしたので...

初めまして。 mbed NXP LPC1768 上でTCPサーバを動かしているのですが、 クライアントから接続/切断を繰り返すと5回目の接続時にacceptがtimeoutしてしまいます。

色々とググっては見たのですが特に有用な情報はないようでした。

下記のコードで再現すると思うのですが、何かまずい部分でもあるのでしょうか... ちなみに、EthernetInterfaceとrtosはいずれもmbed-officialのものを使用しています。

#include "mbed.h"
#include "EthernetInterface.h"
#include "TCPSocketServer.h"
#include "TCPSocketConnection.h"

DigitalOut myled(LED1);

EthernetInterface eth;
TCPSocketServer server;
char receiveBuf[1024];

#define PORT 1234

int main() {
    eth.init();
    eth.connect();
    printf("IP Address is %s\r\n", eth.getIPAddress());

    while(1)
    {
        printf("Binding(%d)...", PORT);
        if (server.bind(PORT) < 0) {
            printf("Could not bind server socket to port '%d'.\n\r", PORT);
            return -1;
        }
        printf("OK\r\n");
        
        printf("Listening...");
        if (server.listen() < 0) {
            printf("Could not put server socket into listening mode.\n\r");
            return -1;
        }
        printf("OK\r\n");
    
        // enable timeout.
        server.set_blocking(false);
        
        while(1)
        {
            TCPSocketConnection connection;
            bool isConnected = false;
            printf("Waiting Connect...");
            if( server.accept(connection) == 0 )
            {
                printf("Connected.\r\n");
                isConnected = true;
                connection.set_blocking(false, 10000);
                while( isConnected )
                {
                    int result = connection.receive(receiveBuf, sizeof(receiveBuf));
                    switch(result)
                    {
                    case 0:
                    case -1:
                        isConnected = false;
                        break;
                    default:
                        printf("receive %dbytes.\r\n", result);
                        break;
                    }
                }
                connection.close();
                printf("Disconnected.\r\n");
                
                // break;
            }
            else
            {
                printf("Timeout.\r\n");
            }
        }
        
        server.close();
    }    
}

36行目のset_blockingコメントアウトしてacceptでタイムアウトしないようにすると何度でも接続/切断できます。

少しTCPSocketServerのソースを見たのですが、set_blockingでタイムアウトするように設定すると、 selectで待ち受けを行うようです。これ以上は深く潜ってないのですが、何か情報ご存知の方いらっしゃいますでしょうか。

また、コード上まずそうな部分があれば、ご指摘いただけると幸いです。

23 Apr 2013

その後いろいろ調べてみたところ、acceptされない原因が分かりました。

結論から言うと、メールボックスのキューサイズの指定に問題があります。

TCPクライアントからの接続があった場合、lwip/api/api_msg.c のaccept_function()が呼ばれます。 ここで次のようなコードがあるのですが、

  if (sys_mbox_trypost(&conn->acceptmbox, newconn) != ERR_OK) {

このコードの前後で conn->socket の値が newcon の値で壊されます。

conn->acceptmbox は sys_mbox_t 構造体で次のように定義されています。

// === MAIL BOX ===
#define MB_SIZE      8

typedef struct {
    osMessageQId    id;
    osMessageQDef_t def;
#ifdef CMSIS_OS_RTX
    uint32_t        queue[MB_SIZE];
#endif
} sys_mbox_t;

sys_mbox_trypost()を通るとqueueにnewconの値が入るのですが、queue先頭の4要素は予約されているようで、 queue[4]から順に使われていきます。 MB_SIZEは8なので、queue[7]の次はqueue[4]に戻って欲しいところです。 しかし、実際にはqueue[8]に書き込まれ、メモリ上acceptmboxのすぐ後ろにsocketが配置されているため、 ここでsocketが上書きされることになります。

試しにMB_SIZEを16にしてみたところ、6個(つまりqueue[9])まで書き込むと、次にqueue[4]に書き込むことが分かりました。

acceptmboxを作る際のコードが次のようになっていて、

if (!sys_mbox_valid(&msg->conn->acceptmbox)) {
    msg->err = sys_mbox_new(&msg->conn->acceptmbox, DEFAULT_ACCEPTMBOX_SIZE);
}

DEFAULT_ACCEPTMBOX_SIZEが6で定義されており、この値でqueueをローテートして使用する作りになっているためだと思います。 (実際にそうなっているかまでは追いかけませんでした)

従って、

  • MB_SIZEを10以上に設定する
  • DEFAULT_ACCEPTMBOX_SIZEを4に設定する

のいずれかの対応でよさそうですが、

#define TCPIP_MBOX_SIZE             6
#define DEFAULT_TCP_RECVMBOX_SIZE   6
#define DEFAULT_UDP_RECVMBOX_SIZE   6
#define DEFAULT_RAW_RECVMBOX_SIZE   6
#define DEFAULT_ACCEPTMBOX_SIZE     6

のようにaccept以外のメールボックスサイズの定義もあるので、私の方ではMB_SIZEを10以上に設定して対応することにしました。

以上、問題ありそうならご指摘いただければ嬉しいです。