C++によるファイル入出力とネットワークストリーム操作の完全ガイド

C++を用いたプログラミングにおいて、ファイル入出力とネットワークストリーム操作は重要な技術です。本記事では、これらの基本から応用までを網羅的に解説し、実用的なコード例や演習問題を通じて理解を深めることを目指します。

目次

ファイル入出力の基本

C++では、ファイル入出力操作を行うために標準ライブラリが提供する様々なクラスや関数を利用します。基本的なファイル入出力の操作を理解することは、データの保存や読み込みにおいて重要です。ここでは、ファイル入出力の基本概念とその使用方法について説明します。

fstreamライブラリの紹介

C++では、ファイル操作のために<fstream>ライブラリが使用されます。このライブラリには、以下の主要なクラスがあります。

  • ifstream: ファイルからの読み取りを行うためのクラス
  • ofstream: ファイルへの書き込みを行うためのクラス
  • fstream: ファイルの読み書きを両方行うためのクラス

ファイルを開く方法

ファイルを開くには、openメソッドを使用します。また、コンストラクタでファイル名を指定して開くことも可能です。

#include <iostream>
#include <fstream>

int main() {
    std::ifstream inputFile;
    inputFile.open("example.txt");

    if (!inputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }

    // ファイル操作

    inputFile.close();
    return 0;
}

または

#include <iostream>
#include <fstream>

int main() {
    std::ifstream inputFile("example.txt");

    if (!inputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }

    // ファイル操作

    inputFile.close();
    return 0;
}

ファイルを閉じる方法

ファイルを閉じるには、closeメソッドを使用します。ファイル操作が完了したら必ずファイルを閉じるようにしましょう。

inputFile.close();

次のセクションでは、具体的なファイルの読み書き方法について説明します。

ファイルの読み書き方法

C++でファイルの読み書きを行う際には、ifstreamofstreamクラスを使用します。ここでは、具体的な読み書きの方法を例示しながら解説します。

ファイルからの読み取り

ifstreamクラスを使用してファイルからデータを読み取る方法を説明します。以下の例では、テキストファイルから1行ずつ読み取ります。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream inputFile("example.txt");
    if (!inputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }

    std::string line;
    while (std::getline(inputFile, line)) {
        std::cout << line << std::endl;
    }

    inputFile.close();
    return 0;
}

この例では、std::getline関数を使用してファイルから1行ずつ読み取り、読み取った行をコンソールに出力しています。

ファイルへの書き込み

ofstreamクラスを使用してファイルにデータを書き込む方法を説明します。以下の例では、テキストファイルにデータを書き込みます。

#include <iostream>
#include <fstream>

int main() {
    std::ofstream outputFile("output.txt");
    if (!outputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }

    outputFile << "こんにちは、世界!" << std::endl;
    outputFile << "これはファイルへの書き込みの例です。" << std::endl;

    outputFile.close();
    return 0;
}

この例では、<<演算子を使用してテキストをファイルに書き込んでいます。

読み書きのエラーチェック

ファイル操作中にエラーが発生した場合、それを検出して適切に対処することが重要です。以下のコード例では、読み取りエラーを検出する方法を示しています。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream inputFile("example.txt");
    if (!inputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }

    std::string line;
    while (std::getline(inputFile, line)) {
        if (inputFile.fail()) {
            std::cerr << "読み取りエラーが発生しました。" << std::endl;
            break;
        }
        std::cout << line << std::endl;
    }

    inputFile.close();
    return 0;
}

この例では、failメソッドを使用して読み取りエラーを検出しています。

次のセクションでは、バイナリファイルの操作について詳述します。

バイナリファイルの操作

バイナリファイルの操作は、テキストファイルとは異なり、データをそのままのバイト形式で読み書きするために行います。ここでは、C++でのバイナリファイルの読み書き方法について解説します。

バイナリファイルへの書き込み

バイナリファイルにデータを書き込む際には、ofstreamクラスを使用し、ファイルをバイナリモードで開きます。以下の例では、整数の配列をバイナリファイルに書き込みます。

#include <iostream>
#include <fstream>

int main() {
    std::ofstream outputFile("output.bin", std::ios::binary);
    if (!outputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }

    int numbers[] = {1, 2, 3, 4, 5};
    outputFile.write(reinterpret_cast<const char*>(numbers), sizeof(numbers));

    outputFile.close();
    return 0;
}

この例では、writeメソッドを使用して、整数配列の内容をバイナリ形式でファイルに書き込んでいます。

バイナリファイルからの読み取り

バイナリファイルからデータを読み取る際には、ifstreamクラスを使用し、ファイルをバイナリモードで開きます。以下の例では、先ほどの例で書き込んだバイナリファイルからデータを読み取ります。

#include <iostream>
#include <fstream>

int main() {
    std::ifstream inputFile("output.bin", std::ios::binary);
    if (!inputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }

    int numbers[5];
    inputFile.read(reinterpret_cast<char*>(numbers), sizeof(numbers));

    for (int i = 0; i < 5; ++i) {
        std::cout << numbers[i] << std::endl;
    }

    inputFile.close();
    return 0;
}

この例では、readメソッドを使用して、バイナリ形式のデータをファイルから読み取り、配列に格納しています。

バイナリファイルのエラーチェック

バイナリファイル操作中のエラーチェックは、テキストファイルの場合と同様に重要です。以下のコード例では、読み取りエラーを検出する方法を示しています。

#include <iostream>
#include <fstream>

int main() {
    std::ifstream inputFile("output.bin", std::ios::binary);
    if (!inputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }

    int numbers[5];
    inputFile.read(reinterpret_cast<char*>(numbers), sizeof(numbers));
    if (inputFile.fail()) {
        std::cerr << "読み取りエラーが発生しました。" << std::endl;
    }

    for (int i = 0; i < 5; ++i) {
        std::cout << numbers[i] << std::endl;
    }

    inputFile.close();
    return 0;
}

この例では、failメソッドを使用して読み取りエラーを検出しています。

次のセクションでは、テキストファイルの操作について詳述します。

テキストファイルの操作

テキストファイルの操作は、プログラムの設定情報やユーザーの入力データを扱う際に頻繁に使用されます。ここでは、C++でのテキストファイルの読み書き方法について詳述します。

テキストファイルの読み取り

ifstreamクラスを使用して、テキストファイルからデータを読み取る方法を説明します。以下の例では、テキストファイルから1行ずつ読み取り、コンソールに出力します。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream inputFile("example.txt");
    if (!inputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }

    std::string line;
    while (std::getline(inputFile, line)) {
        std::cout << line << std::endl;
    }

    inputFile.close();
    return 0;
}

この例では、std::getline関数を使用してテキストファイルから1行ずつ読み取り、読み取った行をコンソールに出力しています。

テキストファイルへの書き込み

ofstreamクラスを使用して、テキストファイルにデータを書き込む方法を説明します。以下の例では、複数行のテキストをファイルに書き込みます。

#include <iostream>
#include <fstream>

int main() {
    std::ofstream outputFile("output.txt");
    if (!outputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }

    outputFile << "こんにちは、世界!" << std::endl;
    outputFile << "これはテキストファイルへの書き込みの例です。" << std::endl;

    outputFile.close();
    return 0;
}

この例では、<<演算子を使用してテキストをファイルに書き込んでいます。

ファイルを追記モードで開く

既存のファイルにデータを追記する場合は、ofstreamクラスのコンストラクタにstd::ios::appフラグを指定してファイルを開きます。

#include <iostream>
#include <fstream>

int main() {
    std::ofstream outputFile("output.txt", std::ios::app);
    if (!outputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }

    outputFile << "これが追記された行です。" << std::endl;

    outputFile.close();
    return 0;
}

この例では、ファイルの末尾に新しい行が追記されます。

テキストファイルのエラーチェック

テキストファイルの読み書き中にエラーが発生した場合、それを検出して適切に対処することが重要です。以下のコード例では、書き込みエラーを検出する方法を示しています。

#include <iostream>
#include <fstream>

int main() {
    std::ofstream outputFile("output.txt");
    if (!outputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }

    outputFile << "これはエラーチェックの例です。" << std::endl;
    if (outputFile.fail()) {
        std::cerr << "書き込みエラーが発生しました。" << std::endl;
    }

    outputFile.close();
    return 0;
}

この例では、failメソッドを使用して書き込みエラーを検出しています。

次のセクションでは、ネットワークストリームの基本について説明します。

ネットワークストリームの基本

ネットワークストリームは、ネットワークを介してデータを送受信するための基礎技術です。C++では、ソケットプログラミングを通じてネットワークストリームを操作します。ここでは、ネットワークストリームの基本概念とその利用方法について説明します。

ネットワークストリームとは

ネットワークストリームは、ネットワーク上のデータの流れを管理するための抽象的な概念です。これは、ファイル入出力と似ていますが、データの送受信先がネットワーク上の他のコンピュータである点が異なります。

ソケットプログラミングの概要

ソケットプログラミングは、ネットワーク通信を実現するためのプログラミング技法です。ソケットは、ネットワーク上で通信を行うためのエンドポイントです。C++では、通常、<sys/socket.h><arpa/inet.h>などのライブラリを使用します。

ソケットの種類

ソケットには、以下の2種類があります:

  • ストリームソケット(TCP): 信頼性の高い、順序が保証されたデータ伝送を行います。
  • データグラムソケット(UDP): 信頼性の低い、順序が保証されないデータ伝送を行いますが、高速です。

ソケットの作成と接続

以下に、ソケットを作成し、サーバーに接続する基本的な手順を示します。

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        std::cerr << "ソケット作成に失敗しました。" << std::endl;
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);

    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        std::cerr << "無効なアドレスです。" << std::endl;
        return -1;
    }

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cerr << "接続に失敗しました。" << std::endl;
        return -1;
    }

    std::cout << "接続に成功しました。" << std::endl;

    close(sock);
    return 0;
}

この例では、以下の手順でソケットを作成し、ローカルホストのポート8080に接続しています:

  1. socket関数を使用してソケットを作成。
  2. サーバーアドレスとポートを設定。
  3. connect関数を使用してサーバーに接続。
  4. 接続成功を確認した後、ソケットを閉じる。

データの送受信

データの送受信は、sendrecv関数を使用します。以下の例では、ソケットを通じてデータを送信し、受信します。

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[1024] = {0};

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        std::cerr << "ソケット作成に失敗しました。" << std::endl;
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);

    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        std::cerr << "無効なアドレスです。" << std::endl;
        return -1;
    }

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cerr << "接続に失敗しました。" << std::endl;
        return -1;
    }

    const char *message = "こんにちは、サーバー!";
    send(sock, message, strlen(message), 0);
    std::cout << "メッセージが送信されました。" << std::endl;

    int valread = recv(sock, buffer, 1024, 0);
    std::cout << "サーバーからのメッセージ: " << buffer << std::endl;

    close(sock);
    return 0;
}

この例では、クライアントがサーバーにメッセージを送信し、サーバーからの応答を受信します。

次のセクションでは、C++でのソケットプログラミングの基本について詳述します。

ソケットプログラミングの基本

C++でのソケットプログラミングは、ネットワークアプリケーションの基盤となる重要な技術です。ここでは、ソケットの作成から接続、データの送受信までの基本的な手法を解説します。

ソケットの作成

ソケットを作成するには、socket関数を使用します。以下の例では、TCPソケットを作成します。

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

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        std::cerr << "ソケット作成に失敗しました。" << std::endl;
        return -1;
    }
    std::cout << "ソケット作成に成功しました。" << std::endl;

    close(server_fd);
    return 0;
}

この例では、socket関数を使用してTCPソケットを作成し、ソケットのファイルディスクリプタを取得しています。

ソケットのバインド

ソケットを特定のポートにバインドするには、bind関数を使用します。以下の例では、ポート8080にソケットをバインドします。

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

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        std::cerr << "ソケット作成に失敗しました。" << std::endl;
        return -1;
    }

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

    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        std::cerr << "バインドに失敗しました。" << std::endl;
        close(server_fd);
        return -1;
    }
    std::cout << "バインドに成功しました。" << std::endl;

    close(server_fd);
    return 0;
}

この例では、bind関数を使用してソケットをポート8080にバインドしています。

ソケットのリスニング

ソケットをリスニング状態にするには、listen関数を使用します。以下の例では、ソケットを最大3つの接続を待機するように設定します。

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

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        std::cerr << "ソケット作成に失敗しました。" << std::endl;
        return -1;
    }

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

    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        std::cerr << "バインドに失敗しました。" << std::endl;
        close(server_fd);
        return -1;
    }

    if (listen(server_fd, 3) < 0) {
        std::cerr << "リスニングに失敗しました。" << std::endl;
        close(server_fd);
        return -1;
    }
    std::cout << "リスニングに成功しました。" << std::endl;

    close(server_fd);
    return 0;
}

この例では、listen関数を使用してソケットをリスニング状態にしています。

クライアントの接続を受け入れる

クライアントからの接続を受け入れるには、accept関数を使用します。以下の例では、接続を受け入れた後、メッセージを送信します。

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

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        std::cerr << "ソケット作成に失敗しました。" << std::endl;
        return -1;
    }

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

    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        std::cerr << "バインドに失敗しました。" << std::endl;
        close(server_fd);
        return -1;
    }

    if (listen(server_fd, 3) < 0) {
        std::cerr << "リスニングに失敗しました。" << std::endl;
        close(server_fd);
        return -1;
    }

    int addrlen = sizeof(address);
    int new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
    if (new_socket < 0) {
        std::cerr << "接続の受け入れに失敗しました。" << std::endl;
        close(server_fd);
        return -1;
    }

    const char* message = "こんにちは、クライアント!";
    send(new_socket, message, strlen(message), 0);
    std::cout << "メッセージが送信されました。" << std::endl;

    close(new_socket);
    close(server_fd);
    return 0;
}

この例では、accept関数を使用してクライアントからの接続を受け入れ、send関数を使用してメッセージを送信しています。

次のセクションでは、C++でのサーバーとクライアントの実装について詳述します。

サーバーとクライアントの実装

C++でネットワーク通信を行う際には、サーバーとクライアントの両方を実装する必要があります。ここでは、基本的なサーバーとクライアントの実装例を示します。

サーバーの実装

以下の例では、TCPソケットを使用して基本的なサーバーを実装します。このサーバーは、クライアントからの接続を受け入れ、メッセージを受信して返信します。

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

#define PORT 8080

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[1024] = {0};
    const char* hello = "Hello from server";

    // ソケットファイルディスクリプタの作成
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        std::cerr << "ソケット作成に失敗しました" << std::endl;
        return -1;
    }

    // ソケットオプションの設定
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        std::cerr << "ソケットオプションの設定に失敗しました" << std::endl;
        close(server_fd);
        return -1;
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // バインド操作
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        std::cerr << "バインドに失敗しました" << std::endl;
        close(server_fd);
        return -1;
    }

    // リスニング操作
    if (listen(server_fd, 3) < 0) {
        std::cerr << "リスニングに失敗しました" << std::endl;
        close(server_fd);
        return -1;
    }

    // 接続受け入れ
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        std::cerr << "接続受け入れに失敗しました" << std::endl;
        close(server_fd);
        return -1;
    }

    // データ受信
    read(new_socket, buffer, 1024);
    std::cout << "クライアントからのメッセージ: " << buffer << std::endl;

    // データ送信
    send(new_socket, hello, strlen(hello), 0);
    std::cout << "メッセージが送信されました" << std::endl;

    close(new_socket);
    close(server_fd);
    return 0;
}

この例では、サーバーがクライアントからのメッセージを受信し、それに対して応答を送信します。

クライアントの実装

以下の例では、サーバーに接続してメッセージを送信し、サーバーからの応答を受信する基本的なクライアントを実装します。

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>

#define PORT 8080

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[1024] = {0};
    const char* hello = "Hello from client";

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        std::cerr << "ソケット作成に失敗しました" << std::endl;
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // サーバーのアドレスとポートを設定
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        std::cerr << "無効なアドレスまたはアドレスがサポートされていません" << std::endl;
        return -1;
    }

    // サーバーに接続
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cerr << "接続に失敗しました" << std::endl;
        return -1;
    }

    // メッセージ送信
    send(sock, hello, strlen(hello), 0);
    std::cout << "メッセージが送信されました" << std::endl;

    // メッセージ受信
    read(sock, buffer, 1024);
    std::cout << "サーバーからのメッセージ: " << buffer << std::endl;

    close(sock);
    return 0;
}

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

次のセクションでは、非同期通信の実装方法について説明します。

非同期通信の実装方法

非同期通信は、ネットワークプログラミングにおいて効率的にデータを送受信するための重要な手法です。ここでは、C++での非同期通信の基本的な実装方法について説明します。

非同期通信とは

非同期通信では、データの送受信操作がブロッキングされず、他の処理を並行して実行できます。これにより、効率的なリソース管理と応答性の向上が図れます。

非同期通信のためのライブラリ

C++で非同期通信を実現するために、Boost.Asioなどのライブラリがよく使用されます。Boost.Asioは、ネットワークプログラミングおよびその他の非同期操作をサポートする強力なライブラリです。

Boost.Asioを使用した非同期通信の例

以下に、Boost.Asioを使用した基本的な非同期TCPサーバーとクライアントの例を示します。

Boost.Asioのインストール

Boost.Asioを使用するには、まずBoostライブラリをインストールする必要があります。Boostは、以下のコマンドでインストールできます。

sudo apt-get install libboost-all-dev

非同期TCPサーバーの実装

以下は、Boost.Asioを使用した非同期TCPサーバーの例です。

#include <iostream>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

void session(tcp::socket sock) {
    try {
        for (;;) {
            char data[1024];
            boost::system::error_code error;

            size_t length = sock.read_some(boost::asio::buffer(data), error);

            if (error == boost::asio::error::eof)
                break; // 接続が閉じられた
            else if (error)
                throw boost::system::system_error(error); // その他のエラー

            boost::asio::write(sock, boost::asio::buffer(data, length));
        }
    } catch (std::exception& e) {
        std::cerr << "Exception in thread: " << e.what() << std::endl;
    }
}

int main() {
    try {
        boost::asio::io_context io_context;

        tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 8080));

        for (;;) {
            tcp::socket socket(io_context);
            acceptor.accept(socket);
            std::thread(session, std::move(socket)).detach();
        }
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

このサーバーは、クライアントからの接続を受け入れ、受信したデータをそのまま送り返します。std::threadを使用して各セッションを新しいスレッドで処理します。

非同期TCPクライアントの実装

以下は、Boost.Asioを使用した非同期TCPクライアントの例です。

#include <iostream>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

int main() {
    try {
        boost::asio::io_context io_context;

        tcp::resolver resolver(io_context);
        tcp::resolver::results_type endpoints = resolver.resolve("127.0.0.1", "8080");

        tcp::socket socket(io_context);
        boost::asio::connect(socket, endpoints);

        const std::string msg = "Hello from client";
        boost::asio::write(socket, boost::asio::buffer(msg));

        char reply[1024];
        size_t reply_length = boost::asio::read(socket, boost::asio::buffer(reply, msg.size()));
        std::cout << "Reply is: ";
        std::cout.write(reply, reply_length);
        std::cout << std::endl;
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

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

次のセクションでは、実用的な応用例について紹介します。

実用的な応用例

C++でのファイル入出力とネットワークストリームを組み合わせることで、さまざまな実用的なアプリケーションを作成することができます。ここでは、いくつかの具体的な応用例を紹介します。

ファイル転送サーバーとクライアント

ファイル転送は、ネットワークアプリケーションの一般的な機能です。以下に、ファイルを送受信するサーバーとクライアントの簡単な実装例を示します。

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

このサーバーは、クライアントからの接続を受け入れ、指定されたファイルを送信します。

#include <iostream>
#include <fstream>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

void sendFile(tcp::socket& socket, const std::string& filename) {
    std::ifstream file(filename, std::ios::binary);
    if (!file) {
        std::cerr << "ファイルを開くことができませんでした: " << filename << std::endl;
        return;
    }

    char buffer[1024];
    while (file.read(buffer, sizeof(buffer)).gcount() > 0) {
        boost::asio::write(socket, boost::asio::buffer(buffer, file.gcount()));
    }
}

int main() {
    try {
        boost::asio::io_context io_context;

        tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 8080));

        for (;;) {
            tcp::socket socket(io_context);
            acceptor.accept(socket);
            sendFile(socket, "example.txt");
        }
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

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

このクライアントは、サーバーに接続してファイルを受信し、ローカルに保存します。

#include <iostream>
#include <fstream>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

void receiveFile(tcp::socket& socket, const std::string& filename) {
    std::ofstream file(filename, std::ios::binary);
    if (!file) {
        std::cerr << "ファイルを作成することができませんでした: " << filename << std::endl;
        return;
    }

    char buffer[1024];
    boost::system::error_code error;
    size_t len;
    while ((len = socket.read_some(boost::asio::buffer(buffer), error)) > 0) {
        file.write(buffer, len);
    }

    if (error && error != boost::asio::error::eof) {
        std::cerr << "読み取りエラー: " << error.message() << std::endl;
    }
}

int main() {
    try {
        boost::asio::io_context io_context;

        tcp::resolver resolver(io_context);
        tcp::resolver::results_type endpoints = resolver.resolve("127.0.0.1", "8080");

        tcp::socket socket(io_context);
        boost::asio::connect(socket, endpoints);

        receiveFile(socket, "received_example.txt");
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

ログサーバーとクライアント

リアルタイムでログを収集し、中央のサーバーに保存するシステムも、ファイル入出力とネットワークストリームの実用的な応用例です。

ログサーバーの実装

このサーバーは、クライアントから受信したログメッセージをファイルに書き込みます。

#include <iostream>
#include <fstream>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

void saveLog(tcp::socket& socket) {
    std::ofstream logfile("server_log.txt", std::ios::app);
    if (!logfile) {
        std::cerr << "ログファイルを開くことができませんでした。" << std::endl;
        return;
    }

    char buffer[1024];
    boost::system::error_code error;
    size_t len;
    while ((len = socket.read_some(boost::asio::buffer(buffer), error)) > 0) {
        logfile.write(buffer, len);
    }

    if (error && error != boost::asio::error::eof) {
        std::cerr << "読み取りエラー: " << error.message() << std::endl;
    }
}

int main() {
    try {
        boost::asio::io_context io_context;

        tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 8080));

        for (;;) {
            tcp::socket socket(io_context);
            acceptor.accept(socket);
            saveLog(socket);
        }
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

ログクライアントの実装

このクライアントは、ログメッセージをサーバーに送信します。

#include <iostream>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

void sendLog(tcp::socket& socket, const std::string& message) {
    boost::asio::write(socket, boost::asio::buffer(message));
}

int main() {
    try {
        boost::asio::io_context io_context;

        tcp::resolver resolver(io_context);
        tcp::resolver::results_type endpoints = resolver.resolve("127.0.0.1", "8080");

        tcp::socket socket(io_context);
        boost::asio::connect(socket, endpoints);

        sendLog(socket, "これはログメッセージです。\n");
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

このように、ファイル入出力とネットワークストリームを組み合わせることで、さまざまな実用的なシステムを構築することができます。次のセクションでは、これまで学んだ内容を確認するための演習問題を提示します。

演習問題

ここでは、これまで学んだC++によるファイル入出力とネットワークストリーム操作の知識を確認するための演習問題を提示します。各演習問題には、問題の概要と目標が含まれています。

演習1: テキストファイルの読み書き

概要: テキストファイルに複数行のテキストを書き込み、その後、書き込んだファイルを読み取ってコンソールに出力するプログラムを作成します。
目標: テキストファイルの基本的な読み書き操作を理解する。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    // テキストファイルに書き込む
    std::ofstream outputFile("test.txt");
    if (!outputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }
    outputFile << "1行目のテキスト\n";
    outputFile << "2行目のテキスト\n";
    outputFile << "3行目のテキスト\n";
    outputFile.close();

    // テキストファイルを読み取る
    std::ifstream inputFile("test.txt");
    if (!inputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }
    std::string line;
    while (std::getline(inputFile, line)) {
        std::cout << line << std::endl;
    }
    inputFile.close();

    return 0;
}

演習2: バイナリファイルの読み書き

概要: 整数の配列をバイナリファイルに書き込み、その後、バイナリファイルを読み取って配列の内容をコンソールに出力するプログラムを作成します。
目標: バイナリファイルの基本的な読み書き操作を理解する。

#include <iostream>
#include <fstream>

int main() {
    int numbers[] = {1, 2, 3, 4, 5};

    // バイナリファイルに書き込む
    std::ofstream outputFile("test.bin", std::ios::binary);
    if (!outputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }
    outputFile.write(reinterpret_cast<const char*>(numbers), sizeof(numbers));
    outputFile.close();

    // バイナリファイルを読み取る
    std::ifstream inputFile("test.bin", std::ios::binary);
    if (!inputFile) {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
        return 1;
    }
    int readNumbers[5];
    inputFile.read(reinterpret_cast<char*>(readNumbers), sizeof(readNumbers));
    inputFile.close();

    for (int i = 0; i < 5; ++i) {
        std::cout << readNumbers[i] << std::endl;
    }

    return 0;
}

演習3: 簡単なチャットサーバーとクライアント

概要: 非同期通信を利用した簡単なチャットサーバーとクライアントを実装します。サーバーは複数のクライアントからのメッセージを受け取り、それを他のクライアントにブロードキャストします。
目標: ソケットプログラミングと非同期通信の理解を深める。

// サーバーの実装例(Boost.Asio使用)
#include <iostream>
#include <set>
#include <memory>
#include <thread>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

class ChatSession : public std::enable_shared_from_this<ChatSession> {
public:
    ChatSession(tcp::socket socket, std::set<std::shared_ptr<ChatSession>>& sessions)
        : socket_(std::move(socket)), sessions_(sessions) {}

    void start() {
        sessions_.insert(shared_from_this());
        do_read();
    }

    void deliver(const std::string& msg) {
        boost::asio::post(socket_.get_executor(),
            [this, msg]() {
                do_write(msg);
            });
    }

private:
    void do_read() {
        auto self(shared_from_this());
        socket_.async_read_some(boost::asio::buffer(data_, max_length),
            [this, self](boost::system::error_code ec, std::size_t length) {
                if (!ec) {
                    for (auto& session : sessions_) {
                        if (session != self) {
                            session->deliver(std::string(data_, length));
                        }
                    }
                    do_read();
                } else {
                    sessions_.erase(self);
                }
            });
    }

    void do_write(const std::string& msg) {
        auto self(shared_from_this());
        boost::asio::async_write(socket_, boost::asio::buffer(msg),
            [this, self](boost::system::error_code ec, std::size_t /*length*/) {
                if (ec) {
                    sessions_.erase(self);
                }
            });
    }

    tcp::socket socket_;
    std::set<std::shared_ptr<ChatSession>>& sessions_;
    enum { max_length = 1024 };
    char data_[max_length];
};

class ChatServer {
public:
    ChatServer(boost::asio::io_context& io_context, const tcp::endpoint& endpoint)
        : acceptor_(io_context, endpoint) {
        do_accept();
    }

private:
    void do_accept() {
        acceptor_.async_accept(
            [this](boost::system::error_code ec, tcp::socket socket) {
                if (!ec) {
                    std::make_shared<ChatSession>(std::move(socket), sessions_)->start();
                }
                do_accept();
            });
    }

    tcp::acceptor acceptor_;
    std::set<std::shared_ptr<ChatSession>> sessions_;
};

int main() {
    try {
        boost::asio::io_context io_context;
        tcp::endpoint endpoint(tcp::v4(), 8080);
        ChatServer server(io_context, endpoint);
        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}
// クライアントの実装例(Boost.Asio使用)
#include <iostream>
#include <thread>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

void read_messages(tcp::socket& socket) {
    for (;;) {
        char read_msg[1024];
        boost::system::error_code error;
        size_t len = socket.read_some(boost::asio::buffer(read_msg), error);
        if (error == boost::asio::error::eof) {
            break; // 接続が閉じられた
        } else if (error) {
            throw boost::system::system_error(error); // その他のエラー
        }
        std::cout.write(read_msg, len);
        std::cout << std::endl;
    }
}

int main() {
    try {
        boost::asio::io_context io_context;
        tcp::resolver resolver(io_context);
        auto endpoints = resolver.resolve("127.0.0.1", "8080");
        tcp::socket socket(io_context);
        boost::asio::connect(socket, endpoints);

        std::thread(read_messages, std::ref(socket)).detach();

        for (std::string line; std::getline(std::cin, line);) {
            boost::asio::write(socket, boost::asio::buffer(line));
        }
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

これらの演習問題を通じて、C++によるファイル入出力とネットワークストリーム操作の実践的なスキルを身につけることができます。

次のセクションでは、これまでの内容をまとめます。

まとめ

本記事では、C++によるファイル入出力とネットワークストリームの操作について、基本から応用までを詳しく解説しました。ファイル入出力では、テキストファイルとバイナリファイルの読み書き方法を学び、ネットワークストリームでは、ソケットプログラミングを通じて非同期通信やサーバー・クライアントの実装方法を理解しました。

これらの技術は、効率的なデータ管理やリアルタイム通信を実現するための重要な要素です。実際のアプリケーション開発において、これらの知識を活用して、より高度なプログラムを構築していってください。

各演習問題に取り組むことで、さらに理解を深め、実践的なスキルを磨くことができます。今後の開発に役立ててください。

コメント

コメントする

目次