まえがき
私はプログラミングに携わっており、年齢によって大学でこれを学ぶ機会はありませんが、勉強への渇望があります。 私が最近書いたプログラムの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); } // protected virtual void WriteBody(BinaryWriter writer) { } // public void Write(BinaryWriter writer) { this.WriteHeader(writer); this.WriteBody(writer); } // } }
パッケージハンドラー
すべてのハンドラーの共通インターフェース:
namespace Common { interface IPacketHandler : ICloneable { void Read(); // void Handle(); // } }
パッケージハンドラーインターフェイスを継承する抽象クラス。
namespace Common { abstract class PacketHandlerBase : IPacketHandler { public PacketHandlerBase() { } public BinaryReader Reader { get; set; } public object Context { get; set; } public virtual void Read() { } // public virtual void Handle() { } // public abstract Object Clone(); //, } }
コンテキストは、接続が関連付けられているオブジェクトであり、パケットハンドラに役立つ情報です。 各ハンドラーは、このコンテキストオブジェクトへのリンクを受け取り、必要に応じて使用します。
ハンドラーストア
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(); // Clone } public void AddHandler(int id, PacketHandlerBase handler) { this._storage.Add(id, handler); } } }
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; // private Thread _newThread; private BinaryReader Reader; private bool _started; public PacketHandlerStorage Handlers { get; set; } private void _handlePacket() { int id = Reader.ReadInt32(); // id PacketHandlerBase handler = this.Handlers.GetHandlerById(id); // handler.Reader = this.Reader; handler.Read(); // this._connection.Receive(handler); // } private void _worker() { while (!this._started) { _handlePacket(); } } public void Run() { this._newThread = new Thread(this._worker); this._newThread.Start(); } } }
コンストラクターは、ネットワークストリーム、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) // this.Packets.Dequeue().Write(this._writer); // else this._lock.Reset(); } } public void Send(PacketBase packet) // { this.Packets.Enqueue(packet); this._lock.Set(); } public void Run() { this._newThread = new Thread(this._worker); this._newThread.Start(); } } }
_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; // / private OutputProccessor _outputProccessor; // public NetworkStream Stream { get; private set; } public object Context { get; set; } public void Run() { this._inputProccessor.Run(); this._outputProccessor.Run(); } public void Send(PacketBase packet) { this._outputProccessor.Send(packet); } public void Receive(PacketHandlerBase handler) { handler.Context = this.Context; handler.Handle(); } } }
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() { //AddHandler(0, new SomePacketHandler1()); //AddHandler(1, new SomePacketHandler2()); } } }
サーバー
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; // public int Port { get; set; } public bool Started { get; private set; } public PacketHandlerStorage Handlers { get; set; } // private ContextFactory _contextFactory { get; set; } private void _worker() { this._listner = new TcpListener(IPAddress.Any, this.Port); this._listner.Start(); this.Started = true; while (this.Started) { TcpClient client = this._listner.AcceptTcpClient(); Connection connection = new Connection(client, this.Handlers); connection.Context = this._contextFactory.MakeContext(connection); connection.Run(); this._connectios.Add(connection); } } public void Run() { this._newThread = new Thread(this._worker); this._newThread.Start(); } } }
コンストラクターはポートとプロトコルバージョンを受け入れます。 _workerメソッドで、tcpListnerを起動します。 次に、クライアントはループで受け入れられ、Connectionオブジェクトとそのコンテキストが作成され、Connectionが起動されて接続のリストに追加されます。 Runメソッドはスレッドを作成し、その中で_workerを開始します。
クライアント部
プロトコルバージョンクラス
ハンドラーストアの後継はClientHandlersV1です。
namespace Client { class ClientHandlersV1 : PacketHandlerStorage { public ClientHandlersV1() { //AddHandler(0, new SomePacketHandler1()); //AddHandler(1, new SomePacketHandler2()); } } }
お客様
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; } // public static List<string> Contacts { get; set; } // static void Main(string[] args) { Contacts = new List<string>(); Server = new Server.Server(1698, new Server.ClientContextFactory(), new Server.ServerHandlersV1()); Server.Run(); DateTime now = new DateTime(); now = DateTime.Now; System.Console.WriteLine("Server started at " + now.Hour + ":" + now.Minute + ":" + now.Second); } } }
ウェルカムパッケージ:
using Common; namespace Server.Packets { class HelloPacket : PacketBase { public HelloPacket() : base(0) {} //id - 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(); // } public override void Handle() { Program.Contacts.Add(this._nick); // DateTime now = new DateTime(); now = DateTime.Now; System.Console.WriteLine(now.Hour + ":" + now.Minute + ":" + now.Second + " " + this._nick + " connected"); } public override object Clone() { return new HelloPacketHandler(); } } }
ウェルカムパケットで、クライアントはニックネームを送信します。ニックネームは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(); // this._message = this.Reader.ReadString(); // } public override void Handle() { Program.Server.SendMessage(this._nick, this._message, ((ClientContext)Context).Connection); // } public override object Clone() { return new MessagePacketHandler(); } } }
ニックネームとメッセージは、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; } // public static string Nick { get; set; } // public static string IpAddress { get; set; } //Ip static void Main(string[] args) { string message; Console.Write(" : "); Nick = Console.ReadLine(); Console.Write("IP : "); IpAddress = Console.ReadLine(); Console.Clear(); Client = new Client.Client(IpAddress, 1698, new Client.ClientHandlersV1()); while (true) { message = Console.ReadLine(); Client.SendMessagePacket(message); } } } }
ループはダイヤルされたメッセージを送信します。 なぜなら パッケージを送信する方法はなく、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) {} //id - 0 protected override void WriteBody(System.IO.BinaryWriter writer) { writer.Write(Program.Nick); } } }
ニックネームは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(); // this._message = this.Reader.ReadString(); // } public override void Handle() { System.Console.ForegroundColor = System.ConsoleColor.Green; System.Console.Write(this._nick + ": "); System.Console.ForegroundColor = System.ConsoleColor.Gray; System.Console.WriteLine(this._message); } public override object Clone() { return new MessagePacketHandler(); } } }
Readメソッドでは、ニックネームとメッセージが受信されます。 Handleメソッドでは、メッセージがコンソールに表示されます。
ClientHandlersV1のハンドラー。
using Common; using Client.PacketHandlers; namespace Client { class ClientHandlersV1 : PacketHandlerStorage { public ClientHandlersV1() { AddHandler(0, new HelloPacketHandler()); AddHandler(1, new MessagePacketHandler()); } } }
シンプルなマルチクライアントコンソールチャットの準備ができました!
ダウンロードプロトコル
チャットのダウンロード(サーバー)
チャットのダウンロード(クライアント)