C++で実装するTCPソケットを使ったデータ通信の方法

C++でのTCPソケットを用いたデータ通信の基本と応用方法を解説します。本記事では、TCPソケットの基礎から具体的な実装方法、さらに応用例としてチャットアプリやファイル転送の実装方法までを詳しく説明します。TCPソケットは、ネットワークプログラミングにおいて非常に重要な役割を果たしており、クライアントとサーバー間の信頼性の高いデータ通信を実現するために不可欠です。初心者から中級者まで、幅広い読者に対応できる内容となっております。

目次

TCPソケットとは

TCPソケットは、ネットワーク上でデータを送受信するための通信端点です。TCP(Transmission Control Protocol)は信頼性の高い通信を保証するプロトコルであり、データの順序や誤りを確認しながら通信を行います。ソケットは、このTCP通信を実現するためのインターフェースで、サーバーとクライアント間の通信チャネルを確立します。具体的には、ソケットを用いて接続を確立し、データの送受信を行い、最後に接続を終了する流れとなります。

環境設定と準備

C++でTCPソケットプログラミングを行うためには、開発環境の設定と必要なライブラリのインストールが必要です。以下の手順で準備を進めます。

開発環境のセットアップ

まず、C++開発環境を整えます。Visual Studio、GCC、ClangなどのC++コンパイラを使用することが一般的です。WindowsユーザーはVisual Studio、LinuxやmacOSユーザーはGCCやClangを使用することを推奨します。

ソケットプログラミング用ライブラリのインストール

C++でソケットプログラミングを行うには、以下のライブラリを使用します:

  • Windows:Windows Sockets API(Winsock)
  • Linux/macOS:Berkeley Sockets API

ライブラリのインストール方法は以下の通りです:

Windowsの場合

Visual StudioにはWinsockライブラリが標準で付属しているため、追加のインストールは不要です。ただし、プロジェクト設定でWs2_32.libをリンクする必要があります。

Linux/macOSの場合

GCCやClangを使用する場合、Berkeley Sockets APIは標準ライブラリに含まれています。特別なインストールは不要ですが、ビルド時に-lsocketフラグを指定します。

これで、TCPソケットを用いたデータ通信プログラムの作成準備が整いました。次のステップでは、実際にソケットの作成方法について解説します。

ソケットの作成

C++でTCPソケットを作成する方法について解説します。ソケットの作成は、通信を行うための最初のステップです。

ソケットの作成手順

ソケットを作成するには、まずsocket()関数を使用します。この関数は、ソケットディスクリプタを返し、通信に使用するプロトコルファミリ、ソケットタイプ、およびプロトコルを指定します。

ソケットの作成コード例(Windows)

#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock == INVALID_SOCKET) {
        // エラーハンドリング
    }

    // 使用後は必ずソケットをクローズ
    closesocket(sock);
    WSACleanup();
    return 0;
}

ソケットの作成コード例(Linux/macOS)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

int main() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        // エラーハンドリング
    }

    // 使用後は必ずソケットをクローズ
    close(sock);
    return 0;
}

パラメータの説明

  • AF_INET:IPv4アドレスファミリを指定します。
  • SOCK_STREAM:TCPプロトコルを指定します。
  • IPPROTO_TCP:明示的にTCPプロトコルを指定します(Linux/macOSでは省略可能)。

これで、基本的なソケットの作成が完了しました。次は、サーバーの実装方法について説明します。

サーバーの実装

C++でTCPサーバーを実装する方法について解説します。サーバーはクライアントからの接続を待ち受け、データを送受信する役割を担います。

サーバーの実装手順

サーバーの実装には以下の手順を踏みます:

  1. ソケットの作成
  2. アドレスのバインド
  3. 接続の待ち受け
  4. 接続の受け入れ
  5. データの送受信
  6. ソケットのクローズ

サーバーの実装コード例(Windows)

#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listenSock == INVALID_SOCKET) {
        // エラーハンドリング
    }

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(8080);

    if (bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        // エラーハンドリング
    }

    if (listen(listenSock, SOMAXCONN) == SOCKET_ERROR) {
        // エラーハンドリング
    }

    SOCKET clientSock = accept(listenSock, NULL, NULL);
    if (clientSock == INVALID_SOCKET) {
        // エラーハンドリング
    }

    char buffer[1024];
    int bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0);
    if (bytesReceived > 0) {
        send(clientSock, buffer, bytesReceived, 0);
    }

    closesocket(clientSock);
    closesocket(listenSock);
    WSACleanup();
    return 0;
}

サーバーの実装コード例(Linux/macOS)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>

int main() {
    int listenSock = socket(AF_INET, SOCK_STREAM, 0);
    if (listenSock < 0) {
        // エラーハンドリング
    }

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(8080);

    if (bind(listenSock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
        // エラーハンドリング
    }

    if (listen(listenSock, SOMAXCONN) < 0) {
        // エラーハンドリング
    }

    int clientSock = accept(listenSock, NULL, NULL);
    if (clientSock < 0) {
        // エラーハンドリング
    }

    char buffer[1024];
    int bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0);
    if (bytesReceived > 0) {
        send(clientSock, buffer, bytesReceived, 0);
    }

    close(clientSock);
    close(listenSock);
    return 0;
}

パラメータと関数の説明

  • bind():ソケットにIPアドレスとポート番号を割り当てます。
  • listen():ソケットを接続待ち状態にします。
  • accept():接続要求を受け入れ、新しいソケットを作成します。
  • recv():データを受信します。
  • send():データを送信します。

これで、基本的なTCPサーバーの実装が完了しました。次は、クライアントの実装方法について説明します。

クライアントの実装

C++でTCPクライアントを実装する方法について解説します。クライアントはサーバーに接続し、データを送受信する役割を担います。

クライアントの実装手順

クライアントの実装には以下の手順を踏みます:

  1. ソケットの作成
  2. サーバーへの接続
  3. データの送受信
  4. ソケットのクローズ

クライアントの実装コード例(Windows)

#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET clientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (clientSock == INVALID_SOCKET) {
        // エラーハンドリング
    }

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);

    if (connect(clientSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        // エラーハンドリング
    }

    const char* sendData = "Hello, Server!";
    send(clientSock, sendData, strlen(sendData), 0);

    char buffer[1024];
    int bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0);
    if (bytesReceived > 0) {
        // 受信データの処理
    }

    closesocket(clientSock);
    WSACleanup();
    return 0;
}

クライアントの実装コード例(Linux/macOS)

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

int main() {
    int clientSock = socket(AF_INET, SOCK_STREAM, 0);
    if (clientSock < 0) {
        // エラーハンドリング
    }

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);

    if (connect(clientSock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
        // エラーハンドリング
    }

    const char* sendData = "Hello, Server!";
    send(clientSock, sendData, strlen(sendData), 0);

    char buffer[1024];
    int bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0);
    if (bytesReceived > 0) {
        // 受信データの処理
    }

    close(clientSock);
    return 0;
}

パラメータと関数の説明

  • inet_pton():IPアドレスをテキスト形式からバイナリ形式に変換します。
  • connect():サーバーに接続します。
  • send():データを送信します。
  • recv():データを受信します。

これで、基本的なTCPクライアントの実装が完了しました。次は、サーバーとクライアントの接続方法について解説します。

サーバーとクライアントの接続

サーバーとクライアントの接続方法について詳しく解説します。これまでに説明したサーバーとクライアントの実装を組み合わせて、実際に接続を確立し、データ通信を行います。

接続の確立

サーバーとクライアントは、次の手順で接続を確立します:

  1. サーバーを起動して、接続待ち状態にします。
  2. クライアントを起動して、サーバーに接続します。
  3. サーバーはクライアントの接続要求を受け入れます。
  4. クライアントとサーバーの間でデータを送受信します。

サーバー側のコード

以下のコードは、サーバーがクライアントの接続を受け入れ、データを受信してそのまま返す(エコー)動作を行います。

#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listenSock == INVALID_SOCKET) {
        // エラーハンドリング
    }

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(8080);

    bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr));
    listen(listenSock, SOMAXCONN);

    SOCKET clientSock = accept(listenSock, NULL, NULL);

    char buffer[1024];
    int bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0);
    if (bytesReceived > 0) {
        send(clientSock, buffer, bytesReceived, 0);
    }

    closesocket(clientSock);
    closesocket(listenSock);
    WSACleanup();
    return 0;
}

クライアント側のコード

以下のコードは、クライアントがサーバーに接続してメッセージを送信し、サーバーからの応答を受信します。

#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET clientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (clientSock == INVALID_SOCKET) {
        // エラーハンドリング
    }

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);

    connect(clientSock, (sockaddr*)&serverAddr, sizeof(serverAddr));

    const char* sendData = "Hello, Server!";
    send(clientSock, sendData, strlen(sendData), 0);

    char buffer[1024];
    int bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0);
    if (bytesReceived > 0) {
        // 受信データの処理
    }

    closesocket(clientSock);
    WSACleanup();
    return 0;
}

接続と通信の流れ

  1. サーバーはlisten()関数で接続待ち状態にし、accept()関数でクライアントからの接続を受け入れます。
  2. クライアントはconnect()関数を使ってサーバーに接続します。
  3. クライアントはデータをsend()関数でサーバーに送信し、サーバーはrecv()関数でデータを受信します。
  4. サーバーは受信したデータをそのままsend()関数でクライアントに返します(エコー)。
  5. クライアントはサーバーからの応答をrecv()関数で受信します。

これで、サーバーとクライアントの接続方法と基本的なデータ通信が理解できました。次は、具体的なデータ送受信の方法について詳しく説明します。

データ送受信の方法

サーバーとクライアント間でデータを送受信する方法について詳しく解説します。データ送受信はTCPソケット通信の中心的な役割を担います。

データ送信

データを送信するには、send()関数を使用します。送信するデータとその長さを指定することで、サーバーまたはクライアントから相手にデータを送ることができます。

データ送信のコード例

const char* sendData = "Hello, Server!";
int bytesSent = send(sock, sendData, strlen(sendData), 0);
if (bytesSent == SOCKET_ERROR) {
    // エラーハンドリング
}

データ受信

データを受信するには、recv()関数を使用します。受信するバッファとそのサイズを指定することで、サーバーまたはクライアントからデータを受け取ることができます。

データ受信のコード例

char buffer[1024];
int bytesReceived = recv(sock, buffer, sizeof(buffer), 0);
if (bytesReceived > 0) {
    buffer[bytesReceived] = '\0'; // 受信データを文字列として扱うための終端文字
    // 受信データの処理
} else if (bytesReceived == 0) {
    // 接続が終了された
} else {
    // エラーハンドリング
}

データ送受信の具体例

以下に、サーバーとクライアントがメッセージを送受信する具体例を示します。

サーバー側のコード

#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(8080);

    bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr));
    listen(listenSock, SOMAXCONN);

    SOCKET clientSock = accept(listenSock, NULL, NULL);
    char buffer[1024];
    int bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0);
    if (bytesReceived > 0) {
        send(clientSock, buffer, bytesReceived, 0);
    }

    closesocket(clientSock);
    closesocket(listenSock);
    WSACleanup();
    return 0;
}

クライアント側のコード

#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET clientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);

    connect(clientSock, (sockaddr*)&serverAddr, sizeof(serverAddr));
    const char* sendData = "Hello, Server!";
    send(clientSock, sendData, strlen(sendData), 0);

    char buffer[1024];
    int bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0);
    if (bytesReceived > 0) {
        buffer[bytesReceived] = '\0'; // 受信データを文字列として扱うための終端文字
        // 受信データの処理
    }

    closesocket(clientSock);
    WSACleanup();
    return 0;
}

データ送受信のポイント

  • 送信するデータのサイズに注意し、バッファオーバーフローを防ぐために適切なサイズを設定します。
  • 受信したデータは必ず終端文字を追加して文字列として処理します。
  • send()およびrecv()関数の戻り値を確認し、エラーハンドリングを適切に行います。

これで、サーバーとクライアント間のデータ送受信の基本的な方法が理解できました。次は、エラーハンドリングについて詳しく説明します。

エラーハンドリング

TCPソケットプログラミングでは、接続の確立やデータ送受信の際に様々なエラーが発生する可能性があります。これらのエラーを適切に処理することは、信頼性の高いアプリケーションを作成するために重要です。

エラーハンドリングの基本

各ソケット関数(socket()bind()listen()accept()connect()send()recv()など)は、エラーが発生した場合に特定のエラーコードを返します。これらのエラーコードを確認して、適切な処理を行います。

Windowsの場合

Windowsでは、WSAGetLastError()関数を使用して詳細なエラー情報を取得します。

if (socket() == INVALID_SOCKET) {
    int errorCode = WSAGetLastError();
    // エラーコードに応じた処理
}

Linux/macOSの場合

LinuxやmacOSでは、errno変数を確認して詳細なエラー情報を取得します。

if (socket() < 0) {
    int errorCode = errno;
    // エラーコードに応じた処理
}

一般的なエラーとその対処法

いくつかの一般的なエラーとその対処法を紹介します。

ソケット作成エラー

ソケットの作成に失敗した場合、INVALID_SOCKET(Windows)または負の値(Linux/macOS)が返されます。

SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {
    int errorCode = WSAGetLastError(); // Windowsの場合
    // エラーメッセージを表示して終了
}

バインドエラー

ソケットを特定のアドレスにバインドする際にエラーが発生した場合、SOCKET_ERRORが返されます。

if (bind(sock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
    int errorCode = WSAGetLastError(); // Windowsの場合
    // エラーメッセージを表示して終了
}

接続エラー

クライアントがサーバーに接続できなかった場合、SOCKET_ERRORが返されます。

if (connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
    int errorCode = WSAGetLastError(); // Windowsの場合
    // エラーメッセージを表示して終了
}

データ送受信エラー

データの送受信中にエラーが発生した場合、SOCKET_ERRORが返されます。

int bytesSent = send(sock, sendData, strlen(sendData), 0);
if (bytesSent == SOCKET_ERROR) {
    int errorCode = WSAGetLastError(); // Windowsの場合
    // エラーメッセージを表示して終了
}

int bytesReceived = recv(sock, buffer, sizeof(buffer), 0);
if (bytesReceived == SOCKET_ERROR) {
    int errorCode = WSAGetLastError(); // Windowsの場合
    // エラーメッセージを表示して終了
}

エラーハンドリングの実装例

以下は、エラーハンドリングを含めたサンプルコードです。

Windowsの場合

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#pragma comment(lib, "Ws2_32.lib")

void handleError(const char* errorMsg) {
    std::cerr << errorMsg << " Error Code: " << WSAGetLastError() << std::endl;
    WSACleanup();
    exit(1);
}

int main() {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        handleError("WSAStartup failed");
    }

    SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listenSock == INVALID_SOCKET) {
        handleError("Socket creation failed");
    }

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(8080);

    if (bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        handleError("Bind failed");
    }

    if (listen(listenSock, SOMAXCONN) == SOCKET_ERROR) {
        handleError("Listen failed");
    }

    SOCKET clientSock = accept(listenSock, NULL, NULL);
    if (clientSock == INVALID_SOCKET) {
        handleError("Accept failed");
    }

    char buffer[1024];
    int bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0);
    if (bytesReceived > 0) {
        send(clientSock, buffer, bytesReceived, 0);
    } else if (bytesReceived == SOCKET_ERROR) {
        handleError("Receive failed");
    }

    closesocket(clientSock);
    closesocket(listenSock);
    WSACleanup();
    return 0;
}

Linux/macOSの場合

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <cstring>
#include <cerrno>

void handleError(const char* errorMsg) {
    std::cerr << errorMsg << " Error Code: " << errno << std::endl;
    exit(1);
}

int main() {
    int listenSock = socket(AF_INET, SOCK_STREAM, 0);
    if (listenSock < 0) {
        handleError("Socket creation failed");
    }

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(8080);

    if (bind(listenSock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
        handleError("Bind failed");
    }

    if (listen(listenSock, SOMAXCONN) < 0) {
        handleError("Listen failed");
    }

    int clientSock = accept(listenSock, NULL, NULL);
    if (clientSock < 0) {
        handleError("Accept failed");
    }

    char buffer[1024];
    int bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0);
    if (bytesReceived > 0) {
        send(clientSock, buffer, bytesReceived, 0);
    } else if (bytesReceived < 0) {
        handleError("Receive failed");
    }

    close(clientSock);
    close(listenSock);
    return 0;
}

これで、エラーハンドリングの基本的な方法と実装例が理解できました。次は、TCPソケットを利用したチャットアプリの実装方法について解説します。

応用例:チャットアプリの実装

TCPソケットを利用した簡単なチャットアプリの実装方法を紹介します。このアプリでは、サーバーと複数のクライアントがリアルタイムでメッセージをやり取りすることができます。

チャットサーバーの実装

チャットサーバーは、複数のクライアントからの接続を受け入れ、メッセージをブロードキャストする役割を担います。

チャットサーバーのコード例(Windows)

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <vector>
#include <thread>
#pragma comment(lib, "Ws2_32.lib")

std::vector<SOCKET> clients;

void handleClient(SOCKET clientSock) {
    char buffer[1024];
    int bytesReceived;

    while ((bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0)) > 0) {
        buffer[bytesReceived] = '\0';
        for (const auto& client : clients) {
            if (client != clientSock) {
                send(client, buffer, bytesReceived, 0);
            }
        }
    }

    closesocket(clientSock);
}

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(8080);

    bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr));
    listen(listenSock, SOMAXCONN);

    while (true) {
        SOCKET clientSock = accept(listenSock, NULL, NULL);
        if (clientSock != INVALID_SOCKET) {
            clients.push_back(clientSock);
            std::thread(handleClient, clientSock).detach();
        }
    }

    closesocket(listenSock);
    WSACleanup();
    return 0;
}

チャットサーバーのコード例(Linux/macOS)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <vector>
#include <thread>
#include <cstring>
#include <cerrno>

std::vector<int> clients;

void handleClient(int clientSock) {
    char buffer[1024];
    int bytesReceived;

    while ((bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0)) > 0) {
        buffer[bytesReceived] = '\0';
        for (const auto& client : clients) {
            if (client != clientSock) {
                send(client, buffer, bytesReceived, 0);
            }
        }
    }

    close(clientSock);
}

int main() {
    int listenSock = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(8080);

    bind(listenSock, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    listen(listenSock, SOMAXCONN);

    while (true) {
        int clientSock = accept(listenSock, NULL, NULL);
        if (clientSock >= 0) {
            clients.push_back(clientSock);
            std::thread(handleClient, clientSock).detach();
        }
    }

    close(listenSock);
    return 0;
}

チャットクライアントの実装

チャットクライアントは、サーバーに接続してメッセージを送信し、他のクライアントからのメッセージを受信します。

チャットクライアントのコード例(Windows)

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <thread>
#pragma comment(lib, "Ws2_32.lib")

void receiveMessages(SOCKET clientSock) {
    char buffer[1024];
    int bytesReceived;

    while ((bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0)) > 0) {
        buffer[bytesReceived] = '\0';
        std::cout << "Received: " << buffer << std::endl;
    }
}

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET clientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);

    connect(clientSock, (sockaddr*)&serverAddr, sizeof(serverAddr));

    std::thread(receiveMessages, clientSock).detach();

    std::string message;
    while (true) {
        std::getline(std::cin, message);
        send(clientSock, message.c_str(), message.size(), 0);
    }

    closesocket(clientSock);
    WSACleanup();
    return 0;
}

チャットクライアントのコード例(Linux/macOS)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <thread>
#include <cstring>
#include <cerrno>

void receiveMessages(int clientSock) {
    char buffer[1024];
    int bytesReceived;

    while ((bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0)) > 0) {
        buffer[bytesReceived] = '\0';
        std::cout << "Received: " << buffer << std::endl;
    }
}

int main() {
    int clientSock = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);

    connect(clientSock, (struct sockaddr*)&serverAddr, sizeof(serverAddr));

    std::thread(receiveMessages, clientSock).detach();

    std::string message;
    while (true) {
        std::getline(std::cin, message);
        send(clientSock, message.c_str(), message.size(), 0);
    }

    close(clientSock);
    return 0;
}

チャットアプリの動作

  1. サーバーを起動してクライアントからの接続を待ち受けます。
  2. クライアントを起動してサーバーに接続します。
  3. クライアントがメッセージを送信すると、サーバーが受信して他のクライアントにブロードキャストします。
  4. 他のクライアントがメッセージを受信して表示します。

これで、TCPソケットを利用した基本的なチャットアプリの実装が理解できました。次は、ファイル転送の実装方法について解説します。

応用例:ファイル転送

TCPソケットを利用したファイル転送の実装方法を解説します。サーバーとクライアント間でファイルを送受信するアプリケーションを作成します。

ファイル転送サーバーの実装

ファイル転送サーバーは、クライアントからのファイル受信要求を受け取り、ファイルを受信して保存します。

ファイル転送サーバーのコード例(Windows)

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <fstream>
#pragma comment(lib, "Ws2_32.lib")

void receiveFile(SOCKET clientSock) {
    std::ofstream outFile("received_file.txt", std::ios::binary);
    char buffer[1024];
    int bytesReceived;

    while ((bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0)) > 0) {
        outFile.write(buffer, bytesReceived);
    }

    outFile.close();
    closesocket(clientSock);
}

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(8080);

    bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr));
    listen(listenSock, SOMAXCONN);

    while (true) {
        SOCKET clientSock = accept(listenSock, NULL, NULL);
        if (clientSock != INVALID_SOCKET) {
            std::thread(receiveFile, clientSock).detach();
        }
    }

    closesocket(listenSock);
    WSACleanup();
    return 0;
}

ファイル転送サーバーのコード例(Linux/macOS)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <fstream>
#include <thread>
#include <cstring>
#include <cerrno>

void receiveFile(int clientSock) {
    std::ofstream outFile("received_file.txt", std::ios::binary);
    char buffer[1024];
    int bytesReceived;

    while ((bytesReceived = recv(clientSock, buffer, sizeof(buffer), 0)) > 0) {
        outFile.write(buffer, bytesReceived);
    }

    outFile.close();
    close(clientSock);
}

int main() {
    int listenSock = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(8080);

    bind(listenSock, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    listen(listenSock, SOMAXCONN);

    while (true) {
        int clientSock = accept(listenSock, NULL, NULL);
        if (clientSock >= 0) {
            std::thread(receiveFile, clientSock).detach();
        }
    }

    close(listenSock);
    return 0;
}

ファイル転送クライアントの実装

ファイル転送クライアントは、サーバーに接続してファイルを送信します。

ファイル転送クライアントのコード例(Windows)

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <fstream>
#pragma comment(lib, "Ws2_32.lib")

void sendFile(SOCKET clientSock) {
    std::ifstream inFile("send_file.txt", std::ios::binary);
    char buffer[1024];

    while (inFile.read(buffer, sizeof(buffer)) || inFile.gcount() > 0) {
        send(clientSock, buffer, inFile.gcount(), 0);
    }

    inFile.close();
    closesocket(clientSock);
}

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET clientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);

    connect(clientSock, (sockaddr*)&serverAddr, sizeof(serverAddr));
    sendFile(clientSock);

    WSACleanup();
    return 0;
}

ファイル転送クライアントのコード例(Linux/macOS)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>

void sendFile(int clientSock) {
    std::ifstream inFile("send_file.txt", std::ios::binary);
    char buffer[1024];

    while (inFile.read(buffer, sizeof(buffer)) || inFile.gcount() > 0) {
        send(clientSock, buffer, inFile.gcount(), 0);
    }

    inFile.close();
    close(clientSock);
}

int main() {
    int clientSock = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);

    connect(clientSock, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    sendFile(clientSock);

    return 0;
}

ファイル転送の流れ

  1. サーバーを起動してクライアントからの接続を待ち受けます。
  2. クライアントを起動してサーバーに接続します。
  3. クライアントがファイルを読み込み、サーバーに送信します。
  4. サーバーがファイルを受信し、指定したファイルに保存します。

これで、TCPソケットを利用した基本的なファイル転送の実装が理解できました。最後に、この記事のまとめを行います。

まとめ

本記事では、C++を用いたTCPソケットプログラミングの基本から応用までを解説しました。TCPソケットの基本概念、環境設定、ソケットの作成、サーバーとクライアントの実装、データ送受信の方法、エラーハンドリング、そして応用例としてチャットアプリとファイル転送の実装方法を紹介しました。これらの知識を活用することで、ネットワークアプリケーションを構築し、信頼性の高いデータ通信を実現することができます。引き続き実践的なプロジェクトに取り組み、スキルを磨いてください。

コメント

コメントする

目次