web-dev-qa-db-ja.com

C / C ++のTCPソケットから読み取る正しい方法は何ですか?

ここに私のコードがあります:

// Not all headers are relevant to the code snippet.
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <cstdlib>
#include <cstring>
#include <unistd.h>

char *buffer;
stringstream readStream;
bool readData = true;

while (readData)
{
    cout << "Receiving chunk... ";

    // Read a bit at a time, eventually "end" string will be received.
    bzero(buffer, BUFFER_SIZE);
    int readResult = read(socketFileDescriptor, buffer, BUFFER_SIZE);
    if (readResult < 0)
    {
        THROW_VIMRID_EX("Could not read from socket.");
    }

    // Concatenate the received data to the existing data.
    readStream << buffer;

    // Continue reading while end is not found.
    readData = readStream.str().find("end;") == string::npos;

    cout << "Done (length: " << readStream.str().length() << ")" << endl;
}

あなたが言うことができるように、それは少しのCとC++です。 BUFFER_SIZEは256です-サイズを増やすだけですか?もしそうなら、何に?それは重要ですか?

「終了」がなんらかの理由で受信されない場合、これはエンドレスループになり、それが悪いことを知っています。

23
Nick Bolton

アプリケーション全体を知ることなく、問題にアプローチする最善の方法を言うのは難しいですが、一般的な手法は、メッセージの残りの長さを示す固定長フィールドで始まるヘッダーを使用することです。

ヘッダーがメッセージの残りの長さを示す4バイトの整数のみで構成されていると仮定します。その後、次の操作を行います。

// This assumes buffer is at least x bytes long,
// and that the socket is blocking.
void ReadXBytes(int socket, unsigned int x, void* buffer)
{
    int bytesRead = 0;
    int result;
    while (bytesRead < x)
    {
        result = read(socket, buffer + bytesRead, x - bytesRead);
        if (result < 1 )
        {
            // Throw your error.
        }

        bytesRead += result;
    }
}

その後、コードで

unsigned int length = 0;
char* buffer = 0;
// we assume that sizeof(length) will return 4 here.
ReadXBytes(socketFileDescriptor, sizeof(length), (void*)(&length));
buffer = new char[length];
ReadXBytes(socketFileDescriptor, length, (void*)buffer);

// Then process the data as needed.

delete [] buffer;

これにはいくつかの仮定があります。

  • intは、送信側と受信側で同じサイズです。
  • エンディアネスは、送信者と受信者の両方で同じです。
  • 両側でプロトコルを制御できます
  • メッセージを送信するときに、前もって長さを計算できます。

ネットワークを介して送信している整数のサイズを明示的に知りたいことが一般的であるため、ヘッダーファイルで定義し、次のように明示的に使用します。

// These typedefs will vary across different platforms
// such as linux, win32, OS/X etc, but the idea
// is that a Int8 is always 8 bits, and a UInt32 is always
// 32 bits regardless of the platform you are on.
// These vary from compiler to compiler, so you have to 
// look them up in the compiler documentation.
typedef char Int8;
typedef short int Int16;
typedef int Int32;

typedef unsigned char UInt8;
typedef unsigned short int UInt16;
typedef unsigned int UInt32;

これにより、上記が次のように変更されます。

UInt32 length = 0;
char* buffer = 0;

ReadXBytes(socketFileDescriptor, sizeof(length), (void*)(&length));
buffer = new char[length];
ReadXBytes(socketFileDescriptor, length, (void*)buffer);

// process

delete [] buffer;

これがお役に立てば幸いです。

32
grieve

いくつかのポインター:

0の戻り値を処理する必要があります。これは、リモートホストがソケットを閉じたことを示します。

ノンブロッキングソケットの場合は、エラーの戻り値(-1)をチェックし、errnoがEINPROGRESSではないことを確認する必要があります。これは予想どおりです。

あなたは間違いなくより良いエラー処理が必要です-あなたは潜在的に「バッファ」が指すバッファをリークしています。これは、このコードスニペットのどこにも割り当てないことに気付きました。

Read()がバッファ全体を埋める場合、バッファがnullで終了するC文字列ではないことについて、他の誰かが良い点を指摘しました。それは確かに問題であり、深刻な問題です。

バッファサイズは少し小さくなりますが、256バイトを超えて読み取ろうとしない限り、または割り当てたものは何でも動作するはずです。

リモートホストから不正なメッセージ(サービス拒否攻撃の可能性がある)が送信されたときに無限ループに入ることが心配な場合は、ソケットのタイムアウトでselect()を使用して読みやすさを確認し、データが利用可能であり、select()がタイムアウトになると救済されます。

このような何かがあなたのために働くかもしれません:

fd_set read_set;
struct timeval timeout;

timeout.tv_sec = 60; // Time out after a minute
timeout.tv_usec = 0;

FD_ZERO(&read_set);
FD_SET(socketFileDescriptor, &read_set);

int r=select(socketFileDescriptor+1, &read_set, NULL, NULL, &timeout);

if( r<0 ) {
    // Handle the error
}

if( r==0 ) {
    // Timeout - handle that. You could try waiting again, close the socket...
}

if( r>0 ) {
    // The socket is ready for reading - call read() on it.
}

受信するデータの量に応じて、メッセージ全体を繰り返して「終了」をスキャンする方法。トークンは非常に非効率的です。これは、ステートマシン(ステートは 'e'-> 'n'-> 'd'-> ';')を使用して行う方が適切です。したがって、着信する各キャラクターを1回だけ見ることができます。

そして真剣に、あなたはあなたのためにこれをすべて行うライブラリを見つけることを検討すべきです。それを正しくするのは簡単ではありません。

9
Ori Pessach

1)他の人は(特にひどく)バッファにいくらかのメモリ空間を割り当てる必要があることに気付きました。 Nの値が小さい場合(N <= 4096など)、スタックに割り当てることもできます。

#define BUFFER_SIZE 4096
char buffer[BUFFER_SIZE]

これにより、delete[]バッファは例外をスローする必要があります。

ただし、スタックareサイズは有限(ヒープも有限ですが)であることを忘れないでください。

2)-1の戻りコードでは、ただちにすぐに戻るべきではありません(すぐに例外をスローすることはさらに大雑把です)。 。たとえば、非ブロッキングソケットで現在使用可能なデータがない場合、EAGAINがerrnoで返されることがあります。 read(2)のmanページをご覧ください。

3
Dan Breslau

ダークスの提案に従って実際にバッファを作成する場合、次のようになります。

  int readResult = read(socketFileDescriptor, buffer, BUFFER_SIZE);

文字列ストリームへの抽出時に依存する終端ゼロ文字を上書きする可能性があるため、バッファを完全に満たす可能性があります。必要なもの:

  int readResult = read(socketFileDescriptor, buffer, BUFFER_SIZE - 1 );
3
anon

bufferのメモリはどこに割り当てますか? bzeroを呼び出す行は、バッファがメモリの有効な領域を指していないため、未定義の動作を呼び出します。

char *buffer = new char[ BUFFER_SIZE ];
// do processing

// don't forget to release
delete[] buffer;
1
dirkgently

これは、ソケットを操作するときに常に参照する記事です。

SELECT()の世界

'select()'を確実に使用する方法を示し、ソケットの詳細については、下部に他の便利なリンクが含まれています。

1
Arnold Spence

上記の投稿のいくつかからのものに追加するだけです:

read()-少なくとも私のシステムでは-ssize_tを返します。これは、署名されていることを除いて、size_tに似ています。私のシステムでは、intではなく長いです。システム、コンパイラ、および有効にした警告に応じて、intを使用するとコンパイラ警告が表示される場合があります。

0
Joseph Larson