3

CのウィンドウでTCP通信をシミュレートしています。送信者と受信者が通信しています。

送信者は特定のサイズのパケットを受信者に送信します。受信者はそれらを取得し、受信した各パケットのACKを送信者に送り返します。送信者が特定のパケットを取得しなかった場合(パケット内のヘッダーで番号が付けられている場合)、送信者はパケットを受信者に再度送信します。受信側のgetPacket関数は次のとおりです。

//get the next packet from the socket. set the packetSize to -1
//if it's the first packet.
//return: total bytes read
// return: 0 if socket has shutdown on sender side, -1 error, else number of bytes received
int getPakcet(char* chunkBuff, int packetSize, SOCKET AcceptSocket)
{
    int totalChunkLen = 0;
    int bytesRecv = -1;
    bool firstTime = false;

    if(packetSize == -1)
    {
        packetSize = MAX_PACKET_LENGTH;
        firstTime = true;
    }

    int needToGet = packetSize;

    do
    {
        char* recvBuff;
        recvBuff = (char*)calloc(needToGet, sizeof(char));

        if(recvBuff == NULL)
        {
            fprintf(stderr, "Memory allocation problem\n");
            return -1;
        }

        bytesRecv = recv(AcceptSocket, recvBuff, needToGet, 0);

        if(bytesRecv == SOCKET_ERROR)
        {
            fprintf(stderr, "recv() error %ld.\n", WSAGetLastError());
            totalChunkLen = -1;
            return -1;
        }

        if(bytesRecv == 0)
        {
            fprintf(stderr, "recv(): socket has shutdown on sender side");
            return 0;
        }
        else if(bytesRecv > 0)
        {
            memcpy(chunkBuff + totalChunkLen, recvBuff, bytesRecv);
            totalChunkLen += bytesRecv;
        }

        needToGet -= bytesRecv;
    }
    while((totalChunkLen < packetSize) && (!firstTime));

    return totalChunkLen;
}

firstTime送信者が送信する通常のパッケージサイズを受信者が初めて知らないために使用するので、を使用してMAX_PACKET_LENGTHパッケージを取得し、通常のパッケージサイズを受信したバイト数に設定します。 。

私の問題は最後のパッケージです。サイズはパッケージサイズより小さくなっています。つまり、最後のパッケージサイズが2で、通常のパッケージサイズが4であるとします。つまりrecv()、2バイトを取得し、while条件を続行します。これにより、ループが再度繰り返され、送信者が送信するものがないためにブロックtotalChunkLen < packetSize されているため、がスタックします。 。2<4recv()

送信側では、ACKが返されなかったために接続を閉じることができないため、一種のデッドロックになります。受信者はさらにパッケージを待機しているためスタックしていますが、送信者は送信するものがありません。

recv()タイムアウトを使用したり、パッケージヘッダーに特殊文字を挿入して、それが最後の文字であることをマークしたりしたくありません。

私に何ができる?

4

4 に答える 4

2

TCPを使用して受信機と送信機の間で通信しており、TCPはストリーム指向のプロトコルです。つまり、一方の端にバイトストリームを配置し、もう一方の端にストリームを順番に、損失なしで取得します。さまざまな理由でデータが分割される可能性があるため、各send()がもう一方の端のrecv()と一致するという保証はありません。

したがって、TCP接続で次のことを行う場合:

char buffer[] = "1234567890";
send(socket, buffer, 10, 0);

そして、受信機で:

char buffer[10];
int bytes = recv(socket, buffer, 10, 0);

recv()が返されるとき、バイトは0から10の間のどこでもかまいません。

TCPは、データグラム指向のプロトコルであるIP上で実行されます。これが、TCP実装が、データグラムを送信するときに、もう一方の端でデータグラム全体を受信する(または受信しない、または順序が狂っている)と想定できる理由です。シミュレートする場合は、少なくとも2つのオプションがあります。

  1. TCPメッセージにフレーミングを追加して、そこからパケットを抽出できるようにします。これには、ストリームに送信するヘッダーにパケットのサイズなどを追加することが含まれます。すべてのパケットが常に順番に到着し、基盤となるTCPフロー制御/輻輳回避メカニズムをすでに使用しているため、これをTCPのシミュレーションに使用することは意味がありません。
  2. UDPなどのデータグラムプロトコルを使用します。これは、TCPが実行されるIP層に近くなります。

おそらくオプション2を使用する必要がありますが、TCPを介してフレーミングルートを使用する場合は、次のようにすることができます(大まかなクイックコードが続きます)。

// We do this to communicate with machines having different byte ordering
u_long packet_size = htonl(10); // 10 bytes packet
send(socket, &packet_size, 4, 0); // First send the frame size
send(socket, buffer, 10, 0); // Then the frame

受信終了:

u_long packet_size; // Hold the size of received packet
int bytes_to_read = 4; // We send 4 bytes on the wire for size and expect 4
int nresult; // hold result of recv()
char *psize = &packet_size; // Point to first byte of size
while( bytes_to_read ) // Keep reading until we have all the bytes for the size
{
  nresult = recv(socket, psize, bytes_to_read, 0);
  if(nresult==0) deal with connection closed.
  bytes_to_read -= nresult;
  psize += nresult;
}
packet_size = ntohl(packet_size);
// Now we know the packet size we can proceed and read it similar to above
于 2011-03-06T20:09:55.903 に答える
2

低レベルのソケット プログラミングで心に留めておくべき概念は、トランスポートによって強制される構造を持たない大量のバイトを交換するということです。メッセージの記述を行うプロトコルを実装するのはあなた次第です。開始時に「メッセージ」と見なすものの全長を配置するか、受信バッファーでチェックする区切り文字バイトまたはシーケンスを使用するか、閉じます。最後に接続します(後者は最も簡単に見えますが、セットアップに費用がかかるため、実際のプログラムで接続を再利用する必要があるため、最適なソリューションではありません)。

これが複雑に見える場合 (実際には必ずしも簡単ではない場合もあります)、この作業をカプセル化するライブラリを探す必要があります。たとえば、ライブラリによってシリアル化、線引き、逆シリアル化されるオブジェクトを送受信できるようにするコード。しかし、作業を行う必要があり、トランスポート層がそれを行うわけではありません。

表示されているコードに関する小さな注意: 複数の受信バッファー割り当てでメモリ リークが発生しています...

于 2011-03-06T16:50:09.333 に答える
1

最初に各パケットのデータ量を指定するか(たとえば、最初の2バイトでパケットサイズを指定できます)、最後のパケットを他のパケットと同じサイズになるように埋め込みます。

編集:本当にTCPを「シミュレート」したい場合は、おそらくrecvfrom()とsendto()を使用する必要があります。そうすれば、さまざまなサイズのパケット全体でデータを受信でき、この問題は発生しません。

于 2011-03-06T16:06:21.383 に答える
1

受信者は、送信者から終了したことを通知される必要があります。これは、受信者が期待できるデータのサイズを最初に送信するか、常に同じ量のデータを送信するか、後続のバイトがなくなることを示すセンチネル値を送信することによって実行できます。送信者は、送信が終了したときに接続を閉じることもできます。この場合、recv は、読み取るものが残っておらず、接続が閉じられたことを検出すると 0 を返します。

于 2011-03-06T16:13:14.843 に答える