XNA入門

STEP.04 ログイン/ログアウト

このチュートリアルの完成イメージ

完成イメージ@ 完成イメージA

ダウンロード

概要・解説

前回のチュートリアルでは、クライアントとサーバー間で接続をする部分まで作成しました。

今回のチュートリアルでは、前回作成した ClientEmulator に機能を追加してデータの送受信を行えるようにします。データの送受信によってログイン/ログアウトの処理を実装してみたいと思います。

なるべくシンプルになるように考えていたのですが、少し複雑になってしまいました。今回のポイントとしては、C#/Javaのリフレクションを使用してフィールドを取得して値の読み書きを行っている箇所です。このチュートリアルでは送受信するデータ型として C# で定義されるプリミティブ型および文字列型を考えています。その他のクラスも送受信対象にしても良いのですが、ひとまずプリミティブ型と文字列型があれば大抵のことは可能だと思います。

データ送受信を行うデータフォーマットは以下の図のように設計してみました。データ長とメッセージ識別コードは余裕を持たせてありますが、それぞれ short(2byte), byte(1byte) でも十分かと思います。

データレイアウトイメージ

データ長にはデータ全体のバイト数が設定されます。また、本格的なものになった場合は末尾にエンドマークが必要かもしれません。参考書籍『MMORPG ゲームサーバープログラミング』

ソリューション設定

STEP.04 のソリューション設定は次の図のようになっています。

ソリューション設定@
    message (送受信を行うクラスを作成します。)
  • IMessage.cs
  • LoginRequest.cs
  • LoginResponse.cs
  • LogoutRequest.cs
  • LogoutResponse.cs
  • MessageManager.cs

JAVA ソースコード(サーバー)

IMessage.java
package sample2DRPG.message;

public interface IMessage 
{
    /**
     * メッセージ識別番号
     */

    public short getCode();
}

LoginRequest.java
package sample2DRPG.message;

public class LoginRequest implements IMessage
{
    /**
     * メッセージ識別番号
     */

    public short getCode()
    {
        return 1;
    }

    // ユーザー名
    public String username;

    // パスワード
    public String password;
    
    // メッセージの文字列表現
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        buf.append("name=");
        buf.append(this.getClass().getSimpleName());
        buf.append(",code=");
        buf.append(this.getCode());
        buf.append(",username=");
        buf.append(this.username);
        buf.append(",password=");
        buf.append(this.password);
        return buf.toString();
    }
}

LoginResponse.java
package sample2DRPG.message;

public class LoginResponse  implements IMessage
{
    /**
     * メッセージ識別番号
     */

    public short getCode()
    {
        return 2;
    }

    // 接続識別番号
    public int connid;

    // メッセージの文字列表現
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        buf.append("name=");
        buf.append(this.getClass().getSimpleName());
        buf.append(",code=");
        buf.append(this.getCode());
        buf.append(",connid=");
        buf.append(this.connid);
        return buf.toString();
    }
}

LogoutRequest.java
package sample2DRPG.message;

public class LogoutRequest  implements IMessage
{
    /**
     * メッセージ識別番号
     */

    public short getCode()
    {
        return 3;
    }

    // メッセージの文字列表現
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        buf.append("name=");
        buf.append(this.getClass().getSimpleName());
        buf.append(",code=");
        buf.append(this.getCode());
        return buf.toString();
    }
}

LogoutResponse.java
package sample2DRPG.message;

public class LogoutResponse  implements IMessage
{
    /**
     * メッセージ識別番号
     */

    public short getCode()
    {
        return 4;
    }

    // メッセージの文字列表現
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        buf.append("name=");
        buf.append(this.getClass().getSimpleName());
        buf.append(",code=");
        buf.append(this.getCode());
        buf.append("]");
        return buf.toString();
    }
}

MessageManager.java
package sample2DRPG.message;

import java.nio.ByteBuffer;
import java.util.HashMap;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class MessageManager
{
    // インスタンス
    private static MessageManager instance;
    
    private MessageManager()
    {
    }
    
    /**
     * メッセージマネージャを生成
     */

    public static void create()
    {
        if (instance != null) {
            throw new RuntimeException("MessageManager は既に作成されています。");
        }
        instance = new MessageManager();
        instance.put(new LoginRequest());
        instance.put(new LoginResponse());
        instance.put(new LogoutRequest());
        instance.put(new LogoutResponse());
    }

    /**
     * メッセージ取得
     * @param code メッセージ識別コード
     * @return
     */

    public static IMessage get(ByteBuffer buffer)
    {
        // メッセージ識別コードを取得
        short code = buffer.getShort();
        Class type = msgmap.get(code);
        IMessage msg = null;
        if (type != null) {
            try {
                msg = (IMessage)type.newInstance();
                
                // マスタからフィールド定義を取得
                Field[] fields = type.getFields();

                // フィールド定義に応じてバッファからデータを取得
                for (Field f : fields) {
                    
                    // final,transient は対象外
                    int mod = f.getModifiers();
                    if (Modifier.isFinal(mod) || Modifier.isTransient(mod)) {
                        continue;
                    }

                    if (f.getType() == Short.TYPE) {
                        // Short型の読み込み

                    } else if (f.getType() == Integer.TYPE) {
                        // Integer型の読み込み

                    } else if (f.getType() == String.class) {
                        // 修正UTF-8の読み込み
                        short length = buffer.getShort();
                        if (length == -1) {
                            // 文字列長が -1 の場合は null を設定
                            f.set(msg, null);
                        } else {
                            byte[] dst = new byte[length];
                            buffer.get(dst);
                            f.set(msg, new String(dst,"UTF8"));
                        }
                    }
                }
                
            } catch(UnsupportedEncodingException uee) {
                System.out.println("例外が発生しました。[" + uee + "]");
                uee.printStackTrace();
                
            } catch(IllegalAccessException iae) {
                System.out.println("例外が発生しました。[" + iae + "]");
                iae.printStackTrace();
                
            } catch(InstantiationException ie) {
                System.out.println("例外が発生しました。[" + ie + "]");
                ie.printStackTrace();
            }
        }
        return msg;
    }

    /**
     * バッファにメッセージのバイト配列イメージを設定
     * @param buffer 書込み用のバッファ
     * @param msg 書き込むメッセージ
     */

    public static void set(ByteBuffer buffer, IMessage msg) 
    {
        buffer.clear();
        buffer.putInt(0);
        buffer.putShort(msg.getCode());

        // メッセージからフィールド定義を取得
        Field[] fields = msg.getClass().getFields();
        
        // フィールド定義に応じてバッファからデータを取得
        for (Field f : fields) {
            
            try {
            
                // final,transient は対象外
                int mod = f.getModifiers();
                if (Modifier.isFinal(mod) || Modifier.isTransient(mod)) {
                    continue;
                }
                
                if (f.getType() == Short.TYPE) {
                    // Short型の書き込み
                    Short value = (Short)f.get(msg);
                    buffer.putShort(value);
    
                } else if (f.getType() == Integer.TYPE) {
                    // Integer型の書き込み
                    Integer value = (Integer)f.get(msg);
                    buffer.putInt(value);
    
                } else if (f.getType() == String.class) {
                    // 修正UTF-8の書き込み(nullの場合は FF-FF)
                    String value = (String)f.get(msg);
                    if (value == null) {
                        buffer.putShort((short)-1);
                    } else {
                        byte[] dst = value.getBytes("UTF8");
                        buffer.putShort((short)dst.length);
                        buffer.put(dst);
                    }
                }
                
            } catch(UnsupportedEncodingException uee) {
                System.out.println("例外が発生しました。[exception=" + uee + "]");
                uee.printStackTrace();
                
            } catch(IllegalAccessException iae) {
                System.out.println("例外が発生しました。[exception=" + iae + "]");
                iae.printStackTrace();
            }
        }
        buffer.flip();
        buffer.putInt(buffer.limit());
    }

    /**
     * メッセージ登録
     * @param msg
     */

    protected void put(IMessage msg)
    {
        short code = msg.getCode();
        msgmap.put(code, msg.getClass());
    }

    // メッセージマッピング
    protected static HashMap<Short, Class> msgmap = new HashMap<Short, Class>();
}

Connection.java
package sample2DRPG;

import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SocketChannel;

import sample2DRPG.message.IMessage;
import sample2DRPG.message.MessageManager;

public class Connection {

    private static final int MIN_ID = 100000;
    private static final int MAX_ID = 199999;

    /**
     * コンストラクタ
     */

    public Connection(SocketChannel channel)
    {
        connid = nextId();
        this.channel = channel;
        buffer = ByteBuffer.allocate(8912);            
        buffer.order(ByteOrder.LITTLE_ENDIAN);
    }
    
    /**
     * チャネルからバイト配列を取得
     * @param channel
     * @return
     * @throws Exception
     */

    public boolean read() throws EOFException, IOException
    {
        buffer.clear();
        int bytesread = channel.read(buffer);

        if (bytesread == -1) {
            throw new EOFException();
        }
        
        if (buffer.position() < 6)
        {
            // 必要最低限のデータが取得できていない
            return false;
        }
        buffer.flip();
        int length = buffer.getInt(0);
        if (length > buffer.limit()) {
            // 1メッセージ分のデータが取得できていない
            buffer.position(buffer.limit());
            return false;
        }
        return true;
    }
    
    /**
     * メッセージ読み込み
     */

    public IMessage receive()
    {
        if (buffer.remaining() < 6) {
            // 必要最低限の容量が確保できていない
            return null;
        }
        
        // データ長を取得
        int length = buffer.getInt();
        if (length > buffer.remaining() + 4) {
            // 1メッセージ分のデータが取得できていない
            return null;
        }
        
        // メッセージマネージャからマスタメッセージを取得
        IMessage msg = MessageManager.get(buffer);

        // 新しいリミットを算出
        int newLimit = buffer.limit() - length;

        // バッファを切り詰めてリミット設定
        // TODO: 毎回 compact は修正する予定
        buffer.compact();
        buffer.limit(newLimit);

        // デバッグ用
        if (msg != null)
            System.out.println("RECV [" + msg + "]");
        return msg;
    }

    /**
     * コネクションの文字列表現
     */

    public String toString()
    {
        return "connid=" + connid;
    }
    
    /**
     * 次のコネクション識別番号を発行する
     * @return コネクション識別番号
     */

    private static int nextId()
    {
        currentId++;
        if (currentId > MAX_ID) {
            currentId = MIN_ID;
        }
        return currentId;
    }

    // コネクション識別番号
    protected int connid;
    
    // コネクションごとのチャネル
    protected SocketChannel channel;

    // 読み取り用バッファ
    protected ByteBuffer buffer;

    // コネクション識別番号の連番管理
    protected static int currentId = MIN_ID;
}

Server.java
package sample2DRPG;

import java.nio.*;
import java.io.*;
import java.nio.channels.*;
import java.net.*;
import java.util.*;
import sample2DRPG.message.*;

public class Server implements Runnable
{
    // デフォルトポート番号
    public static final int DEFAULT_PORT = 65000;

    // キー選択処理のタイムアウトを定義
    protected static final int SELECT_TIME = 100;
    
    // ポート番号
    protected int port;

    // バッファ
    protected ByteBuffer buffer;
    
    // 終了予定の接続
    protected LinkedList<SelectionKey> closeq = new LinkedList<SelectionKey>();

    // セレクタ
    private Selector selector;
    
    // サーバーソケットチャネル
    private ServerSocketChannel serverSocketChannel;

    /**
     * コンストラクタ
     * コンストラクタでポートを指定しなかった場合はデフォルトポート番号(65000)が使用されます。
     */

    public Server()
    {
        this(DEFAULT_PORT);
    }
    
    public Server(int port)
    {
        this.port = port;
                
        // 書き込み用バッファを初期化
        buffer = ByteBuffer.allocate(8912);            
        buffer.order(ByteOrder.LITTLE_ENDIAN);

        // メッセージマネージャ生成
        MessageManager.create();
    }

    /**
     * メイン処理
     */

    public void run()
    {
        try {
            selector = Selector.open();
    
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("Startup server [port=" + port + "]");
            
            // ループ処理
            while (true) {
                selector.select(SELECT_TIME);
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    it.remove();

                    if (key.isAcceptable()) {
                        System.out.println("ACCEPTABLE");
                        handleAcceptable(key);
                    }

                    if (key.isConnectable()) {
                        System.out.println("CONNECTABLE");
                    }

                    if (key.isReadable()) {
                        System.out.println("READABLE");
                        handleReadable(key);
                    }
                    
                    if (key.isWritable()) {
                        System.out.println("WRITABLE");
                    }

                    while ((key = closeq.poll()) != null) {
                        close(key);
                    }
                }
            }
            
        } catch(IOException ioe) {
            System.out.println("例外が発生したため終了します。[exception=" + ioe + "]");
        }
    }
    
    /**
     * 接続受入
     */

    protected void handleAcceptable(SelectionKey key)
    {
        try {
            SocketChannel channel = serverSocketChannel.accept();
            channel.configureBlocking(false);
    
            // 読み取り可能なキーを登録してコネクションを付属させる
            SelectionKey selKey = channel.register(selector, SelectionKey.OP_READ);
            selKey.attach(new Connection(channel));

        } catch(IOException e) {
            System.out.println("例外が発生しました。[exception=" + e + "]");
            e.printStackTrace();
        }
    }
    
    /**
     * 読込処理
     */

    protected void handleReadable(SelectionKey key)
    {
        try {
            Connection conn = (Connection)key.attachment();

            // チャネルからメッセージの受信を行います。
            // 1メッセージ分のデータが受信できた場合にはバイト配列からメッセージ
            // を生成します。受信したデータが1メッセージ未満の場合は、次回読み込
            // み後に再度、1メッセージ分の受信ができたかを確認します。読み込み中
            // のバッファは接続ごとにConnectionクラスで管理されます。
            if (conn.read())
            {
                IMessage msg;
                while((msg = conn.receive()) != null)
                {
                    if (msg instanceof LoginRequest)
                    {
                        // ログイン応答を返す
                        LoginResponse res = new LoginResponse();
                        res.connid = conn.connid;
                        send(conn, res);
                    }
                    else if (msg instanceof LogoutRequest)
                    {
                        // ログイン応答を返す
                        LogoutResponse res = new LogoutResponse();
                        send(conn, res);
                    }
                }
            }

        } catch(EOFException eof) {
            System.out.println("接続が切れました。[" + key.attachment() + "]");
            closeq.add(key);

        } catch(IOException ioe) {
            ioe.printStackTrace();
            closeq.add(key);

        } catch(Exception e) {
            System.out.println("例外が発生しました。[exception=" + e + "]");
            e.printStackTrace();
        }
    }

    /**
     * メッセージ送信
     * @param channel 送信対象
     * @param msg 送信するメッセージ
     */

    protected void send(Connection conn, IMessage msg)
    {
        try {
            MessageManager.set(buffer, msg);
            buffer.rewind();
            conn.channel.write(buffer);
            System.out.println("SEND [" + msg + "]");
            
        } catch(IOException ioe) {
            System.out.println("例外が発生しました。[exception=" + ioe + "]");
            ioe.printStackTrace();
        }
    }

    /**
     * 接続終了
     */

    protected void close(SelectionKey key)
    {
        try {
            key.cancel();
            key.channel().close();

        } catch(IOException e) {
            System.out.println("例外が発生しました。[exception=" + e + "]");
            e.printStackTrace();
        }
    }
    
    /**
     * エントリーポイント
     * @param argv
     * @throws Exception
     */

    public static void main(String[] argv) throws Exception
    {
        Server server = new Server();
        server.run();
    }
}

C# ソースコード(クライアント)

IMessage.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace ClientEmulator.messsage
{
    public interface IMessage
    {
        // メッセージ識別コード
        short Code { get; }
    }
}

LoginRequest.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace ClientEmulator.messsage
{
    public class LoginRequest : IMessage
    {
        // メッセージ識別コード
        public short Code
        {
            get { return 1; }
        }

        // ユーザー名
        public String username;

        // パスワード
        public String password;
    }
}

LoginResponse.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace ClientEmulator.messsage
{
    public class LoginResponse : IMessage
    {
        // メッセージ識別コード
        public short Code
        {
            get { return 2; }
        }

        // 接続識別番号
        public int connid;
    }
}

LogoutRequest.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace ClientEmulator.messsage
{
    public class LogoutRequest : IMessage
    {
        // メッセージ識別コード
        public short Code
        {
            get { return 3; }
        }
    }
}

LogoutResponse.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace ClientEmulator.messsage
{
    public class LogoutResponse : IMessage
    {
        // メッセージ識別コード
        public short Code
        {
            get { return 4; }
        }
    }
}

MessageManager.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Reflection;

namespace ClientEmulator.messsage
{
    public class MessageManager
    {
        // インスタンス
        private static MessageManager instance;

        private MessageManager()
        {
        }

        /// <summary>
        /// メッセージマネージャ生成
        /// </summary>
        public static void create()
        {
            if (instance != null)
            {
                throw new SystemException("MessageManagernbsp;は既に作成されています。");
            }
            instance = new MessageManager();
            instance.put(new LoginRequest());
            instance.put(new LoginResponse());
            instance.put(new LogoutRequest());
            instance.put(new LogoutResponse());
        }

        /// <summary>
        /// メッセージ取得
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        public static IMessage get(byte[] buffer)
        {
            // ストリーム初期化
            MemoryStream stream = new MemoryStream(buffer);
            BinaryReader reader = new BinaryReader(stream);

            // メッセージ識別番号取得
            short code = reader.ReadInt16();
            if (!msgmap.ContainsKey(code)) return null;

            // フィールド情報取得
            Type type = msgmap[code];
            FieldInfo[] fieldInfo = type.GetFields(BindingFlags.Instance |
                            BindingFlags.Public | BindingFlags.NonPublic);

            // メッセージをコピー
            IMessage msg = Activator.CreateInstance(type) as IMessage;

            // フィールド取得
            foreach (FieldInfo field in fieldInfo)
            {
                // 受信対象フラグ設定
                bool subject = true;
                if (field.IsLiteral || field.IsInitOnly || field.IsNotSerialized)
                    subject = false;

                Console.WriteLine("subject={0} [Name={1},Type={2},Value={3}]",
                    subject, field.Name, field.FieldType, field.GetValue(msg));

                // const,readonly,notSerialized は対象外
                if (false == subject) continue;

                if (field.FieldType == typeof(Int16))
                {
                    // 符号付き16bit整数読み込み
                    short value = reader.ReadInt16();
                    field.SetValue(msg, value);
                }
                else if (field.FieldType == typeof(Int32))
                {
                    // 符号付き32bit整数読み込み
                    int value = reader.ReadInt32();
                    field.SetValue(msg, value);
                }
                else if (field.FieldType == typeof(String))
                {
                    // 修正UTF-8読み込み(FF-FF の場合は null)
                    short length = reader.ReadInt16();
                    string value = null;
                    if (length > 0)
                    {
                        byte[] dst = new byte[length];
                        stream.Read(dst, 0, length);
                        value = Encoding.UTF8.GetString(dst);
                    }
                    field.SetValue(msg, value);
                }
            }
            return msg;
        }

        /// <summary>
        /// メッセージバイト変換
        /// [注意] Byte order は Little endian を使用しています
        /// </summary>
        /// <param name="msg">メッセージ</param>
        /// <returns></returns>
        public static byte[] set(IMessage msg)
        {
            // ストリーム初期化
            MemoryStream stream = new MemoryStream(8912);
            BinaryWriter writer = new BinaryWriter(stream);

            // フィールド情報取得
            Type type = msg.GetType();
            FieldInfo[] fieldInfo = type.GetFields(BindingFlags.Instance |
                            BindingFlags.Public | BindingFlags.NonPublic);

            // メッセージ識別番号設定
            stream.Position = 4;
            writer.Write(msg.Code);

            // フィールド設定
            foreach (FieldInfo field in fieldInfo)
            {
                // 送信対象フラグ設定
                bool subject = true;
                if (field.IsLiteral || field.IsInitOnly || field.IsNotSerialized)
                    subject = false;

                Console.WriteLine("subject={0} [Name={1},Type={2},Value={3}]",
                    subject, field.Name, field.FieldType, field.GetValue(msg));

                // const,readonly,notSerialized は対象外
                if (false == subject) continue;

                if (field.FieldType == typeof(Int16))
                {
                    // 符号付き16bit整数書き込み
                    writer.Write((Int16)field.GetValue(msg));
                }
                else if (field.FieldType == typeof(Int32))
                {
                    // 符号付き32bit整数書き込み
                    writer.Write((Int32)field.GetValue(msg));
                }
                else if (field.FieldType == typeof(String))
                {
                    // 修正UTF-8書き込み(nullの場合は FF-FF)
                    // BinaryWriter の Write(String) は使用しない
                    string value = (String)field.GetValue(msg);
                    if (value == null)
                    {
                        writer.Write((short)-1);
                    }
                    else
                    {
                        byte[] dst = Encoding.UTF8.GetBytes(value);
                        writer.Write((short)dst.Length);
                        writer.Write(dst);
                    }
                }
            }
            // データ長を書き込み
            int limit = (int)stream.Position;
            stream.Position = 0;
            writer.Write(limit);
            stream.Capacity = limit;
            return stream.GetBuffer();
        }
        
        /// <summary>
        /// メッセージ登録
        /// </summary>
        /// <param name="msg"></param>
        protected void put(IMessage msg)
        {
            short code = msg.Code;
            msgmap.Add(code, msg.GetType());
        }

        // メッセージマッピング
        protected static Dictionary<short, Type> msgmap
                   = new Dictionary<short, Type>();
    }
}

ClientEmulator.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;
using ClientEmulator.messsage;

namespace ClientEmulator
{
    public class ClientEmulator : Form
    {
        private CheckBox chkConnect;
        private Label lblHost;
        private Label lblPort;
        private TextBox txtHost;
        private TextBox txtPort;
        private CheckBox chkLogin;
        private Label lblName;
        private Label lblPass;
        private TextBox txtPassword;
        private TextBox txtUsername;
        private Panel pnlLogin;
        private GroupBox grpCommand;
        private GroupBox grpConnected;

        public ClientEmulator()
        {
            InitializeComponent();
            MessageManager.create();
        }

        #region private void InitializeComponent()
        private void InitializeComponent()
        {
            this.chkConnect = new System.Windows.Forms.CheckBox();
            this.lblHost = new System.Windows.Forms.Label();
            this.lblPort = new System.Windows.Forms.Label();
            this.txtHost = new System.Windows.Forms.TextBox();
            this.txtPort = new System.Windows.Forms.TextBox();
            this.grpConnected = new System.Windows.Forms.GroupBox();
            this.chkLogin = new System.Windows.Forms.CheckBox();
            this.pnlLogin = new System.Windows.Forms.Panel();
            this.lblName = new System.Windows.Forms.Label();
            this.txtUsername = new System.Windows.Forms.TextBox();
            this.lblPass = new System.Windows.Forms.Label();
            this.txtPassword = new System.Windows.Forms.TextBox();
            this.grpCommand = new System.Windows.Forms.GroupBox();
            this.grpConnected.SuspendLayout();
            this.pnlLogin.SuspendLayout();
            this.SuspendLayout();
            // 
            // chkConnect
            // 
            this.chkConnect.Appearance = System.Windows.Forms.Appearance.Button;
            this.chkConnect.AutoSize = true;
            this.chkConnect.Location = new System.Drawing.Point(22310);
            this.chkConnect.MinimumSize = new System.Drawing.Size(570);
            this.chkConnect.Name = "chkConnect";
            this.chkConnect.Size = new System.Drawing.Size(5722);
            this.chkConnect.TabIndex = 2;
            this.chkConnect.Text = "Connect";
            this.chkConnect.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
            this.chkConnect.UseVisualStyleBackColor = true;
            this.chkConnect.CheckedChanged += new System.EventHandler(this.chkConnect_CheckedChanged);
            // 
            // lblHost
            // 
            this.lblHost.AutoSize = true;
            this.lblHost.Location = new System.Drawing.Point(1215);
            this.lblHost.Name = "lblHost";
            this.lblHost.Size = new System.Drawing.Size(2912);
            this.lblHost.TabIndex = 1;
            this.lblHost.Text = "Host";
            // 
            // lblPort
            // 
            this.lblPort.AutoSize = true;
            this.lblPort.Location = new System.Drawing.Point(14015);
            this.lblPort.Name = "lblPort";
            this.lblPort.Size = new System.Drawing.Size(2612);
            this.lblPort.TabIndex = 2;
            this.lblPort.Text = "Port";
            // 
            // txtHost
            // 
            this.txtHost.ImeMode = System.Windows.Forms.ImeMode.Disable;
            this.txtHost.Location = new System.Drawing.Point(4712);
            this.txtHost.Name = "txtHost";
            this.txtHost.Size = new System.Drawing.Size(8719);
            this.txtHost.TabIndex = 0;
            this.txtHost.Text = "localhost";
            // 
            // txtPort
            // 
            this.txtPort.ImeMode = System.Windows.Forms.ImeMode.Disable;
            this.txtPort.Location = new System.Drawing.Point(17212);
            this.txtPort.MaxLength = 5;
            this.txtPort.Name = "txtPort";
            this.txtPort.Size = new System.Drawing.Size(4219);
            this.txtPort.TabIndex = 1;
            this.txtPort.Text = "65000";
            // 
            // grpConnected
            // 
            this.grpConnected.Controls.Add(this.chkLogin);
            this.grpConnected.Controls.Add(this.pnlLogin);
            this.grpConnected.Enabled = false;
            this.grpConnected.Location = new System.Drawing.Point(1238);
            this.grpConnected.Name = "grpConnected";
            this.grpConnected.Size = new System.Drawing.Size(26876);
            this.grpConnected.TabIndex = 3;
            this.grpConnected.TabStop = false;
            this.grpConnected.Text = "未接続";
            // 
            // chkLogin
            // 
            this.chkLogin.Appearance = System.Windows.Forms.Appearance.Button;
            this.chkLogin.AutoSize = true;
            this.chkLogin.Location = new System.Drawing.Point(20344);
            this.chkLogin.MinimumSize = new System.Drawing.Size(590);
            this.chkLogin.Name = "chkLogin";
            this.chkLogin.Size = new System.Drawing.Size(5922);
            this.chkLogin.TabIndex = 7;
            this.chkLogin.Text = "ログイン";
            this.chkLogin.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
            this.chkLogin.UseVisualStyleBackColor = true;
            this.chkLogin.CheckedChanged += new System.EventHandler(this.chkLogin_CheckedChanged);
            // 
            // pnlLogin
            // 
            this.pnlLogin.Controls.Add(this.lblName);
            this.pnlLogin.Controls.Add(this.txtUsername);
            this.pnlLogin.Controls.Add(this.lblPass);
            this.pnlLogin.Controls.Add(this.txtPassword);
            this.pnlLogin.Location = new System.Drawing.Point(818);
            this.pnlLogin.Name = "pnlLogin";
            this.pnlLogin.Size = new System.Drawing.Size(18951);
            this.pnlLogin.TabIndex = 4;
            // 
            // lblName
            // 
            this.lblName.AutoSize = true;
            this.lblName.Location = new System.Drawing.Point(36);
            this.lblName.Name = "lblName";
            this.lblName.Size = new System.Drawing.Size(5812);
            this.lblName.TabIndex = 7;
            this.lblName.Text = "UserName";
            // 
            // txtUsername
            // 
            this.txtUsername.Location = new System.Drawing.Point(623);
            this.txtUsername.MaxLength = 256;
            this.txtUsername.Name = "txtUsername";
            this.txtUsername.Size = new System.Drawing.Size(12419);
            this.txtUsername.TabIndex = 5;
            // 
            // lblPass
            // 
            this.lblPass.AutoSize = true;
            this.lblPass.Location = new System.Drawing.Point(231);
            this.lblPass.Name = "lblPass";
            this.lblPass.Size = new System.Drawing.Size(5412);
            this.lblPass.TabIndex = 6;
            this.lblPass.Text = "Password";
            // 
            // txtPassword
            // 
            this.txtPassword.Location = new System.Drawing.Point(6228);
            this.txtPassword.MaxLength = 256;
            this.txtPassword.Name = "txtPassword";
            this.txtPassword.PasswordChar = '*';
            this.txtPassword.Size = new System.Drawing.Size(12419);
            this.txtPassword.TabIndex = 6;
            // 
            // grpCommand
            // 
            this.grpCommand.Enabled = false;
            this.grpCommand.Location = new System.Drawing.Point(12120);
            this.grpCommand.Name = "grpCommand";
            this.grpCommand.Size = new System.Drawing.Size(268135);
            this.grpCommand.TabIndex = 8;
            this.grpCommand.TabStop = false;
            this.grpCommand.Text = "ログアウト";
            // 
            // ClientEmulator
            // 
            this.ClientSize = new System.Drawing.Size(292266);
            this.Controls.Add(this.grpCommand);
            this.Controls.Add(this.grpConnected);
            this.Controls.Add(this.txtPort);
            this.Controls.Add(this.txtHost);
            this.Controls.Add(this.lblPort);
            this.Controls.Add(this.lblHost);
            this.Controls.Add(this.chkConnect);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            this.MaximizeBox = false;
            this.Name = "ClientEmulator";
            this.Text = "ClientEmulator";
            this.Load += new System.EventHandler(this.ClientEmulator_Load);
            this.grpConnected.ResumeLayout(false);
            this.grpConnected.PerformLayout();
            this.pnlLogin.ResumeLayout(false);
            this.pnlLogin.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();

        }
        #endregion

        /// <summary>
        /// Formロード時の処理
        /// </summary>
        private void ClientEmulator_Load(object sender, EventArgs e)
        {
            chkConnect.Select();
        }

        /// <summary>
        /// 接続/切断ボタン押下
        /// </summary>
        private void chkConnect_CheckedChanged(object sender, EventArgs e)
        {
            CheckBox cb = sender as CheckBox;
            bool enable = false;
            try
            {
                if (cb.Checked)
                {
                    // サーバーへ接続
                    connect();

                    // ボタンを次の操作の名称に変更
                    cb.Text = "Close";

                    // グループタイトル更新
                    grpConnected.Text = "接続中 [ " + tcpClient.Client.RemoteEndPoint + " ]";

                    enable = true;
                }
                else
                {
                    // サーバーから切断
                    close();

                    // ボタンを次の操作の名称に変更
                    cb.Text = "Connect";

                    // グループタイトル更新
                    grpConnected.Text = "未接続";

                    // ログインパネル初期化
                    initLoginPanel();

                    // コマンドグループ初期化
                    initCommandGroup();
                }

                // グループEnable設定
                grpConnected.Enabled = enable;

                // テキストボックスEnable設定
                txtHost.Enabled = !enable;
                txtPort.Enabled = !enable;
                pnlLogin.Enabled = enable;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                cb.Checked = !cb.Checked;
            }
        }

        /// <summary>
        /// 接続処理
        /// </summary>
        private void connect()
        {
            string host = txtHost.Text;
            int port = 0;
            if (!Int32.TryParse(txtPort.Text, out port))
            {
                MessageBox.Show("Portには数字を入力してください。");
                return;
            }

            // TCP クライアントを生成
            tcpClient = new TcpClient();

            // サーバーへ接続する
            tcpClient.Connect(host, port);

            // ストリーム生成
            reader = new BinaryReader(tcpClient.GetStream());
        }

        /// <summary>
        /// 切断処理
        /// </summary>
        private void close()
        {
            if (tcpClient == nullreturn;

            if (tcpClient.Connected == falsereturn;

            // サーバーから切断する
            tcpClient.Close();

            tcpClient = null;
        }

        /// <summary>
        /// ログインパネルの初期化
        /// </summary>
        private void initLoginPanel()
        {
            txtUsername.Clear();
            txtPassword.Clear();
            chkLogin.Text = "ログイン";
            chkLogin.Checked = false;
        }

        /// <summary>
        /// コマンドグループ初期化
        /// </summary>
        private void initCommandGroup()
        {
            grpCommand.Text = "ログアウト";
            grpCommand.Enabled = false;
        }

        /// <summary>
        /// ログイン/ログオフ
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void chkLogin_CheckedChanged(object sender, EventArgs e)
        {
            CheckBox cb = sender as CheckBox;
            bool enable = false;
            try
            {
                if (cb.Checked)
                {
                    // ログイン処理
                    login();

                    // ログイン結果を受信
                    IMessage msg = receive();

                    // 想定外の結果はエラー
                    bool result = msg is LoginResponse;
                    if (!result) throw new Exception("response failed...");

                    // 識別番号を設定
                    LoginResponse res = msg as LoginResponse;
                    this.connid = res.connid;

                    // コントロール設定
                    enable = true;
                    cb.Text = "ログアウト";
                    grpCommand.Text = String.Format("ログイン [id={0}]", connid);
                }
                else
                {
                    // ログアウト処理
                    logout();

                    // ログアウト結果を受信
                    IMessage msg = receive();

                    // 想定外の結果はエラー
                    bool result = msg is LogoutResponse;
                    if (!result) throw new Exception("response failed...");

                    // 識別番号をクリア
                    this.connid = 0;

                    // コントロール設定
                    enable = false;
                    cb.Text = "ログイン";
                    grpCommand.Text = "ログアウト";
                }
                pnlLogin.Enabled = !enable;
                grpCommand.Enabled = enable;
            }
            catch (IOException ioe)
            {
                MessageBox.Show(ioe.Message);
                cb.Checked = !cb.Checked;
            }
            catch (Exception)
            {
                chkConnect.Checked = false;
            }
        }

        /// <summary>
        /// メッセージ送信
        /// </summary>
        /// <param name="msg"></param>
        private void send(IMessage msg)
        {
            if (tcpClient == nullreturn;

            byte[] dst = MessageManager.set(msg);
            tcpClient.Client.Send(dst);
            Console.WriteLine("Send [msg={0}]", BitConverter.ToString(dst));
        }

        /// <summary>
        /// メッセージ受信
        /// </summary>
        /// <returns></returns>
        private IMessage receive()
        {
            if (tcpClient == nullreturn null;

            int length = reader.ReadInt32();
            if (length == 0throw new Exception("EOF");

            byte[] dst = reader.ReadBytes(length - 4);
            return MessageManager.get(dst);
        }

        /// <summary>
        /// ログイン
        /// </summary>
        private void login()
        {
            LoginRequest req = new LoginRequest();
            req.username = txtUsername.Text;
            req.password = txtPassword.Text;
            if (req.username.Length == 0) req.username = "Anonymous";
            send(req);
        }

        /// <summary>
        /// ログアウト
        /// </summary>
        private void logout()
        {
            LogoutRequest req = new LogoutRequest();
            send(req);
        }

        // TCPクライアント
        private TcpClient tcpClient;

        // 受信用ストリーム
        private BinaryReader reader;

        // 接続識別番号
        private int connid;
    }
}

次回予定

ClientEmulator を更に拡張して実際に Command オブジェクトの受信を行います。

それに伴いマルチスレッド化する予定です。

不具合修正報告

スイマセン、不具合がありました

JAVA

MessageManager.java
    line:24
    修正前    throw new RuntimeException("Executor は既に作成されています。");
    修正後    throw new RuntimeException("MessageManager は既に作成されています。");

Connection.java
    line:49
    修正前    int length = buffer.getInt();
    修正後    int length = buffer.getInt(0);

    line:63
    修正前    if (buffer.limit() < 6) {
    修正後    if (buffer.remaining() < 6) {

    line:69
    修正前    int length = buffer.getInt(0);
    修正後    int length = buffer.getInt();

    line:70
    修正前    if (length > buffer.limit()) {
    修正後    if (length > buffer.remaining() + 4) {

    line:77
    修正前    buffer.compact();
    修正後    // 新しいリミットを算出
              int newLimit = buffer.limit() - length;

              // バッファを切り詰めてリミット設定
              // TODO: 毎回 compact は修正する予定
              buffer.compact();
              buffer.limit(newLimit);


C#
MessageManager.cs
    line:25
    修正前    throw new SystemException("Executor は既に作成されています。");
    修正後    throw new SystemException("MessageManager は既に作成されています。");

ClientEmulator.cs
    line:238
    修正前    grpConnected.Text = "接続中 [ " + tcpClient.Client.LocalEndPoint + " ]";
    修正後    grpConnected.Text = "接続中 [ " + tcpClient.Client.RemoteEndPoint + " ]";



更新日 2008/06/02