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

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

自分よりまず他人を疑え(マテ

ソケットを使ったプログラミングなどやったことがない反面、
通信相手が送ってきたものを全部チェックするようなゴージャスな通信プログラムを作りたい、
というわけで
http://www.computer-shogi.org/protocol/WinSample07Jan.lzh
を参考にせんとす(ry
(48時間後)
…バッファの上書きを避ける目的で一部Sleep()しているのが気に入らない
通信の基本コアぐらいソリッディーかつリジッドなつくりであってほしい

と思ったのでそれっぽいものを書いた

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

#include "stdafx.h"

const int CSAPort = 4081;
const int LineSizeMax = 128;

#ifdef _UNICODE
#   define ostringstream    wostringstream
#endif

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

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

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

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

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

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

    const DWORD bufSz = 2 * LineSizeMax;
    BYTE buf[bufSz];
    int rp = 0;
    int wp = 0;
    int line = 0;
    for (;;) {
        DWORD nb;
        // バッファ内の受信データサイズ取得
        if (!slvSock.IOCtl(FIONREAD, &nb)) {
            ERR("IOCtl()失敗");
            return;
        }

        // 読み出す
        DWORD nbMax = bufSz - wp;   // LineSizeMax以上は保証される
        if (nb > nbMax)
            nb = nbMax;
        int n = slvSock.Receive(buf + wp, nb);
        wp += n;

        // 改行検出
        bool lineFeed = false;
        while (rp < wp) {
            if (buf[rp] == '\n') {
                lineFeed = true;
                break;
            }
            ++rp;
        }

        // 行の解釈
        if (lineFeed) {                 // (改行コード発見)
            // 行終端位置確定
            int ep;
            if (rp < LineSizeMax) {
                ep = rp++;
            } else {
                ep = LineSizeMax;
                buf[ep] = '\n';
                assert(buf[rp] == '\n');
                ++rp;
            }
            /** 行末のコントロール文字を削除 */
            while (ep > 0 && iscntrl(buf[ep - 1]))
                --ep;
            // エコーバック
            int sp = 0;
            while (sp < ep) {
                int n2 = slvSock.Send(buf + sp, ep - sp);
                sp += n2;
            }
            slvSock.Send("\r\n", 2);    // コマンドウィンドウ上での改行を想定
            // サーバー側でも表示
            buf[ep] = '\0';
            cout << dec << ++line << ">" << buf << " (" << ep << ")" << endl;
            // 処理済みの行を詰める
            memmove_s(buf, bufSz, buf + rp, wp - rp);
            wp -= rp;
            rp = 0;
        } else {                        // (改行コード未発見)
            assert(rp == wp);
            // 行末まで無視
            if (rp > LineSizeMax)
                wp = rp = LineSizeMax;
            /*= 改行コードが発見されるまで
             *  wp, rpはLineSizeMaxにリセットされ続ける。
             */
        }
    }
}

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

    SampleServer(0);

    return 0;
}

これは通信部の仕様的には

  • LFで区切られた複数行が一つながりでやってきてもきちんと1行づつ取得する
  • ただし、改行を除く文字数がLineSizeMax文字を超過する行が来たら、超過部分の始まりから最初にLFが現れるまでの文字を無視する
  • Sleep()等の時間待ちはしない

といったもの
サンプル全体としてはエコーサーバーっぽい動きをする
memmove_s()でメモリ転送を行っているが、これの稼働頻度はそれほど高くない(ハズ)*1
データの並びがリニアでなくなることによる複雑性増大リスクと比べれば、極めて安いコストのはずだる

これを走らせた状態で

telnet localhost 4081

などとしてCSAの棋譜とかをコピペして流し込み、一応動くのを確認した(ただしなぜかエコーバックが崩れてまともに見えないのでTeraTeramProでも使った方がいいかも)

ただ今のつくりは通信部から行の処理を呼び出す形なので、これを変更してコルーチン化し、行の取得と行の処理を分離してシーケンシャルに書ける形にせねば利用上つらい

またタイムアウトを全く見ていないので、を参考にして3分でできる予定

サーバーを作る目的ではないのでプロトコルより下の階層でのコネクションの管理はしない

*1:1回のReceive()でLFとそれ以降がいっぺんに来たとき、LF以降が移動される。