C++と色々

主にC++やプログラムに関する記事を投稿します。

Boost.Asioで簡単なチャット

簡単なチャットプログラムを書いてみました。Boost.AsioとWinsock2の2通り書いてみました。とにかく簡単に書こうとしたので同期通信しています。なので一方的にメッセージを送ることはできず、必ずサーバ側とクライアント側が交互にメッセージを送信しなければなりません。
どちらかが"end"というメッセージを送ったら接続を切り、終了します。

WinSockで書いたチャットプログラム

#include <winsock2.h>
#include <iostream>
#include <string>
using namespace std;

#pragma comment(lib, "WSock32.lib")

int client();
int server();

int main()
{
    char c;
    cout << "クライアント?サーバー?c/s\n>";
    cin >> c;
    if (c == 'c')
    {
        client();
    }
    else if (c == 's')
    {
        server();
    }
}

int client()
{
    WSADATA wsadata;//ソケット情報を格納する構造体

    //WinSockの初期化
    if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
    {
        cout << "winsock startup failed.\n";
        return 1;
    }

    //指定したサービスプロバイダへのTCPソケットを作成する。
    const auto sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET)
    {
        cout << "couldn't open a socket.\n";
        WSACleanup();
        return 1;
    }

    //ホスト情報を取得
    cout << "サーバー名\n>";
    string server_name;
    cin >> server_name;
    auto host = gethostbyname(server_name.c_str());
    if (host == nullptr)
    {
        auto addr = inet_addr(server_name.c_str());
        host = gethostbyaddr(reinterpret_cast<char*>(&addr), 4, AF_INET);
        if (host == nullptr)
        {
            cout << server_name << " is not found.\n";
            WSACleanup();
            return 1;
        }
    }

    //IPv4のソケットアドレス情報を設定する
    sockaddr_in sockadd;
    memset(&sockadd, 0, sizeof(sockadd));
    sockadd.sin_family = AF_INET;
    cout << "ポート番号\n>";
    u_short port = 0;
    cin >> port;
    sockadd.sin_port = htons(port);
    sockadd.sin_addr = *(reinterpret_cast<LPIN_ADDR>(*(host->h_addr_list)));

    //指定したソケットへ接続する
    if (connect(sock, reinterpret_cast<PSOCKADDR>(&sockadd), sizeof(sockadd)) != 0)
    {
        cout << "connect failure.\n";
        closesocket(sock);
        WSACleanup();
        return 1;
    }

    //メッセージを送受信
    string buffer;
    while (true)
    {
        cout << "送信\n>";
        cin >> buffer;
        send(sock, buffer.c_str(), buffer.size(), 0);
        if (buffer == "end")
        {
            break;
        }
        cout << "サーバーから返事を待っています\n";
        char buf[1024];
        memset(buf, '\0', sizeof(buf));
        const auto rec = recv(sock, buf, sizeof(buf)-1, 0);
        if (rec == SOCKET_ERROR)
        {
            cout << "エラーです\n";
            break;
        }
        buf[rec] = '\0';
        if (strcmp(buf, "end") == 0)
        {
            cout << "サーバーが接続を切りました\n";
            break;
        }

        cout << "受信:" << buf << endl;
    }
    //ソケットの送受信を切断する
    shutdown(sock, SD_BOTH);
    //ソケットを破棄する
    closesocket(sock);
    //WinSockの解放
    WSACleanup();
    cout << "終了します\n";

    return 0;
}

int server()
{
    WSADATA wsadata;//ソケット情報を格納する構造体

    //WinSockの初期化
    if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
    {
        cout << "winsock startup failed.\n";
        return 1;
    }

    //指定したサービスプロバイダへのTCPソケットを作成する。
    const auto listen_socket = socket(PF_INET, SOCK_STREAM, 0);
    if (listen_socket == INVALID_SOCKET)
    {
        cout << "couldn't open a socket.\n";
        WSACleanup();
        return 1;
    }

    //IPv4のソケットアドレス情報を設定する
    sockaddr_in sockadd;
    memset(&sockadd, 0, sizeof(sockadd));
    sockadd.sin_family = AF_INET;
    cout << "ポート番号\n>";
    u_short port = 0;
    cin >> port;
    sockadd.sin_port = htons(port);
    sockadd.sin_addr.s_addr = INADDR_ANY;

    //ソケットに名前をつける
    if (bind(listen_socket, reinterpret_cast<sockaddr*>(&sockadd), sizeof(sockadd)) == SOCKET_ERROR)
    {
        cout << "bind failure.\n";
        closesocket(listen_socket);
        WSACleanup();
        return 1;
    }

    //接続待ち状態にする
    if (listen(listen_socket, 0) == SOCKET_ERROR)
    {
        cout << "listen failure.\n";
        closesocket(listen_socket);
        WSACleanup();
        return 1;
    }

    //クライアントからの接続を受け入れる
    decltype(sockadd) from;
    int from_length = sizeof(from);
    SOCKET sock;
    sock = accept(listen_socket, reinterpret_cast<sockaddr*>(&from), &from_length);
    if (sock == INVALID_SOCKET)
    {
        cout << "accept failure.\n";
        closesocket(listen_socket);
        WSACleanup();
        return 1;
    }

    cout << inet_ntoa(from.sin_addr) << "が接続してきました\n";

    closesocket(listen_socket);

    //メッセージを送受信
    string buffer;
    while (true)
    {
        cout << "クライアントからの受信を待っています\n>";
        char buf[1024];
        memset(buf, '\0', sizeof(buf));
        const auto rec = recv(sock, buf, sizeof(buf)-1, 0);
        if (rec == SOCKET_ERROR)
        {
            cout << "エラーです\n";
            break;
        }
        buf[rec] = '\0';
        if (strcmp(buf, "end") == 0)
        {
            cout << "クライアントが接続を切りました\n";
            break;
        }
        cout << "受信:" << buf << endl;

        cout << "送信\n>";
        cin >> buffer;
        send(sock, buffer.c_str(), buffer.size(), 0);
        if (buffer == "end")
        {
            break;
        }
    }
    //ソケットの送受信を切断する
    shutdown(sock, SD_BOTH);
    //ソケットを破棄する
    closesocket(sock);
    //WinSockの解放
    WSACleanup();
    cout << "終了します\n";

    return 0;
}

Boost.Asioで書いたチャットプログラム

#include <boost/asio.hpp>
#include <iostream>
#include <string>
using namespace std;
namespace asio = boost::asio;
namespace ip = asio::ip;

int client();
int server();

int main()
{
    char c;
    cout << "クライアント?サーバー?c/s\n>";
    cin >> c;
    if (c == 'c')
    {
        client();
    }
    else if (c == 's')
    {
        server();
    }
}

int client()
try
{
    asio::io_service io_service;

    //TCPソケットを作成する
    ip::tcp::socket sock(io_service);

    //ホスト情報を取得
    cout << "サーバー名\n>";
    string server_name;
    cin >> server_name;
    cout << "ポート番号\n>";
    u_short port = 0;
    cin >> port;
    sock.connect(ip::tcp::endpoint(ip::address::from_string(server_name), port));

    //メッセージを送受信
    string buffer;
    while (true)
    {
        cout << "送信\n>";
        cin >> buffer;
        asio::write(sock, asio::buffer(buffer));
        if (buffer == "end")
        {
            break;
        }
        cout << "サーバーから返事を待っています\n";

        asio::streambuf receive_buffer;
        boost::system::error_code error;
        asio::read(sock, receive_buffer, asio::transfer_at_least(1), error);
        if (error && error != asio::error::eof)
        {
            std::cout << "receive failed: " << error.message() << std::endl;
        }
        else if (asio::buffer_cast<const char*>(receive_buffer.data()) == string("end"))
        {
            cout << "サーバーが接続を切りました\n";
            break;
        }

        cout << "受信:" << &receive_buffer << endl;
    }

    return 0;
}
catch (exception& e)
{
    cout << e.what();
    return 1;
}

int server()
try
{
    asio::io_service io_service;

    //TCPソケットを作成する
    ip::tcp::socket sock(io_service);

    //IPv4のソケットアドレス情報を設定する
    cout << "ポート番号\n>";
    u_short port = 0;
    cin >> port;
    ip::tcp::acceptor acceptor(io_service, ip::tcp::endpoint(ip::tcp::v4(), port));

    //クライアントからの接続を受け入れる
    acceptor.accept(sock);

    //メッセージを送受信
    string buffer;
    while (true)
    {
        cout << "クライアントからの受信を待っています\n>";
        asio::streambuf receive_buffer;
        boost::system::error_code error;
        asio::read(sock, receive_buffer, asio::transfer_at_least(1), error);
        if (error && error != asio::error::eof)
        {
            std::cout << "receive failed: " << error.message() << std::endl;
        }
        else if (asio::buffer_cast<const char*>(receive_buffer.data()) == string("end"))
        {
            cout << "クライアントが接続を切りました\n";
            break;
        }

        cout << "受信:" << &receive_buffer << endl;

        cout << "送信\n>";
        cin >> buffer;
        asio::write(sock, asio::buffer(buffer));
        if (buffer == "end")
        {
            break;
        }
    }
    return 0;
}
catch (exception& e)
{
    cout << e.what();
    return 1;
}

次ネットワークプログラムを書く時は非同期通信に挑戦してみようともいます。