質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.84%

  • C

    3214questions

    C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

  • C++

    3027questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

C++での通信処理におけるデータの取りこぼし

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 1,368

Unity-chan

score 12

前提・実現したいこと

カメラから取得したRGBデータをネットワーク経由で他のPCへ送信するプログラムを作成しています.
現在通信にはWinsock2でUDP通信を使用しています.

発生している問題

取得した画素情報を逐次送信した場合,recvfrom()でデータを取りこぼす.

該当のソースコード

// Send.cpp
#include "SocketCom.h"
#define HEIGHT 480
#define WIDTH 640
static char pixelData[3][HEIGHT][WIDTH]; //送信したいピクセルデータ

int main(void){
    InitSendSocketCommunication("127.0.0.1", 11111);

       ----- データ格納及びシーケンス番号を付与する処理 -----

    // 送信処理
    for (int i = 0; i < HEIGHT ; i++){
        SendCharToPartner(pixelData[0][i], sizeof(pixelData[0][i]));
        SendCharToPartner(pixelData[1][i], sizeof(pixelData[1][i]));
        SendCharToPartner(pixelData[2][i], sizeof(pixelData[2][i]));
    }

    return 0;
}
// Receive.cpp
#include "SocketCom.h"
#define HEIGHT 480
#define WIDTH 640
static char pixelData[3][HEIGHT][WIDTH]; //受信したピクセルデータ

int main(void){
    InitReceiveSocketCommunication("127.0.0.1", 11111);

       ----- データ格納及びシーケンス番号を付与する処理 -----

    // 受信処理
    for (int i = 0; i < HEIGHT ; i++){
        ReceiveDataAsChar(pixelData[0][i], ARRAY_SIZE(pixelData[0][i]));
        ReceiveDataAsChar(pixelData[1][i], ARRAY_SIZE(pixelData[1][i]));
        ReceiveDataAsChar(pixelData[2][i], ARRAY_SIZE(pixelData[2][i]));

        ----- シーケンス番号を元に対応する配列に格納する処理 -----
    }

    return 0;
}
// SecketCom.h
#define UDP_MAX 65507    // UDP通信にて一度に送信することができる最大サイズ
#define ARRAY_SIZE(arr) ( sizeof(arr) / sizeof(arr[0]) )    // 配列の要素数を返すスクリプト

#include <string>
using std::string;

// Description:
//  送信する変数型一覧.
typedef enum Datatype{
    TYPE_INT,
    TYPE_DOUBLE,
} Datatype;

// IPアドレスとポート番号で初期化する
extern bool InitSendSocketCommunication(string    IPAdress,int portnumPartner);
extern bool InitReceiveSocketCommunication( string IPAdress,int    portnumPartner);
extern bool FinalSocketCommunication();
extern bool SendStringToPartner(string sendData);
extern bool SendValueToPartner(void *sendData,int arraySize,Datatype datatype);
extern bool ReceiveDataAsChar(    char *receivedata,int dataSize);
extern bool ReceiveDataAsValue(    void *data,int dataSize,Datatype datatype);
// SocketCom.cpp
//=============================================================================
// include / using / define
//=============================================================================

// ソケット通信
#include <winsock2.h>
// string
#include "SocketCommunication.h"
using std::to_string;
// 文字列出力
#include <iostream>
using std::cout;
using std::endl;

#define STR(var) #var
#define SEPARATOR "/"


//=============================================================================
// variable
//=============================================================================

static SOCKET g_sockSend, g_sockReceive;        // 送受信の通信プロトコル設定
static struct sockaddr_in g_addrSend, g_addrReceive;    // 送受信のアドレス,ポート設定
static bool isInitialized = false;
static int g_socketError = 0;
static bool g_isBind = false;
static int sockaddr_in_size;


//=============================================================================
// Static Function
//=============================================================================

// WinSockのバージョンを指定
static bool InitWSAData(){
    WSADATA wsaData;
    int error;

    error = WSAStartup(MAKEWORD(2, 0), &wsaData);
    if (error != 0){
        cout << "Winsock WSAStartup Error (SocketCommunication.cpp > InitWSAData())" << endl;
        cout << STR(error) << endl;

        return false;
    }
    isInitialized = true;
    return true;
}

//=============================================================================
// Initialize / Finalize Functions
//=============================================================================

// 送信の初期化
bool InitSendSocketCommunication(string IPAdress, int portnumPartner){

    if (!isInitialized)
        if (!InitWSAData())
            return false;

    // 送信のプロトコルを決める
    g_sockSend = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    g_addrSend.sin_family = AF_INET;
    g_addrSend.sin_port = htons(portnumPartner);
    g_addrSend.sin_addr.S_un.S_addr = inet_addr(IPAdress.c_str());

    return true;
}

// 受信の初期化
bool InitReceiveSocketCommunication(string IPAdress, int portnumRecv){
    if (!isInitialized)
        InitWSAData();

    // 送信のプロトコルを決める
    g_sockReceive = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    g_addrReceive.sin_family = AF_INET;
    g_addrReceive.sin_port = htons(portnumRecv);
    g_addrReceive.sin_addr.S_un.S_addr = INADDR_ANY;
    sockaddr_in_size = sizeof(struct sockaddr_in);

    if (!g_isBind){
        g_socketError = bind(g_sockReceive, (struct sockaddr *)&g_addrReceive, sizeof(g_addrReceive));
        if (g_socketError != 0){
            cout << "Port bind failture! (SocketCommunication.cpp > ReceiveDataAsChar() > bind())" << endl;
            return false;
        }
        g_isBind = true;
    }
    char val;
    int len;

    return true;
}

// ソケット通信の終了
bool FinalSocketCommunication(void){
    closesocket(g_sockSend);
    WSACleanup();

    return true;
}


//=============================================================================
// Send / Receive Functions
//=============================================================================

// 文字列の送信
bool SendStringToPartner(string sendData){
    // UDPの限界を超えていないかチェック
    if (sendData.size() > UDP_MAX){
        cout << "Data size is too large! (SocketCommunication.cpp > SendStringToPartner() > string sendData)" << endl;
        return false;
    }

    g_socketError = sendto(g_sockSend, sendData.c_str(), sendData.size(), 0, (struct sockaddr *)&g_addrSend, sizeof(g_addrSend));
    if (g_socketError == -1){
        cout << "Data send failture! (SocketCommunication.cpp > SendStringToPartner() > sendto())" << endl;
        return false;
    }

    return true;
}

// 数値の送信(文字列へ変換して送る)
bool SendValueToPartner(void *sendData, int arraySize, Datatype datatype){
    string buffer;
    int *bufInt;
    double *bufDouble;

    switch (datatype){
        case TYPE_INT:
            bufInt = (int*)sendData;

            for (int j = 0; j < arraySize; j++){
                buffer += to_string(*(bufInt + j));
                buffer += SEPARATOR;
            }
            break;
        case TYPE_DOUBLE:
            bufDouble = (double*)sendData;

            for (int j = 0; j < arraySize; j++){
                buffer += to_string(*(bufDouble + j));
                buffer += SEPARATOR;
            }
            break;
    }

    if( !SendStringToPartner(buffer) )
        return false;

    return true;
}

// charデータの受信
bool ReceiveDataAsChar(char *data, int dataSize){
    // UDPの限界を超えていないかチェック
    if (dataSize > UDP_MAX){
        cout << "Data size is too large! (SocketCommunication.cpp > ReceiveDataAsChar() > int dataSize)" << endl;
        return false;
    }    

    memset(data, 0, dataSize);

        g_socketError = recvfrom(g_sockReceive, data, 1284, 0, (struct sockaddr *)&g_addrReceive, &sockaddr_in_size);
    if (g_socketError < 0){
        cout << "Data receive failture! (SocketCommunication.cpp > ReceiveDataAsChar() > recv())" << endl;
        cout << "ERROR_CODE:" << WSAGetLastError() << endl;
        return false;
    }

    return true;
}

// 数値データの受信
bool ReceiveDataAsValue(void *data, int dataSize, Datatype datatype){
    char buffer[UDP_MAX];
    static char *p;    //正直謎ポインタ(for strtok_s())

    // とりあえずchar型として受け取る
    if (!ReceiveDataAsChar(buffer, sizeof(buffer)))
        return false;

    // doubleとかはcharとデータサイズが違うから,数値データ一つ毎にSEPARATORをはさんで送る
    switch (datatype){
    case TYPE_INT:
        *((int*)data) = atof( strtok_s( buffer, SEPARATOR, &p ) );
        for (int i = 1; i < dataSize; i++)
            *((int*)data + i) = atof(strtok_s(NULL, SEPARATOR, &p));
        break;
    case TYPE_DOUBLE:
        *((double*)data) = atof( strtok_s( buffer, SEPARATOR, &p ) );
        for (int i = 1; i < dataSize; i++)
            *((double*)data + i) = atof(strtok_s(NULL, SEPARATOR, &p));
        break;
    }

    return true;
}

試したこと

送信のforループの中に無意味な処理("for (int h = 0; h < 1000000; h++)")を追加して送信の間隔を空けた場合,取りこぼすことなく受信することができた.しかし速度を重視してUDPを採用したので,可能であれば無意味な処理を使用せず実行したいと考えている.

補足情報(言語/FW/ツール等のバージョンなど)

IDE 
Visual Studio 2013 Express for Windows Desktop

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+3

速度を重視する代わりに取りこぼしが起きるのがUDPです。UDPを使う以上、取りこぼしを防ぐのは無理だと思います。もし取りこぼしに対する検証処理を書くなら、最初からTCPを使うほうが良いと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/12 17:38

    回答ありがとうございます。
    UDPを用いているので多少の取りこぼしがあるのは許容できます.しかしWireSharkでパケットを確認すると,受信自体はできているように見えるにも関わらずrecvfrom()を実行した場合に良くても10%程のデータしか受け取ることが出来ませんでした.
    今まで取りこぼしの主な原因は通信経路上のパケットロスと思っていたのですが,こういったロスも多いのでしょうか….その場合だとTCPにした方がいいかもしれません.

    キャンセル

  • 2017/01/12 17:50

    んー…そこまで詳しい調査をしたことはないのでどこに原因があるかは不明です。
    UDPは下手な鉄砲数撃ちゃ当たるで、TCPは必中スナイパーライフルのようなもの、とだけ認識して使っていたので、詳しくはわかりません。UDPは多少間が抜けても構わないもの、TCPは絶対抜け落ちてはならないものに使うので、画像等の1つのバイナリデータを送信するならTCPを使うのが無難かと思います。

    キャンセル

checkベストアンサー

+2

他回答へのコメントより:

UDPを用いているので多少の取りこぼしがあるのは許容できます.しかしWireSharkでパケットを確認すると,受信自体はできているように見えるにも関わらずrecvfrom()を実行した場合に良くても10%程のデータしか受け取ることが出来ませんでした.

質問者さんが言及している通り、UDPはあらゆるレイヤでデータグラム欠損が起きうる通信プロトコルです。実ネットワーク上でのパケット・ロス発生以外にも、NIC内部で破損検知したデータグラムを破棄したり、ネットワークスタック内部の送受信バッファ不足によりデータグラムを破棄することもあります。

SO_SNDBUFSO_RCVBUFにより送受信バッファサイズを大きくすれば、UDPデータグラムの取りこぼしが多少は減る可能性があります。

送信のforループの中に無意味な処理("for (int h = 0; h < 1000000; h++)")を追加して送信の間隔を空けた場合

UDP送信処理という観点からは、上記のような微妙なタイミング制御には意味があります。データグラム欠損を極力避けるには、UDP送信側はなるべく一定間隔でのデータ送信を行うよう制御し(バースト的な送出処理は避ける)、UDP受信側は可能な限り最速でネットワークスタックからデータを取り出す(recvfromとその後の解析処理を分離するなど)必要があります。

しかし速度を重視してUDPを採用したので,可能であれば無意味な処理を使用せず実行したいと考えている.

(気持ちは分かりますが)UDP通信路全体でみると全くの逆効果です。送信側だけの都合で最速処理すると、既に説明したようなデータグラム欠損があちこちで生じてしまいます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/28 00:38

    返答が遅くなって申し訳ありません!
    以下のプログラムを追加してバッファを大きくしたところ正常に動作することを確認できました。ありがとうございます。
    int recvBuffer
    setsockopt(g_sockReceive, SOL_SOCKET, SO_RCVBUF, (char *)&recvBuffer, OptLen);

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.84%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C

    3214questions

    C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

  • C++

    3027questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。