まえがき
私はプログラミングに携わっており、年齢によって大学でこれを学ぶ機会はありませんが、勉強への渇望があります。 私が最近書いたプログラムの1つにあなたの注意を喚起したいと思います。プログラムの欠陥、改善できること、私がどの方向に向かっているのか、何のために勉強するのかを知りたいです。
このプログラムは、クライアントとサーバーアプリケーションで使用できるマルチクライアントネットワークプロトコルであり、パケットとそのハンドラーのみを構成します。
プロジェクトは3つの部分に分けることができます。
- サーバー側
- クライアント部
- サーバーとクライアントの共通部分
一般部
パッケージ
すべてのパッケージの共通インターフェース:
namespace Common { interface IPacket { void Write(BinaryWriter writer);
インターフェイスを継承する抽象クラス:
namespace Common { abstract class PacketBase : IPacket { protected PacketBase(int id) { this.Id = id; } public int Id { get; private set; } protected void WriteHeader(BinaryWriter writer) { writer.Write(this.Id); }
パッケージハンドラー
すべてのハンドラーの共通インターフェース:
namespace Common { interface IPacketHandler : ICloneable { void Read();
パッケージハンドラーインターフェイスを継承する抽象クラス。
namespace Common { abstract class PacketHandlerBase : IPacketHandler { public PacketHandlerBase() { } public BinaryReader Reader { get; set; } public object Context { get; set; } public virtual void Read() { }
コンテキストは、接続が関連付けられているオブジェクトであり、パケットハンドラに役立つ情報です。 各ハンドラーは、このコンテキストオブジェクトへのリンクを受け取り、必要に応じて使用します。
ハンドラーストア
namespace Common { class PacketHandlerStorage { public PacketHandlerStorage() { this._storage = new Dictionary(); } private Dictionary _storage; public PacketHandlerBase GetHandlerById(int id) { PacketHandlerBase x = this._storage[id]; return (PacketHandlerBase)x.Clone();
GetHandlerByIdメソッドは、対応するパケットハンドラーをIDで返します。 AddHandlerはハンドラーをリポジトリーに追加します。
パッケージの読み取りと処理のためのクラス
namespace Common { class InputProcessor { public InputProcessor(NetworkStream stream, Connection connection, PacketHandlerStorage handlers) { this._connection = connection; this._stream = stream; this.Handlers = handlers; Reader = new BinaryReader(this._stream); this._started = false; } private NetworkStream _stream; private Connection _connection;
コンストラクターは、ネットワークストリーム、Connectionクラスのオブジェクト、およびハンドラーストアのオブジェクトを受け入れます。 _handlePacketは、パッケージのIDを読み取り、そのハンドラーを受け取り、読み取りおよび処理メソッドを呼び出します。 ループ内の_workerは_handlePacketを呼び出します。 Runメソッドはスレッドを作成し、その中で_workerを開始します。
パッケージレコードクラス
namespace Common { class OutputProccessor { public OutputProccessor(NetworkStream stream) { this._stream = stream; _writer = new BinaryWriter(this._stream); this.Packets = new Queue(); this._lock = new ManualResetEvent(true); } private Thread _newThread; private NetworkStream _stream; private BinaryWriter _writer; private Queue Packets; private ManualResetEvent _lock; private void _worker() { while (true) { this._lock.WaitOne(); if (this.Packets.Count > 0)
_workメソッドでは、パケット送信メソッドがループで呼び出されますが、0より大きい場合は別のスレッドのRunメソッドが_workerを開始します。
接続クラス
クラス接続。 名前から、これが接続を担当するクラスであることは明らかです。
namespace Common { class Connection { public Connection(TcpClient client, PacketHandlerStorage handlers) { this._client = client; this.Stream = this._client.GetStream(); this._inputProccessor = new InputProcessor(this.Stream, this, handlers); this._outputProccessor = new OutputProccessor(this.Stream); } private TcpClient _client; private InputProcessor _inputProccessor;
tcpClientおよびハンドラーストレージオブジェクトは、コンストラクターに受け入れられます。 Runメソッドは、パケットストリームの読み取りと送信を開始します。 Sendメソッドはパケットを送信します。 Receiveメソッドでは、ネイティブインスタンスがハンドラコンテキストに書き込まれ、処理メソッドが呼び出されます。
サーバー側
クライアントコンテキスト
Connectionクラスは、クライアントとサーバー間の接続の操作を担当し、その逆も同様です。 ハンドラーには、Connectionインスタンスが保存されるContextフィールドがあります。 サーバーのClientContextクラス。
namespace Server { class ClientContext { public ClientContext(Connection connection) { this.Connection = connection; } public Connection Connection { get; set; } } }
ClientContextFactory
ClientContextFactoryクラスは、Connectionオブジェクトによって新しいClientContextオブジェクトを取得するために使用されます。
namespace Server { class ClientContextFactory : ContextFactory { public override object MakeContext(Connection connection) { return new ClientContext(connection); } } }
プロトコルバージョンクラス
ServerHandlersV1ハンドラーストアの継承者。 ハンドラーがコンストラクターに追加されます。 したがって、異なるパケットハンドラを使用してプロトコルの異なるバージョンを作成し、PacketHandlerStorageの代わりに必要なプロトコルバージョンのクラスに置き換えることができます。
namespace Server { class ServerHandlersV1 : PacketHandlerStorage { public ServerHandlersV1() {
サーバー
namespace Server { class Server { public Server(int port, ContextFactory contextFactory) { this.Port = port; this.Started = false; this._contextFactory = contextFactory; this._connectios = new List(); } private Thread _newThread; private TcpListener _listner; private List _connectios;
コンストラクターはポートとプロトコルバージョンを受け入れます。 _workerメソッドで、tcpListnerを起動します。 次に、クライアントはループで受け入れられ、Connectionオブジェクトとそのコンテキストが作成され、Connectionが起動されて接続のリストに追加されます。 Runメソッドはスレッドを作成し、その中で_workerを開始します。
クライアント部
プロトコルバージョンクラス
ハンドラーストアの後継はClientHandlersV1です。
namespace Client { class ClientHandlersV1 : PacketHandlerStorage { public ClientHandlersV1() {
お客様
namespace Client { class Client { public Client(string ip, int port, PacketHandlerStorage handlers) { this._tcpClient = new TcpClient(ip, port); this._connection = new Connection(this._tcpClient, handlers); this._connection.Context = this; this._connection.Run(); } private TcpClient _tcpClient; private Connection _connection; } }
コンストラクターは、IP、ポート、および必要なプロトコルバージョンのクラスオブジェクトを受け入れ、接続が確立されます。
例
シンプルなコンソールチャット。
サーバー
namespace Chat_server { class Program { public static Server.Server Server { get; set; }
ウェルカムパッケージ:
using Common; namespace Server.Packets { class HelloPacket : PacketBase { public HelloPacket() : base(0) {}
メッセージパッケージ:
using Common; namespace Server.Packets { class MessagePacket : PacketBase { public MessagePacket(string nick, string message) : base(1) { this._nick = nick; this._message = message; } private string _nick; private string _message; protected override void WriteBody(System.IO.BinaryWriter writer) { writer.Write(this._nick); writer.Write(this._message); } } }
WriteBodyメソッドは、パッケージ本体、つまり 送信者のニックネームと彼のメッセージ。
ようこそパケットハンドラー:
using Common; using Chat_server; using System; namespace Server.PacketHandlers { class HelloPacketHandler : PacketHandlerBase { public HelloPacketHandler() { } private string _nick; public override void Read() { this._nick = this.Reader.ReadString();
ウェルカムパケットで、クライアントはニックネームを送信します。ニックネームはReadメソッドで読み取られ、Handleメソッドでリストに追加されます。
メッセージ付きのメッセージハンドラ:
using Common; using Server; using Server.Packets; using Chat_server; namespace Server.PacketHandlers { class MessagePacketHandler : PacketHandlerBase { public MessagePacketHandler() { } private string _nick; private string _message; public override void Read() { this._nick = this.Reader.ReadString();
ニックネームとメッセージは、Readメソッドで読み取られます。 ハンドラーはこのクライアントにのみパケットを送信できるため、サーバークラスに、接続されているすべてのクライアントに送信されたメッセージを送信するメソッドを作成しました。
public void SendMessage(string nick, string message, Connection sender) { foreach (Connection connection in this._connectios) if(connection != sender) connection.Send(new MessagePacket(nick, message)); }
ServerHandlersV1クラスのハンドラー(PacketHandlerStorageの子孫)。
using Common; using Server.PacketHandlers; namespace Server { class ServerHandlersV1 : PacketHandlerStorage { public ServerHandlersV1() { AddHandler(0, new HelloPacketHandler()); AddHandler(1, new MessagePacketHandler()); } } }
お客様
namespace Chat_client { class Program { public static Client.Client Client { get; set; }
ループはダイヤルされたメッセージを送信します。 なぜなら パッケージを送信する方法はなく、Clientクラスでメソッドを作成しました。
public void SendMessagePacket(string message) { this._connection.Send(new MessagePacket(Program.Nick, message)); }
ウェルカムパッケージ:
using Common; using Chat_client; namespace Client.Packets { class HelloPacket : PacketBase { public HelloPacket() : base(0) {}
ニックネームはWriteBodyメソッドで送信されます。
メッセージパッケージ:
using Common; namespace Client.Packets { class MessagePacket : PacketBase { public MessagePacket(string nick, string message) : base(1) { this._nick = nick; this._message = message; } private string _nick; private string _message; protected override void WriteBody(System.IO.BinaryWriter writer) { writer.Write(this._nick); writer.Write(this._message); } } }
ニックネームとメッセージが送信されます。
ようこそパケットハンドラー:
using Common; namespace Client.PacketHandlers { class HelloPacketHandler : PacketHandlerBase { public HelloPacketHandler() { } public override object Clone() { return new HelloPacketHandler(); } } }
彼は何もしません。
メッセージバッチハンドラー:
using Common; namespace Client.PacketHandlers { class MessagePacketHandler : PacketHandlerBase { public MessagePacketHandler() { } private string _nick; private string _message; public override void Read() { this._nick = this.Reader.ReadString();
Readメソッドでは、ニックネームとメッセージが受信されます。 Handleメソッドでは、メッセージがコンソールに表示されます。
ClientHandlersV1のハンドラー。
using Common; using Client.PacketHandlers; namespace Client { class ClientHandlersV1 : PacketHandlerStorage { public ClientHandlersV1() { AddHandler(0, new HelloPacketHandler()); AddHandler(1, new MessagePacketHandler()); } } }
シンプルなマルチクライアントコンソールチャットの準備ができました!

ダウンロードプロトコルチャットのダウンロード(サーバー)チャットのダウンロード(クライアント)