An Embedded Engineer’s Blog

とある組み込みエンジニアの備忘録的なブログです。

C++でソケット通信 (UDPユニキャスト編)

まえがき

ソケットを使ったEthernet通信の実装方法メモです。
今回は、C++UDPユニキャスト通信を行うための機能を実装します。

ソケット通信は、OSによって実装方法が異なるので、Windows / Mac / Linuxそれぞれの実装を行い、環境によって自動的に切り替わるようにしています。

前提条件

今回実装したソースは以下の環境でビルド、動作確認しています。

OS Ver Compiler Remarks
Windows 10 Visual Studio 2019
macOS 10.15.6 Clang 11.0
Ubuntu 18.04 GCC 7.5.0


また、プロジェクトソース一式は GitHub に置いてあります。

インタフェース

まずは、ソケット通信を行うためのインタフェースを設計します。

コンセプト

まえがきにも書きましたが、ソケット通信は、OSなどの環境によって実装方法が異なるので、Windows / Mac / Linuxそれぞれの実装を行い、環境によって自動的に切り替わるようにします。

ソケット通信機能を使用するユーザが環境の違いを意識しなくても良いように、共通のインタフェースを定義したアダプタクラス(SocketAdapter)を作成します。

SocketAdapterクラスでは、以下のインタフェースを提供します。

Interface Type Detail Remarks
Initialize Static ソケット全体の初期化
Finalize Static ソケット全体の後始末
GetErrorCode Static ソケット関連のエラーコード取得
OpenUdpUniTxSocket Instance UDPユニキャスト送信用ソケットオープン
OpenUdpUniRxSocket Instance UDPユニキャスト受信用ソケットオープン
CloseSocket Instance ソケットクローズ
IsSocketOpened Instance ソケットオープン確認
Transmit Instance UDPパケット送信
ReceiveSync Instance UDPパケット同期受信


Initialize / Finalizeメソッドは、ソケット関連処理で全体としての初期化/終了処理です。
これらのメソッドは、プログラムの最初と最後に1度だけ呼び出されることを想定しています。

GetErrorCodeメソッドはソケット関連で何らかのエラー発生時に、エラーコードを取得するためのメソッドです。
SocketAdapterの各メソッド呼び出しに失敗した場合に、このメソッドを呼び出すとエラーコードを取得できます。
後々、全OS共通のエラーコードを定義したいですが、今回は環境固有のエラーコードを返すようにします。

OpenUdpUniTxSocket / OpenUdpUniRxSocket / CloseSocketメソッドは、UDPユニキャスト用のソケットをオープン/クローズするためのメソッドです。
送信と受信で必要な情報(引数)が異なるため、インタフェースを分けています。
また、IsSocketOpenedメソッドで、ソケットがオープンしているかを確認することが出来ます。

Transmit / ReceiveSyncメソッドは、UDPパケットを送受信するメソッドです。
ReceiveSyncメソッドは、UDPパケットを同期的に受信します。
ReceiveSyncメソッドを呼び出すと、ソケットがUDPパケットを受信するまでブロックし呼び出し元に返らなくなります。
そしてUDPパケットを受信すると、受信データをバッファに書き込み、呼び出し元に制御が返ります。


実装

共通定義

まずは、共通で使用するデータ型や例外などを定義します。

【SocketDataTypes.h】

#pragma once
#include <cstdint>
#include <string>

/* Any Data Pointer */
using any_ptr = void*;

/* Byte Data */
using byte = uint8_t;

/* Byte Data Pointer */
using byte_ptr = uint8_t*;


【SocketException.h】

#pragma once

#include <exception>
#include <stdexcept>
#include <string>

class SocketException : public std::runtime_error
{
public:
    SocketException(const char* message, int error_code)
        : runtime_error(message)
        , m_ErrorCode(error_code)
        {
            /* Nothing to do */
        }
    
    SocketException(const std::string& message, int error_code)
        : runtime_error(message)
        , m_ErrorCode(error_code)
        {
            /* Nothing to do */
        }

    int GetErrorCode()
    {
        return this->m_ErrorCode;
    }

private:
    int m_ErrorCode;
};


コンパイルスイッチ

OSやコンパイラによってソケット通信の実装を切り替えられるように、コンパイルスイッチCOM_TYPEを定義します。

【CompileSwitch.h】

#pragma once
#define COM_SOCKET          (0)
#define COM_WINSOCK         (1)
#define COM_MACSOCK         (2)
#define COM_UNKNONW         (-1)

#if defined(_MSC_VER)
#define COM_TYPE            COM_WINSOCK         /* Visual Studio : WinSock */
#elif defined(__MACH__)
#define COM_TYPE            COM_MACSOCK         /* Mac : Mac Socket */
#elif defined(__linux__)
#define COM_TYPE            COM_SOCKET          /* Linux : Socket */
#else
#define COM_TYPE            COM_UNKNONW         /* Other : Not Support */
#endif


ソケットアダプタ

SocketAdapterクラスは、OSによって実装を切り替えるためにImplパターンで実装します。

【SocketAdapter.h】

#pragma once
#include "SocketDataTypes.h"

#include <memory>

/* Socket Adapter Implクラス前方宣言 */
class SocketAdapterImpl;

/* Socket Adapterクラス宣言 */
class SocketAdapter final
{
public:
    /* Socket全体の初期化 */
    static void Initialize();
    /* Socket全体の後始末 */
    static void Finalize();
    /* エラーコード取得 */
    static int GetErrorCode();

    /* コンストラクタ */
    SocketAdapter();
    /* デストラクタ */
    ~SocketAdapter();

    /* UDPユニキャスト送信用ソケットオープン */
    void OpenUdpUniTxSocket(const std::string& remote_ip, const uint16_t remote_port);
    /* UDPユニキャスト受信用ソケットオープン */
    void OpenUdpUniRxSocket(const uint16_t local_port);
    /* ソケットクローズ */
    void CloseSocket();

    /* ソケットオープン確認 */
    bool IsSocketOpened();

    /* パケット送信 */
    void Transmit(const any_ptr data_ptr, size_t tx_size);
    /* パケット同期受信 */
    void ReceiveSync(byte_ptr& buffer_ptr, const size_t buffer_size, size_t& rx_size);

private:
    /* Socket Adapter Implクラスインスタンス */
    std::unique_ptr<SocketAdapterImpl> m_Impl;
};

SocketAdapterImplクラスは、コンパイルスイッチCOM_TYPEによって自動的に切り替わるようにし、SocketAdapterクラスの各メソッドはSocketAdapterImplクラスのメソッドを呼び出すだけにします。

【SocketAdapter.cpp】

#include "SocketAdapter.h"
#include "CompileSwitch.h"

#if COM_TYPE == COM_WINSOCK
#include "SocketAdapterImpl_WinSock.h"
#elif COM_TYPE == COM_SOCKET || COM_TYPE == COM_MACSOCK
#include "SocketAdapterImpl_Socket.h"
#else
#include "SocketAdapterImpl_Unknown.h"
#endif

/* Socket全体の初期化 */
void SocketAdapter::Initialize()
{
    SocketAdapterImpl::Initialize();
}

/* Socket全体の後始末 */
void SocketAdapter::Finalize()
{
    SocketAdapterImpl::Finalize();
}

/* エラーコード取得 */
int SocketAdapter::GetErrorCode()
{
    return SocketAdapterImpl::GetErrorCode();
}

/* コンストラクタ */
SocketAdapter::SocketAdapter()
    : m_Impl(nullptr)
{
    /* Socket Adapter Implクラスインスタンス生成 */
    this->m_Impl = std::make_unique<SocketAdapterImpl>();
}

/* デストラクタ */
SocketAdapter::~SocketAdapter()
{
    /* Nothing to do */
}

/* UDPユニキャスト送信用ソケットオープン */
void SocketAdapter::OpenUdpUniTxSocket(const std::string& remote_ip, const uint16_t remote_port)
{
    this->m_Impl->OpenUdpUniTxSocket(remote_ip, remote_port);
}

/* UDPユニキャスト受信用ソケットオープン */
void SocketAdapter::OpenUdpUniRxSocket(const uint16_t local_port)
{
    this->m_Impl->OpenUdpUniRxSocket(local_port);
}

/* ソケットクローズ */
void SocketAdapter::CloseSocket()
{
    this->m_Impl->CloseSocket();
}

/* ソケットオープン確認 */
bool SocketAdapter::IsSocketOpened()
{
    return this->m_Impl->IsSocketOpened();
}

/* パケット送信 */
void SocketAdapter::Transmit(const any_ptr data_ptr, size_t tx_size)
{
    this->m_Impl->Transmit(data_ptr, tx_size);
}

/* パケット同期受信 */
void SocketAdapter::ReceiveSync(byte_ptr& buffer_ptr, const size_t buffer_size, size_t& rx_size)
{
    this->m_Impl->ReceiveSync(buffer_ptr, buffer_size, rx_size);
}


Windows版(WinSock2)

Windowsでは、WinSock2 APIを使ってソケット通信を実装します。

【SocketAdapterImpl_WinSock.h】

#pragma once
#include "CompileSwitch.h"

#if COM_TYPE == COM_WINSOCK
#include "SocketDataTypes.h"
#include "SocketException.h"

#include <WinSock2.h>
#include <ws2tcpip.h>

#include <sstream>

#pragma comment(lib, "ws2_32.lib")

/* Socket Adapter Implクラス定義 */
class SocketAdapterImpl final
{
public:
    /* Socket全体の初期化 */
    static void Initialize()
    {
        /* エラーコードクリア */
        SocketAdapterImpl::s_ErrorCode = 0;

        /* WinSockサービススタートアップ */
        int startup_result = WSAStartup(MAKEWORD(2, 0), &SocketAdapterImpl::s_WsaData);

        /* WinSockサービスススタートアップ失敗時のエラー処理 */
        if (startup_result != 0)
        {
            /* エラーコードセット */
            SocketAdapterImpl::s_ErrorCode = startup_result;

            /* ソケット例外送出 */
            throw SocketException(SocketAdapterImpl::GetErrorMessage("WSA Startup Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
        }
    }

    /* Socket全体の後始末 */
    static void Finalize()
    {
        /* WinSockサービスクリーンアップ */
        int cleanup_result = WSACleanup();

        /* WinSockサービスクリーンアップ失敗時のエラー処理 */
        if (cleanup_result == SOCKET_ERROR)
        {
            SocketAdapterImpl::s_ErrorCode = WSAGetLastError();

            /* ソケット例外送出 */
            throw SocketException(SocketAdapterImpl::GetErrorMessage("WSA Cleanup Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
        }
    }

    /* エラーコード取得 */
    static int GetErrorCode()
    {
        return SocketAdapterImpl::s_ErrorCode;
    }

    /* コンストラクタ */
    SocketAdapterImpl()
        : m_Socket(0)
        , m_Address()
        , m_IsSocketOpened(false)
    {
        /* Nothing to do */
    }

    /* デストラクタ */
    ~SocketAdapterImpl()
    {
        /* ソケットオープン確認 */
        if (this->IsSocketOpened() == true)
        {
            /* ソケットがオープンしていたらクローズする */
            this->CloseSocket();
        }
    }

    /* UDPユニキャスト送信用ソケットオープン */
    void OpenUdpUniTxSocket(const std::string& remote_ip, const uint16_t remote_port)
    {
        /* UDP用ソケットをオープン */
        this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);

        /* ソケットオープン失敗時のエラー処理 */
        if (this->m_Socket == INVALID_SOCKET)
        {
            /* エラーコードセット */
            SocketAdapterImpl::s_ErrorCode = WSAGetLastError();

            /* ソケット例外送出 */
            throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Unicast Tx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
        }

        /* UDPユニキャスト送信用アドレス情報セット */
        this->m_Address.sin_family = AF_INET;
        this->m_Address.sin_port = htons(remote_port);
        this->m_Address.sin_addr.S_un.S_addr = this->ConvertIpStrToNum(this->m_Address.sin_family, remote_ip);

        /* ソケットオープン状態更新 */
        this->m_IsSocketOpened = true;
    }

    /* UDPユニキャスト受信用ソケットオープン */
    void OpenUdpUniRxSocket(const uint16_t local_port)
    {
        /* UDP用ソケットをオープン */
        this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);

        /* ソケットオープン失敗時のエラー処理 */
        if (this->m_Socket == INVALID_SOCKET)
        {
            /* エラーコードセット */
            SocketAdapterImpl::s_ErrorCode = WSAGetLastError();

            /* ソケット例外送出 */
            throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Unicast Rx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
        }

        /* UDPユニキャスト受信用アドレス情報セット */
        this->m_Address.sin_family = AF_INET;
        this->m_Address.sin_port = htons(local_port);
        this->m_Address.sin_addr.S_un.S_addr = INADDR_ANY;

        /* ソケットにアドレス情報をバインド */
        int wsa_result = bind(this->m_Socket, (struct sockaddr*)&this->m_Address, sizeof(this->m_Address));

        /* ソケットバインド失敗時のエラー処理 */
        if (wsa_result != 0)
        {
            /* エラーコードセット */
            SocketAdapterImpl::s_ErrorCode = WSAGetLastError();

            /* ソケット例外送出 */
            throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Unicast Rx Socket Bind Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
        }

        /* ソケットオープン状態更新 */
        this->m_IsSocketOpened = true;
    }

    /* ソケットクローズ */
    void CloseSocket()
    {
        /* ソケットオープン確認 */
        if (this->IsSocketOpened() == true)
        {
            /* ソケットがオープンしていたらクローズ */
            int close_result = closesocket(this->m_Socket);

            /* ソケットクローズ失敗時のエラー処理 */
            if (close_result == SOCKET_ERROR)
            {
                /* エラーコードセット */
                SocketAdapterImpl::s_ErrorCode = WSAGetLastError();

                /* ソケット例外送出 */
                throw SocketException(SocketAdapterImpl::GetErrorMessage("Socket Close Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
            }
            /* ソケットクローズ成功時 */
            else
            {
                /* ソケットオープン状態をクリア */
                this->m_IsSocketOpened = false;
            }
        }
        else
        {
            /* Nothing to do */
        }
    }

    /* ソケットオープン確認 */
    bool IsSocketOpened()
    {
        return this->m_IsSocketOpened;
    }

    /* パケット送信 */
    void Transmit(const any_ptr data_ptr, size_t tx_size)
    {
        /* ソケットにパケット送信 */
        int send_result = sendto(this->m_Socket, (const char*)data_ptr, (int)tx_size, 0, (struct sockaddr*)&this->m_Address, sizeof(this->m_Address));

        /* パケット送信失敗時のエラー処理 */
        if (send_result == SOCKET_ERROR)
        {
            /* エラーコードセット */
            SocketAdapterImpl::s_ErrorCode = WSAGetLastError();

            /* ソケット例外送出 */
            throw SocketException(SocketAdapterImpl::GetErrorMessage("Packet Transmit Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
        }
    }

    /* パケット同期受信 */
    void ReceiveSync(byte_ptr& buffer_ptr, const size_t buffer_size, size_t& rx_size)
    {
        /* ソケットからパケット受信(ブロッキング) */
        int receive_result = recv(this->m_Socket, (char*)buffer_ptr, (int)buffer_size, 0);

        /* パケット受信失敗時のエラー処理 */
        if (receive_result == SOCKET_ERROR)
        {
            /* エラーコードセット */
            SocketAdapterImpl::s_ErrorCode = WSAGetLastError();

            /* ソケット例外送出 */
            throw SocketException(SocketAdapterImpl::GetErrorMessage("Packet Receive Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
        }
        /* パケット受信成功時 */
        else
        {
             /* 受信データサイズをセット */
            rx_size = (size_t)receive_result;
        }
    }

private:
    /* エラーメッセージ生成 */
    static inline std::string GetErrorMessage(const char* message, int error_code)
    {
        std::stringstream ss;
        ss << "[Socket Error] " << message << " : ErrorCode = " << error_code;
        return ss.str();
    }

    /* IPアドレスの変換(文字列 -> 数値) */
    static ULONG ConvertIpStrToNum(const ADDRESS_FAMILY family, const std::string& ip)
    {
        /* 変換後IPアドレス */
        ULONG ip_num = 0;

        /* IPアドレスを文字列から数値に変換 */
        int pton_result = inet_pton(family, ip.c_str(), &ip_num);

        /* IPアドレス変換成功時 */
        if (pton_result == 1)
        {
            return ip_num;
        }
        /* IPアドレス変換失敗時のエラー処理 */
        else if (pton_result == 0)
        {
            /* エラーコードセット */
            SocketAdapterImpl::s_ErrorCode = -1;

            /* ソケット例外送出 */
            throw SocketException(SocketAdapterImpl::GetErrorMessage("Convert IP Address Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
        }
        else
        {
            /* エラーコードセット */
            SocketAdapterImpl::s_ErrorCode = WSAGetLastError();

            /* ソケット例外送出 */
            throw SocketException(SocketAdapterImpl::GetErrorMessage("Convert IP Address Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
        }
    }

private:
    /* WinSockサービス情報 */
    static WSADATA s_WsaData;
    /* エラーコード */
    static int s_ErrorCode;

private:
    /* ソケット */
    SOCKET m_Socket;
    /* ソケットアドレス情報 */
    struct sockaddr_in m_Address;
    /* ソケットオープン状態 */
    bool m_IsSocketOpened;
};

/* WinSockサービス情報 */
WSADATA SocketAdapterImpl::s_WsaData;

/* エラーコード */
int SocketAdapterImpl::s_ErrorCode = 0;

#else

#endif


Mac / Linux版(Socket)

MacおよびLinuxでは、BSD Socket APIを使ってソケット通信を実装します。
MacLinuxで一部インタフェースが異なる部分があるため、その部分に関してはコンパイルスイッチCOM_TYPEで切り替える仕様にします。

【SocketAdapterImpl_Socket.h】

#pragma once
#include "CompileSwitch.h"

#if COM_TYPE == COM_SOCKET || COM_TYPE == COM_MACSOCK
#include "SocketDataTypes.h"
#include "SocketException.h"

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#include <sstream>

/* Socket Adapter Implクラス定義 */
class SocketAdapterImpl final
{
public:
    /* Socket全体の初期化 */
    static void Initialize()
    {
        /* エラーコードクリア */
        SocketAdapterImpl::s_ErrorCode = 0;
    }

    /* Socket全体の後始末 */
    static void Finalize()
    {
        /* Nothing to do */
    }

    /* エラーコード取得 */
    static int GetErrorCode()
    {
        return SocketAdapterImpl::s_ErrorCode;
    }

    /* コンストラクタ */
    SocketAdapterImpl()
        : m_Socket(0)
        , m_Address()
        , m_IsSocketOpened(false)
    {
        /* Nothing to do */
    }

    /* デストラクタ */
    ~SocketAdapterImpl()
    {
        /* ソケットオープン確認 */
        if (this->IsSocketOpened() == true)
        {
            /* ソケットがオープンしていたらクローズする */
            this->CloseSocket();
        }
    }

    /* UDPユニキャスト送信用ソケットオープン */
    void OpenUdpUniTxSocket(const std::string& remote_ip, const uint16_t remote_port)
    {
        /* UDP用ソケットをオープン */
        this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);

        /* ソケットオープン失敗時のエラー処理 */
        if (this->m_Socket < 0)
        {
            /* エラーコードセット */
            SocketAdapterImpl::s_ErrorCode = errno;

            /* ソケット例外送出 */
            throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Unicast Tx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
        }

        /* UDPユニキャスト送信用アドレス情報セット */
        this->m_Address.sin_family = AF_INET;
        this->m_Address.sin_port = htons(remote_port);
        this->m_Address.sin_addr.s_addr = inet_addr(remote_ip.c_str());
#if COM_TYPE == COM_MACSOCK
        this->m_Address.sin_len = sizeof(this->m_Address);
#endif

        /* ソケットオープン状態更新 */
        this->m_IsSocketOpened = true;
    }

    /* UDPユニキャスト受信用ソケットオープン */
    void OpenUdpUniRxSocket(const uint16_t local_port)
    {
        /* UDP用ソケットをオープン */
        this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);

        /* ソケットオープン失敗時のエラー処理 */
        if (this->m_Socket < 0)
        {
            /* エラーコードセット */
            SocketAdapterImpl::s_ErrorCode = errno;

            /* ソケット例外送出 */
            throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Unicast Rx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
        }

        /* UDPユニキャスト受信用アドレス情報セット */
        this->m_Address.sin_family = AF_INET;
        this->m_Address.sin_port = htons(local_port);
        this->m_Address.sin_addr.s_addr = INADDR_ANY;
#if COM_TYPE == COM_MACSOCK
        this->m_Address.sin_len = sizeof(this->m_Address);
#endif

        /* ソケットにアドレス情報をバインド */
        int bind_result = bind(this->m_Socket, (struct sockaddr*)&this->m_Address, sizeof(this->m_Address));

        /* ソケットバインド失敗時のエラー処理 */
        if (bind_result != 0)
        {
            /* エラーコードセット */
            SocketAdapterImpl::s_ErrorCode = errno;

            /* ソケット例外送出 */
            throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Unicast Rx Socket Bind Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
        }

        /* ソケットオープン状態更新 */
        this->m_IsSocketOpened = true;
    }

    /* ソケットクローズ */
    void CloseSocket()
    {
        /* ソケットオープン確認 */
        if (this->IsSocketOpened() == true)
        {
            /* ソケットがオープンしていたらクローズ */
            int close_result = close(this->m_Socket);

            /* ソケットクローズ成功時 */
            if (close_result == 0)
            {
                /* ソケットオープン状態をクリア */
                this->m_IsSocketOpened = false;
            }
            /* ソケットクローズ失敗時のエラー処理 */
            else
            {
                /* エラーコードセット */
                SocketAdapterImpl::s_ErrorCode = errno;

                /* ソケット例外送出 */
                throw SocketException(SocketAdapterImpl::GetErrorMessage("Socket Close Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
            }
        }
        else
        {
            /* Nothing to do */
        }
    }

    /* ソケットオープン確認 */
    bool IsSocketOpened()
    {
        return this->m_IsSocketOpened;
    }

    /* パケット送信 */
    void Transmit(const any_ptr data_ptr, size_t tx_size)
    {
        /* ソケットにパケット送信 */
        int send_result = sendto(this->m_Socket, (const char*)data_ptr, (int)tx_size, 0, (struct sockaddr*)&this->m_Address, sizeof(this->m_Address));

        /* パケット送信失敗時のエラー処理 */
        if (send_result < 0)
        {
            /* エラーコードセット */
            SocketAdapterImpl::s_ErrorCode = errno;

            /* ソケット例外送出 */
            throw SocketException(SocketAdapterImpl::GetErrorMessage("Packet Transmit Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
        }
    }

    /* パケット同期受信 */
    void ReceiveSync(byte_ptr& buffer_ptr, const size_t buffer_size, size_t& rx_size)
    {
        /* ソケットからパケット受信(ブロッキング) */
        int receive_result = recv(this->m_Socket, (char*)buffer_ptr, (int)buffer_size, 0);

        /* パケット受信成功時 */
        if (receive_result >= 0)
        {
            /* 受信データサイズをセット */
            rx_size = (size_t)receive_result;
        }
        /* パケット受信失敗時のエラー処理 */
        else
        {
            /* エラーコードセット */
            SocketAdapterImpl::s_ErrorCode = errno;

            /* ソケット例外送出 */
            throw SocketException(SocketAdapterImpl::GetErrorMessage("Packet Receive Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);
        }
    }

private:
    /* エラーメッセージ生成 */
    static inline std::string GetErrorMessage(const char* message, int error_code)
    {
        std::stringstream ss;
        ss << "[Socket Error] " << message << " : ErrorCode = " << error_code;
        return ss.str();
    }

private:
    /* エラーコード */
    static int s_ErrorCode;

private:
    /* ソケット */
    int m_Socket;
    /* ソケットアドレス情報 */
    struct sockaddr_in m_Address;
    /* ソケットオープン状態 */
    bool m_IsSocketOpened;
};

/* エラーコード */
int SocketAdapterImpl::s_ErrorCode = 0;

#else

#endif


未対応環境

今回ビルド&動作確認を行っていない環境の場合には、実行時に例外を投げるようなSocketAdapterImplクラスを実装します。

【SocketAdapterImpl_Unknown.h】

#pragma once
#include "CompileSwitch.h"

#if COM_TYPE != COM_WINSOCK && COM_TYPE != COM_SOCKET && COM_TYPE != COM_MACSOCK
#include "SocketDataTypes.h"
#include "SocketException.h"

/* Socket Adapter Implクラス定義 */
class SocketAdapterImpl final
{
public:
    /* Socket全体の初期化 */
    static void Initialize()
    {
        throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));
    }

    /* Socket全体の後始末 */
    static void Finalize()
    {
        throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));
    }

    /* エラーコード取得 */
    static int GetErrorCode()
    {
        throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));
    }

    /* コンストラクタ */
    SocketAdapterImpl()
    {
        throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));
    }

    /* デストラクタ */
    ~SocketAdapterImpl()
    {
    }

    /* UDPユニキャスト送信用ソケットオープン */
    void OpenUdpUniTxSocket(const std::string& remote_ip, const uint16_t remote_port)
    {
        throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));
    }

    /* UDPユニキャスト受信用ソケットオープン */
    void OpenUdpUniRxSocket(const uint16_t local_port)
    {
        throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));
    }

    /* UDPマルチキャスト送信用ソケットオープン */
    void OpenUdpMultiTxSocket(const std::string& multicast_ip, const std::string& local_ip, const uint16_t multicast_port, const int32_t ttl)
    {
        throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));
    }

    /* UDPマルチキャスト受信用ソケットオープン */
    void OpenUdpMultiRxSocket(const std::string& multicast_ip, const uint16_t multicast_port)
    {
        throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));
    }

    /* UDPブロードキャスト送信用ソケットオープン */
    void OpenUdpBroadTxSocket(const std::string& remote_ip, const uint16_t remote_port)
    {
        throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));
    }

    /* UDPブロードキャスト受信用ソケットオープン */
    void OpenUdpBroadRxSocket(const uint16_t local_port)
    {
        throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));
    }

    /* ソケットクローズ */
    void CloseSocket()
    {
        throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));
    }

    /* ソケットオープン確認 */
    bool IsSocketOpened()
    {
        throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));
    }

    /* パケット送信 */
    void Transmit(const any_ptr data_ptr, size_t tx_size)
    {
        throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));
    }

    /* パケット同期受信 */
    void ReceiveSync(byte_ptr& buffer_ptr, const size_t buffer_size, size_t& rx_size)
    {
        throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));
    }
};

#else
#error Invalid Com Type : COM_TYPE
#endif


サンプルコード

機能

SocketAdapterクラスを使ったUDPユニキャスト送受信のサンプルコードを実装します。
サンプルコードでは、Sender/Receiverをそれぞれコンソールから起動することで以下のような動作をします。

【Sender】

  1. UDPユニキャスト送信用ソケット(IP:localhost Port:5000)を開く
  2. キーボードからの送信メッセージ入力を受け付ける
  3. キーボードから入力された送信メッセージをReceiverに送信する
  4. キーボードから"exit"が入力されると、ソケットを閉じてプログラムを終了する


【Receiver】

  1. UDPユニキャスト受信用ソケット(Port:5000)を開く
  2. Senderからのメッセージ受信を待機する
  3. メッセージを受信したら内容を表示する
  4. 受信メッセージが"exit"の場合、ソケットを閉じてプログラムを終了する

実装

Sender / Receiverそれぞれのサンプルコードを以下に示します。

【Sender.cpp】

#include "SocketAdapter.h"

#include <iostream>
#include <cstring>
#include <thread>
#include <chrono>

/* 受信メッセージのエンコード */
static void EncodeTxMessage(const std::string& message, byte_ptr& buffer_ptr, size_t& buffer_size);

int main()
{
    try
    {
        /* Socket全体の初期化 */
        SocketAdapter::Initialize();

        std::cout << "Socket Adapter Initialize Success" << std::endl;

        /* Socket Adapterインスタンス生成 */
        SocketAdapter adapter;

        std::cout << "Socket Adapter Instance Creation Success" << std::endl;

        /* UDPユニキャスト送信用ソケットオープン */
        adapter.OpenUdpUniTxSocket("127.0.0.1", 5000);

        std::cout << "UDP Unicast Tx Socket Open Success" << std::endl;

        while (true)
        {
            /* 送信メッセージ */
            std::string tx_msg =  "";
            /* 送信データバッファ */
            byte_ptr buffer_ptr = nullptr;
            /* 送信データバッファサイズ */
            size_t buffer_size = 0;

            /* 送信メッセージを標準入力から入力 */
            std::cout << ">";
            std::cin >> tx_msg;

            try
            {
                /* 送信メッセージをエンコード(送信データバッファ動的確保) */
                EncodeTxMessage(tx_msg, buffer_ptr, buffer_size);

                /* パケット送信 */
                adapter.Transmit((any_ptr)buffer_ptr, buffer_size);

                std::cout << "Transmit UDP Unicast Message : [" << tx_msg << "]" << std::endl;

                /* 送信データバッファ解放 */
                free(buffer_ptr);

                /* 送信終了判定 */
                if (tx_msg == "exit")
                {
                    break;
                }
            }
            catch(const std::exception& ex)
            {
                std::cerr << "[ERROR] " << ex.what() << std::endl;

                /* 送信エラーの場合は、一定時間待機して送信を再開する */
                std::this_thread::sleep_for(std::chrono::microseconds(100));
            }
        }

        /* ソケットクローズ */
        adapter.CloseSocket();

        std::cout << "Socket Close Success" << std::endl;

        /* Socket全体の後始末 */
        SocketAdapter::Finalize();

        std::cout << "Socket Adapter Finalize Success" << std::endl;

        return 0;
    }
    catch(const std::exception& ex)
    {
        std::cerr << "[ERROR] " << ex.what() << std::endl;

        /* ソケット初期化/終了/オープン/クローズ失敗時はエラー終了 */
        return -1;
    }
}

/* 受信メッセージのエンコード */
static void EncodeTxMessage(const std::string& message, byte_ptr& buffer_ptr, size_t& buffer_size)
{
    /* 文字列の長さ(null文字を含まない)を取得 */
    size_t tx_msg_len = message.length();

    /* 送信データサイズ(null文字を含む文字数)を算出 */
    size_t tx_data_size = tx_msg_len + 1;

    /* 送信データサイズ文のメモリを動的確保 */
    byte_ptr tx_data_ptr = (byte_ptr)malloc(tx_data_size);

    /* メモリ確保成功 */
    if (tx_data_ptr != nullptr)
    {
        /* 送信メッセージを送信データバッファにコピー */
        memcpy(tx_data_ptr, message.c_str(), tx_msg_len);

        /* 送信データバッファの終端にnull文字をセット */
        tx_data_ptr[tx_msg_len] = '\0';

        /* 送信バッファ/送信バッファサイズをセット */
        buffer_ptr = tx_data_ptr;
        buffer_size = tx_data_size;
    }
    else
    {
        /* 送信バッファ/送信バッファサイズをクリア */
        buffer_ptr = nullptr;
        buffer_size = 0;

        /* Bad Allocation例外送出 */
        throw std::bad_alloc();
    }
}

【Receiver.cpp】

#include "SocketAdapter.h"

#include <iostream>
#include <sstream>
#include <thread>
#include <chrono>
#include <exception>

/* 受信メッセージのデコード */
static std::string DecodeRxMessage(byte_ptr buffer_ptr, size_t data_size);

int main()
{
    try
    {
        /* Socket全体の初期化 */
        SocketAdapter::Initialize();

        std::cout << "Socket Adapter Initialize Success" << std::endl;

        /* Socket Adapterインスタンス生成 */
        SocketAdapter adapter;

        std::cout << "Socket Adapter Instance Creation Success" << std::endl;

        /* UDPユニキャスト受信用ソケットオープン */
        adapter.OpenUdpUniRxSocket(5000);

        std::cout << "UDP Unicast Rx Socket Open Success" << std::endl;

        while (true)
        {
            /* 受信バッファ */
            uint8_t buffer[1024];
            byte_ptr buffer_ptr = buffer;
            /* 受信バッファサイズ */
            size_t buffer_size = sizeof(buffer);
            /* 受信データサイズ */
            size_t rx_size = 0;

            try
            {
                /* パケット同期受信 */
                adapter.ReceiveSync(buffer_ptr, buffer_size, rx_size);

                /* 受信メッセージをデコード */
                std::string rx_msg = DecodeRxMessage(buffer_ptr, rx_size);

                std::cout << "UDP Unicast Message Received : [" << rx_msg << "]" << std::endl;

                /* 受信終了判定 */
                if (rx_msg == "exit")
                {
                    break;
                }
            }
            catch(const std::exception& ex)
            {
                std::cerr << "[ERROR] " << ex.what() << std::endl;

                /* 受信エラーの場合は、一定時間待機して受信を再開する */
                std::this_thread::sleep_for(std::chrono::microseconds(100));
            }
        }

        /* ソケットクローズ */
        adapter.CloseSocket();

        std::cout << "Socket Close Success" << std::endl;

        /* Socket全体の後始末 */
        SocketAdapter::Finalize();

        std::cout << "Socket Adapter Finalize Success" << std::endl;

        return 0;
    }
    catch(const std::exception& ex)
    {
        std::cerr << "[ERROR] " << ex.what() << std::endl;

        /* ソケット初期化/終了/オープン/クローズ失敗時はエラー終了 */
        return -1;
    }
}

/* 受信メッセージのデコード */
static std::string DecodeRxMessage(byte_ptr buffer_ptr, size_t data_size)
{
    /* 文字配列として受け取るのでそのまま文字列に変換 */
    std::stringstream ss;

    ss << buffer_ptr;

    std::string rx_msg = ss.str();

    return rx_msg;
}


あとがき

今回は、C++UDPユニキャスト通信を行う処理を実装しました。
次回は、ユニキャスト/ブロードキャストに対応させたいと思います。