今日のテスト
VC++でIPv6のUDP通信プログラム作成(2023/07/23)
→目次

VC++でIPv6のUDPソケット通信プログラムを作りたくて、ウェブサイトで検索して作り方を勉強して2台PC間で互いに文字列を送受信してチャタリングするという確認用のプログラムを作りました。
送受信ソケットは下図に示します。

このプログラムはVisual Studio 2022でテスト済です。
ビルドで生成した実行ファイルはipv6udp.exeとしましたが、コンソールのアプリケーションです。
PC1のコンソール画面で「ipv6udp.exe 1」 命令で起動して、PC2のコンソール画面で「ipv6udp.exe 2」 命令で起動しておけば、2台のPC間で互いに対向通信ができます。
このプログラムは1台のPC上でもテストできます。その場合は、2つのコンソール画面を立ち上げる必要があります。
PC1とPC2のIPv6アドレスを調べて、下記のA_REVC_IP_ADDRとB_REVC_IP_ADDRの内容を編集して、ビルドして頂く必要です。
#define A_NAME "A"
#define A_REVC_IP_ADDR "aaaa::bbbb:cccc:dddd:eeee%44"
#define A_REVC_PORT 10000
#define B_NAME "B"
#define B_REVC_IP_ADDR "aaaa::bbbb:cccc:dddd:eeee%44"
#define B_REVC_PORT 10001
このテストプログラムでは、送信ソケットと受信ソケットを別々作成して、送受信を行っています。
送信処理は、送信ソケットを作成してから、メインループに入り、手入力した文字列を相手の受信ソケットへ送信します。
受信処理は、受信ソケットを作成してから、スレッドのループに入り、相手の送信ソケットから受信した文字列をディスプレイに表示します。

以下はソースコードです。
// ipv6udp.cpp
#include <ws2tcpip.h>
#include <winsock2.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")

#pragma warning(disable:4996) 

using namespace std;

HANDLE hThread = NULL;
DWORD ThreadId;
volatile bool g_bRunning = 0;

// PC1のIPアドレスと受信用ソケットのポート
#define A_NAME "A"
#define A_REVC_IP_ADDR "aaaa::bbbb:cccc:dddd:eeee%44"
#define A_REVC_PORT 10000
// PC2のIPアドレスと受信用ソケットのポート
#define B_NAME "B"
#define B_REVC_IP_ADDR "aaaa::bbbb:cccc:dddd:eeee%44"
#define B_REVC_PORT 10001

SOCKET sockSend;
struct sockaddr_in6 send_sockAddrIn;
SOCKET sockRecv;
struct sockaddr_in6 recv_sockAddrIn;

char* send_to_ipaddr = NULL;
unsigned short send_to_port = 0;
char* revc_sock_ipaddr = NULL;
unsigned short revc_sock_port = 0;
char* myName = NULL;

// 受信スレッドによって実行される関数
DWORD WINAPI ReceiveFunction(LPVOID lpParam)
{
    // 受信ループ
    while (g_bRunning)
    {
        fd_set mask;
        FD_ZERO(&mask);
        FD_SET(sockRecv, &mask);
        struct timeval tv = { 1, 0 };

        // 受信データが入ったかをチェックする。待ち時間は1秒
        int ret = select((int)sockRecv + 1, &mask, NULL, NULL, &tv);
        if (ret == SOCKET_ERROR)
            break;
        else if (ret == 0)
        {
            continue;
        }
        else if (FD_ISSET(sockRecv, &mask))
        {
            // 受信データを読んで、画面に表示する
            char buff[1024];
            memset(buff, 0, sizeof(buff));
            int ilen = recvfrom(sockRecv, buff, 256, 0, NULL, NULL);
            if (ilen > 0)
            {
                cout << endl <<  buff << endl;
                cout << myName << ": ";
                if (ilen > 3) {
                    // 相手から "end"が送信されたら、g_bRunning ← 0で受信ループを抜ける
                    if (strcmp(&(buff[strlen(buff) - 3]), "end") == 0) {
                        g_bRunning = 0;
                        cout << endl << "相手が終了したようです。" << endl;
                        cout << myName << ": ";
                    }
                }
            }
        }
    }
    // 送信ソケットを閉じる
    closesocket(sockRecv);
    // スレッドを終了する
    ExitThread(0);
    return 0;
}

// 受信スレッドを立ち上げる
void CreateAndStartReceiveThread()
{
    g_bRunning = 1;
    hThread = CreateThread(NULL, 0, ReceiveFunction, NULL, 0, &ThreadId);
}

// 受信スレッドを終了する
void StopReceiveThread()
{
    g_bRunning = 0;
    if (hThread)
    {
        if (WAIT_TIMEOUT == WaitForSingleObject(hThread, 5000))
        {
            TerminateThread(hThread, 0);
        }
        CloseHandle(hThread);
        hThread = NULL;
    }
}

// メイン関数
int main(int argc, char* argv[])
{
    if (argc <= 1) {
        cout << "ipv6udp 1 ro ipv6udp 2" << endl;
        exit(0);
    }

    if (argv[1][0] == '1') {
        send_to_ipaddr = (char*)B_REVC_IP_ADDR;
        send_to_port = B_REVC_PORT;
        revc_sock_ipaddr = (char*)A_REVC_IP_ADDR;
        revc_sock_port = A_REVC_PORT;
        myName = (char*)A_NAME;
    } else {
        send_to_ipaddr = (char*)A_REVC_IP_ADDR;
        send_to_port = A_REVC_PORT;
        revc_sock_ipaddr = (char*)B_REVC_IP_ADDR;
        revc_sock_port = B_REVC_PORT;
        myName = (char*)B_NAME;
    }

    // Winsock DLL の使用を開始する
    WORD ver = MAKEWORD(2, 2);
    WSADATA wsa;
    if (WSAStartup(ver, &wsa) != 0) {
        cout << "WSAStartup error" << endl;
        exit(-1);
    }

    // 送信先のアドレス構造体初期化
    memset(&send_sockAddrIn, 0, sizeof(send_sockAddrIn));
    send_sockAddrIn.sin6_port = htons(send_to_port);
    send_sockAddrIn.sin6_family = AF_INET6;
    inet_pton(AF_INET6, send_to_ipaddr, &send_sockAddrIn.sin6_addr);

    // 送信ソケットを作成する
    sockSend = socket(AF_INET6, SOCK_DGRAM, 0);
    if (INVALID_SOCKET == sockSend) {
        cout << "送信ソケット作成失敗" << endl;
        WSACleanup();
        exit(-1);
    }

    // 受信ソケットのアドレス構造体初期化
    memset(&recv_sockAddrIn, 0, sizeof(recv_sockAddrIn));
    recv_sockAddrIn.sin6_port = htons(revc_sock_port);
    recv_sockAddrIn.sin6_family = AF_INET6;
    inet_pton(AF_INET6, revc_sock_ipaddr, &recv_sockAddrIn.sin6_addr);

    // 受信ソケットを作成する
    sockRecv = socket(AF_INET6, SOCK_DGRAM, 0);
    if (INVALID_SOCKET == sockRecv) {
        cout << "受信ソケット作成失敗" << endl;
        WSACleanup();
        exit(-1);
    }

    // 受信ソケットのアドレス構造体をバインドする
    if (SOCKET_ERROR == 
    	bind(sockRecv, (const struct sockaddr*)&recv_sockAddrIn, sizeof(recv_sockAddrIn)))
    {
        closesocket(sockRecv);
        cout << "受信ソケットバインド失敗" << endl;
        WSACleanup();
        exit(-1);
    }

    // 受信スレッドを立ち上げる
    CreateAndStartReceiveThread();

    // メインルール=送信ループ
    // キーボードから文字列を入力して、送信ソケット(sockSend)にて相手に送信する
    // "end"を入力したら、相手へ送信して、ループを抜ける
    while (1) {
        string textstring;
        cout << myName << ": ";
        cin >> textstring;

        if (g_bRunning == 0) {
            break;
        }

        // キーボードから入力した文字列を自分の名前につけてから相手へ送信する
        char buff[1024];
        strcpy(buff, myName);
        strcat(buff, ":");
        strcat(buff, textstring.c_str());
        sendto(sockSend, buff, strlen(buff) + 1, 0, 
        	(const struct sockaddr*)&send_sockAddrIn, sizeof(send_sockAddrIn));

        if (textstring == "end") {
            Sleep(1000);
            break;
        }
    }

    // 受信スレッドを終了する
    StopReceiveThread();

    // 送信ソケットを閉じる
    closesocket(sockSend);

    // WS2_32.dllの使用を終了する
    WSACleanup();
    cout << "goodbye!\n";
}