思うだけで学ばない日記 2.0

思うだけで学ばない日記から移転しました☆!よろしくお願いします。

完成した完成した詐欺(マテ;

次の3点に対応した。

  • 行単位送受信処理の行の解釈・レスポンス送出処理からの分離(コルーチン化)
  • 受信タイムアウト設定
  • そこそこ強固なエラー処理
    Receive()やSend()の戻り値がSOCKET_ERRORかどうか見るようにしたり色々。

今や上のEchoサーバーもどきは次のように書ける。

// SocketTest.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include "Telex.h"

const int CSAPort = 4081;
const int LineSizeMax = 128;
const UINT TCTimeout = 10 * 1000;

#ifdef _UNICODE
#   define ostringstream    wostringstream
#   define strlen           wstrlen
#endif

/// エラーメッセージを表示します。
void error(LPCSTR func, LPCTSTR msg)
{
    ostringstream err;
    err << func << _T("(): ") << msg << _T(" (") << hex << showbase << GetLastError() << _T(")");
    AfxMessageBox(err.str().c_str());
}

#define ERR(msg)    (error(__FUNCTION__, _T(msg)))

/// Echoサーバーのサンプルです。
void SampleServer(LPVOID param)
{
    CSocket mstSock;
    CTimeoutSocket slvSock;

    if (!mstSock.Create(CSAPort)) {
        ERR("Create失敗");
        return;
    }

    if (!mstSock.Listen(1)) {
        ERR("Listen失敗");
        return;
    }

    for (;;) {
        if (!mstSock.Accept(slvSock)) {
            ERR("Accept失敗");
            return;
        }

        Telex tty(slvSock, LineSizeMax);    // 行単位送受信オブジェクト
        tty.SetTimeout(TCTimeout);          // タイムアウト時定数設定
        int line = 0;
        for (;;) {
            // 1行受信
            LPCSTR str;
            int len;
            if (!tty.Receive(str, len)) {
                if (tty.PopTimeoutState()) {
                    cout << "******* Timeout (" << TCTimeout << " ms Expired) *******" << endl;
                    break;
                } else {
                    error(__FUNCTION__, tty.LastErr());
                    return;
                }
            }
            // エコーバック
            if (!tty.Send(str, len)) {
                error(__FUNCTION__, tty.LastErr());
                return;
            }
            // サーバー側にも表示
            cout << ++line << ">" << str << " (" << len << ")" << endl;
        }
    }
}

/// メインルーチンです。
int _tmain(int argc, _TCHAR* argv[])
{
    if (!AfxSocketInit()) {
        ERR("AfxSocketInit()失敗");
        return -1;
    }

    SampleServer(0);

    return 0;
}

Telexクラス(笑)が行単位の送受信、それのみを担当する。


↓↓↓↓そのヘッダファイル↓↓↓↓

#pragma once

/// LFを行末とする電文1行づつの送受機能を提供します。
class Telex
{
private:
    CTimeoutSocket& socket;
    const int LineSizeMax;

    // バッファ
    const int bufSz;
    BYTE* const buf;

    // 行切り出し制御
    int wp, rp;
    bool lineFeed;
    bool bTimeout;
    UINT uTimeout;

    // エラーメッセージ
    TCHAR lastErr[128];

    /// エラーを記録します。
    void Error(LPCSTR func, LPCTSTR msg);

public:
    /// 新しいインスタンスを初期化します。
    Telex(CTimeoutSocket& sock, int lszmax = 128)
        : socket(sock),
          LineSizeMax(lszmax),
          bufSz(2 * lszmax),
          buf(new BYTE[2 * LineSizeMax])
    {
        wp = rp = 0;
        lineFeed = false;
        bTimeout = false;
        memset(lastErr, 0, sizeof(lastErr));
    }

    /// インスタンスを破棄します。
    virtual ~Telex() {
        delete[] buf;
    }

    /// 1行受信し、内容へのポインタと長さ(バイト数)を返します。
    /// 受信内容はTelexオブジェクトが確保したバッファで保持されます。
    bool Receive(LPCSTR& p, int& len);

    /// 1行受信し、内容へのポインタを返します。
    /// 受信内容はTelexオブジェクトが確保したバッファで保持されます。
    bool Receive(LPCSTR& p) { int len; Receive(p, len); }

    /*= (両Receive()に関する補足)
     *  - 返されたポインタpはヌル終端文字列としても扱えます。
     *  - 文字列pにLFは含まれません。
     *  - LFの直前にも制御文字が並んでいた場合、それらも文字列pに含まれません。
     *  - 文字数が制限を超える行については超過部分を無視します。
     *  - 受信内容自体にヌル文字が含まれるか否かは関知しません。
     *    (含まれていた場合、strlen(p) < lenになります。)
     */

    /// pが指すlenバイトのデータにLFを付加して送信します。
    /// データにヌル文字が含まれるか否かは関知しません。
    bool Send(LPCSTR str, int len);

    /// ヌル終端文字列pにLFを付加して送信します。
    bool Send(LPCSTR str);

    /// 最後に起きたエラーを取得します。
    LPCTSTR LastErr() { return lastErr; }

    /// タイムアウト時定数を設定します。0でタイムアウト無しです。
    void SetTimeout(UINT tmo) { uTimeout = tmo; }

    /// 受信タイムアウトが発生したか否かを取得します(リードクリア)。
    bool PopTimeoutState() { bool val = bTimeout; bTimeout = false; return val; }

};

実行結果。

(前略)
8426>To_Move:+ (9)
8427>BEGIN Time (10)
8428>Time_Unit:1sec (14)
8429>Total_Time:1500 (15)
8430>Least_Time_Per_Move:1 (21)
8431>END Time (8)
8432>BEGIN Position (14)
8433>P1-KY-KE-GI-KI-OU-KI-GI-KE-KY (29)
8434>P2 * -HI *  *  *  *  * -KA * (28)
8435>P3-FU-FU-FU-FU-FU-FU-FU-FU-FU (29)
8436>P4 *  *  *  *  *  *  *  *  * (28)
8437>P5 *  *  *  *  *  *  *  *  * (28)
8438>P6 *  *  *  *  *  *  *  *  * (28)
8439>P7+FU+FU+FU+FU+FU+FU+FU+FU+FU (29)
8440>P8 * +KA *  *  *  *  * +HI * (28)
8441>P9+KY+KE+GI+KI+OU+KI+GI+KE+KY (29)
8442>P+ (2)
8443>P- (2)
8444>+ (1)
8445>+2726FU,T12 (11)
8446>-3334FU,T6 (10)
8447>END Position (12)
8448>END Game_Summary (16)
####### Timeout (10000 ms Expired) #######

タイムアウトやエラーが起きたらコネクションをクローズして接続待ちに戻る、、、、
、、、、
、、、、
何を必死になってEchoサーバー作ってるんだ漏れorz