Cのマルチクライアントネットワークプロトコル#

まえがき



私はプログラミングに携わっており、年齢によって大学でこれを学ぶ機会はありませんが、勉強への渇望があります。 私が最近書いたプログラムの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()); } } }
      
      







シンプルなマルチクライアントコンソールチャットの準備ができました!



画像

画像



ダウンロードプロトコル



チャットのダウンロード(サーバー)



チャットのダウンロード(クライアント)



All Articles