ちょっとしたTCP/IP, UDP/IP通信でいろいろテストしたいときのコードテンプレ(Windows/Linux両方コピペだけでビルド可能!)
はじめに
ノード間でTCP/IPおよびUDP/IP通信が通るかどうかをチェックするツールです。
注意事項はこちらです。
- 個人的なテスト用に作られたモノです
- コピって適当に改造して使いましょう
- エラー処理は無いに等しいです
- アドレスは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; }