結果だけでなく過程も見てください

たい焼きさんの日々の奮闘を綴る日記です。

ちょっとしたTCP/IP, UDP/IP通信でいろいろテストしたいときのコードテンプレ(Windows/Linux両方コピペだけでビルド可能!)

はじめに

ノード間でTCP/IPおよびUDP/IP通信が通るかどうかをチェックするツールです。

  • 1つの実行ファイルにサーバー/クライアント両方の機能が入っているので、双方向の確認が可能です。
  • WindowsLinux両方でビルドできるので、プラットフォームを越えてのチェックが可能です。

注意事項はこちらです。

  • 個人的なテスト用に作られたモノです
  • コピって適当に改造して使いましょう
  • エラー処理は無いに等しいです
  • アドレスはIPv4しか使えません
  • 一部機能が未実装です
  • 2時間くらいでとりあえず上げたものです。徐々に修正していきます(これいつもの放置するやつや!)。

ビルド

Windows
  • Visual Studio Community 2015以降で動作確認済みです。
  • 追加のライブラリとして「ws2_32.lib」を追加しといてください。(プロジェクトのプロパティ→リンカー→入力→追加の依存ファイル)
  • 「WIN32」というマクロを定義しておいてください。(プロジェクトのプロパティ→C/C++プリプロセッサプリプロセッサの定義) ※昔はデフォルトで定義されてたのに、最近のはないんですね…
  • SDLが有効な場合は、無効にしてください。(プロジェクトのプロパティ→C/C++→全般→SDLチェックを「いいえ」にする)

あとは普通にビルド一発です。

Linux

gcc 4.8.5, 8.3.1(Redhat系), 9.3.0(Ubuntu)で動作確認済みです。
まぁ何でも適当で大丈夫だと思います。以下を実行してください。

gcc ./main.cpp -o main -lstdc++ -lpthread

以下のようなエラーが出た場合は、追加パッケージをインストールしてください。

  • Redhat系の場合は「gcc-c++」パッケージ (yum install gcc-c++)
  • Ubuntuの場合は「g++」パッケージ (sudo apt-get install g++) ※gccとg++のバージョンは合わせる必要があるようです。
gcc: error trying to exec 'cc1plus': execvp: そのようなファイルやディレクトリはありません

使用方法

サーバー
実行ファイル server <プロトコル種別> <リッスンするIPアドレス> <リッスンするポート>

■例
./main server tcp 192.168.1.10 19876
クライアント
実行ファイル client <プロトコル種別> <接続先IPアドレス> <接続先ポート> <接続元IPアドレス> <接続元ポート> <サーバーに送るメッセージ>

■例
./main client tcp 192.168.1.10 19876 0 0 ahanufun

※接続元のIPアドレスやポート番号に0を指定すると、OS(プロトコルスタック)が自動で選んだIPアドレス/ポート番号が選ばれます。

ソースコード (ここをコピペすべし!)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>

#ifdef WIN32
#include <WinSock2.h>
#include <Windows.h>
#include <process.h>
#else
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pthread.h>
#endif


#ifdef WIN32
#define HERROR                          WSAGetLastError()
#define SAFE_SOCK_CLOSE(sock)           if((sock) != INVALID_SOCKET){ closesocket((sock)); (sock)=INVALID_SOCKET; }
#define SOCK_TYPE                       SOCKET
#define SOCKLEN_TYPE                    int
#define SLEEP_SEC(sec)                  Sleep((sec) * 1000)
#define SET_SOCKADDR_IPV4(addr, value)  (addr).sin_addr.S_un.S_addr = (value)
#else
#define HERROR                          h_errno
#define INVALID_SOCKET                  (-1)
#define SAFE_SOCK_CLOSE(sock)           if((sock) != INVALID_SOCKET){ close((sock)); (sock)=INVALID_SOCKET; }
#define SOCK_TYPE                       int
#define SOCKLEN_TYPE                    socklen_t
#define SLEEP_SEC(sec)                  sleep((sec))
#define SET_SOCKADDR_IPV4(addr, value)  (addr).sin_addr.s_addr = (value)
#endif

#define SAFE_DELETE(p)  if(p){ delete(p); (p)=NULL; }


const char* errno_str()
{
    switch (errno)
    {
    case 1:   return "EPERM";
    case 2:   return "ENOENT";
    case 3:   return "ESRCH";
    case 4:   return "EINTR";
    case 5:   return "EIO";
    case 6:   return "ENXIO";
    case 7:   return "E2BIG";
    case 8:   return "ENOEXEC";
    case 9:   return "EBADF";
    case 10:  return "ECHILD";
    case 11:  return "EAGAIN or EWOULDBLOCK";
    case 12:  return "ENOMEM";
    case 13:  return "EACCES";
    case 14:  return "EFAULT";
    case 15:  return "ENOTBLK";
    case 16:  return "EBUSY";
    case 17:  return "EEXIST";
    case 18:  return "EXDEV";
    case 19:  return "ENODEV";
    case 20:  return "ENOTDIR";
    case 21:  return "EISDIR";
    case 22:  return "EINVAL";
    case 23:  return "ENFILE";
    case 24:  return "EMFILE";
    case 25:  return "ENOTTY";
    case 26:  return "ETXTBSY";
    case 27:  return "EFBIG";
    case 28:  return "ENOSPC";
    case 29:  return "ESPIPE";
    case 30:  return "EROFS";
    case 31:  return "EMLINK";
    case 32:  return "EPIPE";
    case 33:  return "EDOM";
    case 34:  return "ERANGE";
    case 35:  return "EDEADLK or EDEADLOCK";
    case 36:  return "ENAMETOOLONG";
    case 37:  return "ENOLCK";
    case 38:  return "ENOSYS";
    case 39:  return "ENOTEMPTY";
    case 40:  return "ELOOP";
    case 42:  return "ENOMSG";
    case 43:  return "EIDRM";
    case 44:  return "ECHRNG";
    case 45:  return "EL2NSYNC";
    case 46:  return "EL3HLT";
    case 47:  return "EL3RST";
    case 49:  return "EUNATCH";
    case 51:  return "EL2HLT";
    case 52:  return "EBADE";
    case 53:  return "EBADR";
    case 54:  return "EXFULL";
    case 56:  return "EBADRQC";
    case 57:  return "EBADSLT";
    case 60:  return "ENOSTR";
    case 61:  return "ENODATA";
    case 62:  return "ETIME";
    case 63:  return "ENOSR";
    case 64:  return "ENONET";
    case 65:  return "ENOPKG";
    case 66:  return "EREMOTE";
    case 67:  return "ENOLINK";
    case 70:  return "ECOMM";
    case 71:  return "EPROTO";
    case 72:  return "EMULTIHOP";
    case 74:  return "EBADMSG";
    case 75:  return "EOVERFLOW";
    case 76:  return "ENOTUNIQ";
    case 77:  return "EBADFD";
    case 78:  return "EREMCHG";
    case 79:  return "ELIBACC";
    case 80:  return "ELIBBAD";
    case 81:  return "ELIBSCN";
    case 82:  return "ELIBMAX";
    case 83:  return "ELIBEXEC";
    case 84:  return "EILSEQ";
    case 85:  return "ERESTART";
    case 86:  return "ESTRPIPE";
    case 87:  return "EUSERS";
    case 88:  return "ENOTSOCK";
    case 89:  return "EDESTADDRREQ";
    case 90:  return "EMSGSIZE";
    case 91:  return "EPROTOTYPE";
    case 92:  return "ENOPROTOOPT";
    case 93:  return "EPROTONOSUPPORT";
    case 94:  return "ESOCKTNOSUPPORT";
    case 95:  return "ENOTSUP or EOPNOTSUPP";
    case 96:  return "EPFNOSUPPORT";
    case 97:  return "EAFNOSUPPORT";
    case 98:  return "EADDRINUSE";
    case 99:  return "EADDRNOTAVAIL";
    case 100: return "ENETDOWN";
    case 101: return "ENETUNREACH";
    case 102: return "ENETRESET";
    case 103: return "ECONNABORTED";
    case 104: return "ECONNRESET";
    case 105: return "ENOBUFS";
    case 106: return "EISCONN";
    case 107: return "ENOTCONN";
    case 108: return "ESHUTDOWN";
    case 110: return "ETIMEDOUT";
    case 111: return "ECONNREFUSED";
    case 112: return "EHOSTDOWN";
    case 113: return "EHOSTUNREACH";
    case 114: return "EALREADY";
    case 115: return "EINPROGRESS";
    case 116: return "ESTALE";
    case 117: return "EUCLEAN";
    case 120: return "EISNAM";
    case 121: return "EREMOTEIO";
    case 122: return "EDQUOT";
    case 123: return "ENOMEDIUM";
    case 124: return "EMEDIUMTYPE";
    case 125: return "ECANCELED";
    case 126: return "ENOKEY";
    case 127: return "EKEYEXPIRED";
    case 128: return "EKEYREVOKED";
    case 129: return "EKEYREJECTED";
    default:  return "UNKNOWN";
    }
}


const char* getLocalTimeStr(time_t curTime, char* pszResultTime)
{
    struct tm* pt = localtime(&curTime);

    sprintf(pszResultTime, "%02d/%02d/%02d %02d:%02d:%02d", pt->tm_year + 1900, pt->tm_mon + 1, pt->tm_mday, pt->tm_hour, pt->tm_min, pt->tm_sec);

    return pszResultTime;
}


class NetworkInfo
{
public:

    ~NetworkInfo()
    {

#ifdef WIN32
        if (bWSASetup)
        {
            WSACleanup();
            bWSASetup = false;
        }
#endif

        SAFE_SOCK_CLOSE(sock0);
    }

#ifdef WIN32
    WSADATA             wsaData;
    bool                bWSASetup;
#endif

    /* listening socket */
    SOCK_TYPE           sock0;

    /* サーバーならリッスンするアドレス情報     */
    /* クライアントなら送信先アドレス情報とする */
    struct sockaddr_in  addr;

    /* オプション。クライアントのときだけ使われる。クライアント側アドレスとポート。 */
    struct sockaddr_in  client_addr_forsend;


    NetworkInfo() :
#ifdef WIN32
        wsaData(),
        bWSASetup(false),
#endif
        sock0(INVALID_SOCKET),
        addr(),
        client_addr_forsend()
    {
    }

};


/* サーバーかクライアントの種別列挙子 */
enum NodeType
{
    eNodeTypeUnknown = 0,
    eServer,
    eClient
};

static const char* NODE_SERVER = "server";
static const char* NODE_CLIENT = "client";

/* TCPかUDPの種別列挙子 */
enum ProtocolType
{
    eProtocolTypeUnknown = 0,
    eProtocolTCP,
    eProtocolUDP
};

static const char* PROTOCOL_TCP = "tcp";
static const char* PROTOCOL_UDP = "udp";


/* 受信関数で必要な情報 */
struct RecvInfo_t
{
    /* 受信処理で使用するソケット */
    SOCK_TYPE  recvSock_;

    RecvInfo_t(SOCK_TYPE recvSock) :
        recvSock_(recvSock)
    {
    }
};


/* 受信関数。TCPの場合だけ使われます。 */
#ifdef WIN32
unsigned __stdcall recvThread(void *p)
#else
void* recvThread(void *p)
#endif
{
    RecvInfo_t* pRecvInfo = (RecvInfo_t *)p;

    char  inbuf[2048];   /* 受信バッファ     */
    char  atimeWk[64];   /* 時刻表示ワーク用 */

    memset(inbuf, 0, sizeof(inbuf));
    memset(atimeWk, 0, sizeof(atimeWk));

    /* 情報を取得してメモリ解放しておく */
    SOCK_TYPE sock = pRecvInfo->recvSock_;
    SAFE_DELETE(pRecvInfo);

    /* 受信処理。同期とする。出力結果が混ざって表示されるが気にしない */
    recv(sock, inbuf, sizeof(inbuf), 0);
    printf("%s Received Message:%s\n", getLocalTimeStr(time(NULL), atimeWk), inbuf);

    SAFE_SOCK_CLOSE(sock);

#ifdef WIN32
    return 0;
#else
    pthread_exit(NULL);
#endif
}


void printUsage()
{
    printf("Usage:\n");
    printf("   server {tcp|udp} listening_addr listening_port\n");
    printf("   or\n");
    printf("   client {tcp|udp} dest_addr dest_port src_addr src_port message\n");
    printf("\n");
    printf("Example:\n");
    printf("   1. server tcp 192.168.1.5 12345\n");
    printf("      client tcp 192.168.1.5 12345 0 0 sample_to_12345\n");
    printf("\n");
    printf("   2. server udp 192.168.1.5 12345\n");
    printf("      client udp 192.168.1.5 12345 192.168.1.2 5000 sample_from_5000_to_12345\n");
}


/*
* me.exe <NodeType> <ProtocolType> <listening addr> <listening port>
*      or
* me.exe <NodeType> <ProtocolType> <dest addr> <dest port> <src addr> <src port> <msg>
*
* <NodeType> "server" or "client"
* <ProtocolType> "tcp" or "udp"
*
* Only IPv4.
*/
int main(int argc, char* argv[])
{
    if (argc <= 1)
    {
        fprintf(stderr, "Error: Bad args num.\n");
        printUsage();
        return 1;
    }

    NodeType eNodeType = eNodeTypeUnknown;

    if (strcmp(argv[1], NODE_SERVER) == 0)
    {
        eNodeType = eServer;

        if (argc != 5)
        {
            fprintf(stderr, "Error: Bad args num(Server)(%d).\n", argc);
            printUsage();
            return 1;
        }
    }
    else if (strcmp(argv[1], NODE_CLIENT) == 0)
    {
        eNodeType = eClient;

        if (argc != 8)
        {
            fprintf(stderr, "Error: Bad args num(Client)(%d).\n", argc);
            printUsage();
            return 1;
        }
    }
    else
    {
        fprintf(stderr, "Error: Input correct NodeType(server or client).\n");
        printUsage();
        return 2;
    }


    ProtocolType eProtocolType = eProtocolTypeUnknown;

    if (strcmp(argv[2], PROTOCOL_TCP) == 0)
    {
        eProtocolType = eProtocolTCP;
    }
    else if (strcmp(argv[2], PROTOCOL_UDP) == 0)
    {
        eProtocolType = eProtocolUDP;
    }
    else
    {
        fprintf(stderr, "Error: Input correct ProtocolType(tcp or udp).\n");
        printUsage();
        return 2;
    }


    int          nFuncRet = 0;   /* Function return code */
    NetworkInfo  netInfo;
    char         atimeWk[64];

    memset(atimeWk, 0, sizeof(atimeWk));


#ifdef WIN32
    nFuncRet = WSAStartup(MAKEWORD(2, 0), &(netInfo.wsaData));
    netInfo.bWSASetup = true;
    if (nFuncRet != 0)
    {
        fprintf(stderr, "%s WSAStartup error(%d)(h_errno:%d)(errno:%d(%s))\n", getLocalTimeStr(time(NULL), atimeWk), nFuncRet, HERROR, errno, errno_str());
        return 3;
    }
#endif


    if (eNodeType == eServer)
    {
        /* argv */
        /* [1]:ノード種別       */
        /* [2]:プロトコル種別   */
        /* [3]:リッスンアドレス */
        /* [4]:リッスンポート   */

        const char* pszAddr     = argv[3];
        const char* pszPortNum  = argv[4];


        /* リッスンソケットの作成 */
        netInfo.sock0 = socket(AF_INET, (eProtocolType == eProtocolTCP) ? SOCK_STREAM : SOCK_DGRAM, 0);
        if (netInfo.sock0 == INVALID_SOCKET)
        {
            fprintf(stderr, "%s listen socket error(h_errno:%d)(errno:%d(%s))\n", getLocalTimeStr(time(NULL), atimeWk), HERROR, errno, errno_str());
            return 4;
        }

        /* バインドリッスンするアドレスとポート設定 */
        netInfo.addr.sin_family = AF_INET;
        netInfo.addr.sin_port = htons(atoi(pszPortNum));    /* リッスンポート TODO:引数を信じろ! */
        SET_SOCKADDR_IPV4(netInfo.addr, inet_addr(pszAddr));  /* バインドアドレス TODO:引数を信じろ! */


        /* サーバーの場合はポート再利用可能としておく */
        {
            int nYes = 1;
            setsockopt(netInfo.sock0, SOL_SOCKET, SO_REUSEADDR, (const char *)&nYes, sizeof(nYes));
        }


        nFuncRet = bind(netInfo.sock0, (struct sockaddr *)&(netInfo.addr), sizeof(netInfo.addr));
        if (nFuncRet == -1)
        {
            fprintf(stderr, "%s bind(recv) error(h_errno:%d)(errno:%d(%s))\n", getLocalTimeStr(time(NULL), atimeWk), HERROR, errno, errno_str());
            return 8;
        }


        /** TCPの場合だけリッスンする */
        if (eProtocolType == eProtocolTCP)
        {
            nFuncRet = listen(netInfo.sock0, 5);  /* backlogは5 */
            if (nFuncRet == -1)
            {
                fprintf(stderr, "%s listen error(h_errno:%d)(errno:%d(%s))\n", getLocalTimeStr(time(NULL), atimeWk), HERROR, errno, errno_str());
                return 9;
            }
        }


        /* recv loop */
        while (1)
        {
            struct sockaddr_in  client = { 0 };
            SOCKLEN_TYPE        nClientLen = sizeof(client);
            
            if (eProtocolType == eProtocolTCP)
            {
                int sock = INVALID_SOCKET;

                /** TCPの場合だけacceptで待ち受けする */
                sock = accept(netInfo.sock0, (struct sockaddr *)&client, &nClientLen);
                if (sock == INVALID_SOCKET)
                {
                    fprintf(stderr, "%s accept error(h_errno:%d)(errno:%d(%s))\n", getLocalTimeStr(time(NULL), atimeWk), HERROR, errno, errno_str());
                    continue;
                }

                /* TCPの場合、acceptと受信処理は分離したいため、受信用スレッドを立ち上げる */
                RecvInfo_t* pRecvInfo = NULL;
                try
                {
                    pRecvInfo = new RecvInfo_t(sock);
                }
                catch (...)
                {
                    /* bad memory alloc */
                    SAFE_SOCK_CLOSE(sock);
                    continue;
                }

#ifdef WIN32
                unsigned int thID = 0;

                HANDLE hThread = (HANDLE)_beginthreadex(NULL,
                    0,
                    recvThread,
                    pRecvInfo,
                    0  /*CREATE_SUSPENDED*/,
                    &thID);
                if (hThread == 0)
                {
                    fprintf(stderr, "%s _beginthreadex error(h_errno:%d)(errno:%d(%s))\n", getLocalTimeStr(time(NULL), atimeWk), HERROR, errno, errno_str());

                    SAFE_SOCK_CLOSE(sock);
                    continue;
                }

                /* Windowsではデフォルトでpthread_detach相当の設定になっている(たぶん) */
#else
                pthread_t th = 0;

                nFuncRet = pthread_create(&th, NULL, recvThread, (void *)pRecvInfo);
                if (nFuncRet != 0)
                {
                    fprintf(stderr, "%s pthread_create error(h_errno:%d)(errno:%d(%s))\n", getLocalTimeStr(time(NULL), atimeWk), HERROR, errno, errno_str());

                    SAFE_SOCK_CLOSE(sock);
                    continue;
                }

                (void)pthread_detach(th);
#endif
            }
            else
            {
                /** UDPの場合、acceptがないため、recvでブロッキングするような処理にする */
                char  inbuf[2048];   /* 受信バッファ     */
                char  atimeWk[64];   /* 時刻表示ワーク用 */

                memset(inbuf, 0, sizeof(inbuf));
                memset(atimeWk, 0, sizeof(atimeWk));

                /* 受信処理。同期とする。出力結果が混ざって表示されるが気にしない */
                recv(netInfo.sock0, inbuf, sizeof(inbuf), 0);
                printf("%s Received Message:%s\n", getLocalTimeStr(time(NULL), atimeWk), inbuf);
            }
        }

    }
    else
    {
        /* argv */
        /* [1]:ノード種別       */
        /* [2]:プロトコル種別   */
        /* [3]:接続先アドレス   */
        /* [4]:接続先ポート     */
        /* [5]:接続元アドレス   */
        /* [6]:接続元ポート     */
        /* [7]:メッセージ       */

        const char* pszDestAddr = argv[3];
        const char* pszDestPortNum = argv[4];
        const char* pszSrcAddr = argv[5];
        const char* pszSrcPortNum = argv[6];
        const char* pszMessage = argv[7];


        int   sock = 0;
        char  sendmsg[256];

        memset(sendmsg, 0, sizeof(sendmsg));

        /* 接続先アドレス情報 */
        netInfo.addr.sin_family = AF_INET;
        netInfo.addr.sin_port = htons(atoi(pszDestPortNum));       /* TODO:引数を信じて! */
        SET_SOCKADDR_IPV4(netInfo.addr, inet_addr(pszDestAddr));   /* TODO:引数を信じて! */


        /* 接続元アドレス情報 */
        netInfo.client_addr_forsend.sin_family = AF_INET;
        netInfo.client_addr_forsend.sin_port = htons(atoi(pszSrcPortNum));    /* TODO:引数を信じて! */
        SET_SOCKADDR_IPV4(netInfo.client_addr_forsend, inet_addr(pszSrcAddr));  /* TODO:引数を信じて! */


        sock = socket(AF_INET, (eProtocolType == eProtocolTCP) ? SOCK_STREAM : SOCK_DGRAM, 0);
        if (sock == INVALID_SOCKET)
        {
            fprintf(stderr, "%s socket(send) h_errno:%d errno:%d(%s)\n", getLocalTimeStr(time(NULL), atimeWk), HERROR, errno, errno_str());

            return 10;
        }


        /* TODO:将来的にクライアントの場合のポート再利用可能は設定するかしないか設定できるようにしたい */
        {
            int nYes = 1;
            setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&nYes, sizeof(nYes));
        }


        /* TODO:将来的に接続元アドレスを必ずbindするかどうかはオプション化したい */
        nFuncRet = bind(sock, (struct sockaddr *)&(netInfo.client_addr_forsend), sizeof(netInfo.client_addr_forsend));
        if (nFuncRet == -1)
        {
            fprintf(stderr, "%s bind(send) error(h_errno:%d)(errno:%d(%s))\n", getLocalTimeStr(time(NULL), atimeWk), HERROR, errno, errno_str());

            return 11;
        }


        memset(sendmsg, 0, sizeof(sendmsg));
        strncpy(sendmsg, pszMessage, sizeof(sendmsg) - 1);


        if (eProtocolType == eProtocolTCP)
        {
            /** TCPの場合だけ、コネクションを確立させる */
            nFuncRet = connect(sock, (struct sockaddr *)&(netInfo.addr), sizeof(netInfo.addr));
            if (nFuncRet == -1)
            {
                fprintf(stderr, "%s connect error(h_errno:%d)(errno:%d(%s))\n", getLocalTimeStr(time(NULL), atimeWk), HERROR, errno, errno_str());

                return 12;
            }

            (void)send(sock, sendmsg, (int)strlen(sendmsg), 0);
        }
        else
        {
            /** UDPの場合はsendtoするだけ */
            (void)sendto(sock, sendmsg, (int)strlen(sendmsg), 0, (struct sockaddr *)&netInfo.addr, sizeof(netInfo.addr));
        }

        SAFE_SOCK_CLOSE(sock);
    }


    return 0;
}
プライバシーポリシー お問い合わせ