Многоклиентский сетевой протокол на C#

Предисловие


Занимаюсь программированием, по возрасту не имею возможности обучатся этому в вузе, а тяга к изучению есть. Хочу представить Вашему вниманию одну из моих недавно написанных программ, хотелось бы узнать свои недоработки в ней, какие вещи можно было бы улучшить и в какую сторону двигся, что изучать для этого.
Программа представляет собой многоклиентский сетевой протокол, который можно было бы использовать в любом клиент-серверном приложении, настраивая только пакеты и их обработчики.



Проект можно разделить на три части:
  • серверная часть
  • клиентская часть
  • общая часть для сервера и клиента


Общая часть


Пакет



Общий интерфейс для всех пакетов:

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(); //Метод, возвращающий клона обработчика
    }
}


Context — это объект с которым связано соединение, полезная информация для обработчиков пакетов. Каждый обработчик получит ссылку на этот объект контекста и воспользуется им, если пожелает.

Хранилище обработчиков



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.

Класс соединения


Класс Connection. Из названия понятно что это класс, отвечающий за работу соединения.

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 записывается в Context обработчика собственный экземпляр и вызывается метод обработки.

Серверная часть


Клиентский контекст



Класс Connection отвечает за работу соединения, у клиента с сервером и наоборот. У обработчиков есть поле Context в котором хранится экземпляр Connection. Класс ClientContext для сервера.

namespace Server
{
    class ClientContext
    {
        public ClientContext(Connection connection) { this.Connection = connection; }

        public Connection Connection { get; set; }
    }
}


ClientContextFactory



Класс ClientContextFactory служит для получения нового объекта ClientContext по объекту Connection

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());
        }
    }
}


Простой многоклиентский консольный чат готов!

image
image

Скачать протокол

Скачать чат (сервер)

Скачать чат (клиент)

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 84

    +10
    Недоработка одна — называется WCF. Она полностью заменяет ту тонну кода, которую вы написали.
      +7
      Я писал эту тонну кода для развития и получения опыта. Если уже есть куча вещей, что теперь изобретать все время что то новое чтобы попрактиковаться?
        –3
        Ну, если для себя — тогда мне непонятен смысл публикации. Покритиковать? Я бы обратился за этим на профильные ресурсы.
          –1
          Здесь есть статьи где пишут чат, Remote — Desktop, а что там нового?
            –6
            Не знаю, она не попадалась мне на глаза.
          • UFO just landed and posted this here
              +2
              Я не думаю, что подобные статьи имеют смысл.

              Во-первых — это велосипед, есть WCF.

              Во-вторых — это довольно плохо написанный код.

              В-третьих — он еще и ужасно представлен.
              • UFO just landed and posted this here
                  +2
                  Я бы не стал рекомендовать его начинающим — в нем применяются довольно плохие приемы программирования, а форматирование не оставляет шансов разобраться в нем.
                  • UFO just landed and posted this here
                      –5
                      Мне кажется избыточным применение интерфейса IPacket — пакетами при таком подходе могут быть совершенно любые объекты, помеченные как сериализуемые. Сам по себе он не привносит ни удобства реализации, ни четкости семантики.

                      Семантически неверное использование порождающего паттерна через интерфейс ICloneable. Я прекрасно понимаю задумку, но семантика этого интерфейса требует совершенно другой реализации.

                      Довольно странная синхронизация на ManualResetEvent. Первый раз такую вижу, честно говоря.
                      • UFO just landed and posted this here
                          +2
                          Представьте себе, что я бы использовал интерфейс IEnumerable для того, чтобы передвигать каретку печатной машинки.
                          • UFO just landed and posted this here
                              +2
                              Возможно, вы сказали очень умную вещь, но я, к сожалению, не способен ее понять.
                              • UFO just landed and posted this here
                                  +1
                                  К сожалению, я слишком глуп для понимания таких глубоких аналогий. Не могли бы вы мне объяснить в понятных терминах?
                                  • UFO just landed and posted this here
                                      +3
                                      Увы, мне совершенно непонятно, каким образом вы делаете глубокие выводы по функциональному программированию, оперируя исключительно статьей из Википедии.
                                      • UFO just landed and posted this here
                                          +1
                                          Ну, вы даете на нее ссылку и цитируете ее как авторитетный источник. Логично было бы предположить именно то, что предполагаю я.

                                          Мои познания в функциональном программировании не позволяют представить императивное действие в виде вычисления списка, не пользуясь хитрыми техниками вроде монад.

                                          Впрочем, вы можете быть опытнее меня в этом вопросе — вы прочитали хотя бы один абзац хотя бы одной стати о функциональном программировании в Википедии.
                                          • UFO just landed and posted this here
                                              0
                                              Действие, вызываемое в итераторе, имеет довольно посредственное отношение к функциональному программированию.

                                              Впрочем, мне может это только казаться.
                                              • UFO just landed and posted this here
                                                  0
                                                  Вот и договорились.
                                • UFO just landed and posted this here
                            –1
                            А почему вы сразу не написали так? Зачем было дискуссию по поводу ненужности статьи устраивать? Как мне еще учиться писать правильный хороший код самому?
                              –1
                              Мне на секунду показалось, что вы написали велосипед.

                              Моя ошибка.
                                +2
                                В защиту скажу что такая болезнь как велосипедостроение не имеет возрастных и этнических границ.

                                Я бы с удовольствием почитал про велосипед «как мы на перле писали убийцу nginx» или «тетрис на брайнфаке», если бы в статье было только пару диаграмм и много впечатлений/опыта полученного в процессе разработки.

                                А листинги кода… даже не знаю кому могут быть интересны.
                                  +3
                                  > если бы в статье было только пару диаграмм и много впечатлений/опыта полученного в процессе разработки.
                                  Ну какие я мог бы сюда засунуть диаграммы? И вся статья состояла бы только из моих впечатлений? Тогда ее бы точно все засрали.
                                    0
                                    >Ну какие я мог бы сюда засунуть диаграммы?
                                    Например, тестирование производительности.
                                    >И вся статья состояла бы только из моих впечатлений?
                                    Не «только», а впечатления, которые бы разбавляли написанный код.
                      +2
                      Да что вы за свой велосипед?! Как будто кроме меня все пишут статьи о своих гениальных разработках. И я в начале статьи дал понял что еще даже школу не закончил не то что универ.
                        0
                        Множество людей пишут примерно о том же, о чем и вы. Это совершенно не значит, что так нужно делать.

                        Вы правильно делаете, что развиваетесь. Построение систем на низком уровне позволяет понять, как они устроены и лучше представить, как с ними работать на высоком уровне.

                        Однако, данная статья, к сожалению, бесполезна. Единственное, что можно сделать — это сообщить, где у данного кода проблемы.
                        0
                        Согласен, умение программировать это в первую очередь умение выстроить архитектуру, и применить к ней уже готовые проверенные решения, и здесь WCF + net.MSMQ (net.pipe, net.tcp) вполне подошли бы.
                        0
                        От нехватки статей Хабр как раз не страдает, так что пока нелепо руководствоваться соображениями «А то на хабр перестанут писать».

                        > Не пугайте авторов.
                        > А то на хабр перестанут писать.

                        Пусть авторы не пугают читателей.
                        А то Хабр перестанут читать.

                        Статья должна быть хоть чем-то полезной для читателей, а не только для автора.

                        > А то на хабр перестанут писать.

                        Это ведь замечательно. Так здорово, когда человек приходит домой со школы, садится за компьютер и НЕ ПИШЕТ СТАТЬЮ НА ХАБРАХАБР. Это прекрасно, и к этому нужно стремиться.

                        Можно завести отдельный блог, в котором отмечаться: «Сегодня я был готов наваять нетленку про мои первые шаги в языке X, но совесть склонила меня не злоупотреблять вашим вниманием.» Я буду периодически заходить в этот блог и от души благодарить неписателей.
                    +3
                    WCF не всегда работает так, как того требуют условия. Приходится городить свои заточенные под задачу реализации работающие поверх инфраструктуры TransparentProxy.
                    +5
                    «Общая часть»
                    и сразу фейл:
                    «interface IPacket»

                    пакет это расходный материал, он не должен иметь поведения, а цена его создания должна быть ничтожна. В C# для этого есть структуры.
                    • UFO just landed and posted this here
                        +5
                        Я его посмотрел. Даже переименовывание в «сообщение» не избавляет от надобности рационально использовать память и думать над дизайном.
                        • UFO just landed and posted this here
                            +8
                            [анекдот]Разговор за столом:
                            — Извините, пожалуйста, не могли бы Вы, если Вам не трудно,
                            передать мне соль…
                            — Ну ты че, блин, интеллигент, чтоли???
                            — Отнюдь… Такое же быдло, как и вы...[/анекдот]

                            Отнюдь… Такой же разработчик на C# как и вы… :)
                            • UFO just landed and posted this here
                                +6
                                Совершенно верно, ведь не я предлагаю переименовывать классы для решения проблем с памятью :)

                                no offence
                                • UFO just landed and posted this here
                            0
                            Пожалуй, присоединюсь.
                            API Насчет IPacket — согласен
                              +3
                              Не дописал.
                              API можно обсуждать до бесконечности, но все же.

                              IPacket — реально, в данном контексте, пакет/message — это просто обертка, транспорт, DTO, если хотите. С одной стороны у нас — опасность over-engineering-а, а с другой — нарушение SRP. Возможно, имеет смысл сделать некий IPacketWriter, если уж на то пошло.

                              Лично, на мой взгляд, IPacket — пример вырожденного role-interface, то есть интерфейс, который умеет делать всего одну вещь, и врядли нуждается в интерфейсе.
                      • UFO just landed and posted this here
                          +3
                          подсветил
                            0
                            Один класс в середине пропустили.
                          –5
                          Может если сказать возраст меня не будут сильно критиковать? :D
                          • UFO just landed and posted this here
                              +1
                              Вот думал, поделюсь с народом, узнаю о недоработках. А тут понаехали. Ничего конкретного, а только один негатив.
                              • UFO just landed and posted this here
                              +3
                              не надо говорить про возраст, это ни к чему. Он у вас в профиле написан.

                              Посыл хороший, согласен, код, конечно не так хорош, и архитектура тоже. Но хабр немного не то место где нужно такие посылы реализовывать.

                              А то что вы пишете такой код в таком возрасте, это похвально. Вы же это хотели услышать, не так ли?
                                0
                                Нет, просто я не ожидал столько негатива
                                  +2
                                  Если бы вы не хотели негатива — вы бы пошли на rsdn.ru в раздел .NET, выложили там этот код, сообщили бы действительное положение вещей и попросили совета. Негатива бы не было.
                                    0
                                    А Хабр это значит место для негатива?
                                    • UFO just landed and posted this here
                                        0
                                        Замечу, что лично я сегодня исключительно вежлив и корректен.
                                        • UFO just landed and posted this here
                                            +2
                                            Увы, грешен.
                                  • UFO just landed and posted this here
                                  +6
                                  Парень, отличная наработка и отличный код для твоего возраста — так вообще. Уже знаешь про хранилища, фабрики и используешь интерфейсы. Некоторые все ещё процедурами говнокодят и холиварят по этому поводу, а ты ещё до универа начал развиваться в правильном направлении. Почитай Эванса, если не читал и ещё книжку хорошую «Art of Testing.» Чтобы писать код, который подлежит тестированию. Что касается универа в твоем случае, то закончи его обязательно, не бросай (может сперва показаться не интересно. Столько писать кода ты там уже не будешь), но делай упор теорию, ищи где её сможешь потом применить.
                                    0
                                    А полное имя этого Эванса? И на запрос Art of Testing мне полно лабуды вылезает )
                                    • UFO just landed and posted this here
                                        0
                                        Видимо, имелось в виду The Art of Unit Testing. Если так, то присоединяюсь к рекомендациям.
                                      0
                                      Несмотря на то, что подобную задачу действительно лучше решать с применением WCF (с напильничком), реализация неплохая, тем более для целей освоения темы. Вполне норм. Но простор для развития большой, главное избегайте over-engineering-а.
                                      +2
                                      Автор молодец, но на будущее — нужно стараться как-то минимизировать объем текста, чтобы была понятна идея. Мне, например, совсем неохота разбираться в том, какие классы с какими взаимодействует. Имхо, хорошо собранная диаграммка со стрелочками-пояснениями к самым важным классам наподобие «этот класс ответственен за то-то и это» выглядела бы намного презентабельнее стены кода.
                                        +1
                                        Ну это моя первая статья. Спасибо за совет.
                                        +2
                                        Почитайте Рихтера, если еще не читали CLR via C#. Попробуйте использовать асинхронные методы (APM) вместо создания Thread.
                                          0
                                          Лично я для сервера игры писал на сокетах используя библиотеку SuperSocket (пришлось много допилить, но все равно рекоммендую!). WCF в лично моем случае не канал — т.к. на WP7 он сильно кастрированный.
                                            +1
                                            Молодца.
                                              +2
                                              1) Используйте XML-комментарии.
                                              2) Используйте более-менее принятый стиль наименования (пока положил Coding Standarts for .NET), хотя бы для того, чтобы избежать избыточных this.
                                              3) Для IP-адресов в .NET есть IPAddress.
                                              4) Есть ManualResetEventSlim. К тому же, для ManualResetEvent(Slim) нужно вызывать Dispose(), так что ваш класс должен правильно реализовать интерфейс IDisposable (что уже отдельная тема);
                                              5) Используйте Task вместо Thread;
                                              6) Активнее используйте ключевые слова const, readonly, sealed…
                                              7) Используйте форматные строки (вроде как Console.WriteLine(DateTime.Now.ToString(«h:m:s»)); вместо DateTime now = new DateTime(); now = DateTime.Now; System.Console.WriteLine(«Server started at » + now.Hour + ":" + now.Minute + ":" + now.Second);
                                              8) Ваш код может иметь имеет проблемы в синхронизации потоков. Разберитесь в этой проблеме, это достаточно не простая тема.

                                              Ну и ещё достаточно придирок, код точно не самый лучший. Самостоятельное изучение в любом случае похвально.
                                                0
                                                Немного ударюсь в полемику:
                                                Чем Task лучше ThreadPool? Вроде Рихтер рекомендует активно использовать именно пул потоков.
                                                  +2
                                                  Task тоже использует пул потоков, но помимо этого имеет еще много хорошего. Возможно это был Рихтер до выхода TPL?
                                                  +1
                                                  Task или ThreadPool — дело вкуса и потребностей. Основное отличие Task в том, что есть возможность узнать когда она завершается и вернуть результат без лишних извращений в коде =)
                                                    +2
                                                    Насколько мне известно, Task внутрях использует ThreadPool. Task поддерживает предпочтительный механизм завершения синхронных/асинхронных операций — CancellationToken. Вот тут ещё кое-что написано.
                                                  +1
                                                  ну и в копилку конкретных примеров «не очень» кода, в рамках наименования переменных

                                                  class InputProcessor — и в его конструкторе Reader = new BinaryReader(this._stream);

                                                  1) все private переменные однотипно названы с префиксом "_", Reader без префикса, и с большой буквы
                                                  2) обращение к private переменным начинется c this. ( спорно, но некоторыми рекомендуется ), а тот же Reader без this. что при прочтении навевает мысль что доступ должен быть public, а оказывается что нет.

                                                  ну а в целом, да, мне бы в таком возрасте такую увлечённость.
                                                    +1
                                                    Для изучения и пробы пера вполне хорошо.
                                                    1) Преодолеть себя и начать писать код, учитывая что чисто для себя — это плюс.
                                                    2) Учитывая какие программисты порой любители искать огрехи в чужом коде и не побоятся показать свой код на публику надо иметь смелость.
                                                    Замечания (постараюсь не повторятся):
                                                    1) Интересно было посмотреть что да как, но когда увидел, что все в разных солюшенах это сделать стало не совсем удобно.
                                                    2) Про IpAdress, XML комментарии, диаграммы — все по делу сказано.
                                                    3) Увы, правда жизни, но когда надо делать что-то реальное коммерческое, то не до баловства, и упор на уже написанные «велосипеды» надо делать, так как в жизни больше они понадобятся, чем вот такие вот изучения.
                                                      +2
                                                      Для возраста автора — очень даже круто. Сразу видно, что человек активно учится, виден прочтенный и более-менее понятый как минимум GoF, понятые потоки итд, и при этом без свойственных подросткам самомнения и попыток самоутвердиться.

                                                      Критика:
                                                      1. Оверинжиниринг. Все можно сделать проще.
                                                      2. Есть некоторые проблемы с именованием. Хотя все равно лучше, чем у многих хабровских «веб-разработчиков».
                                                      3. Из-за оверинжиниринга опять же толком не отделены ввод/вывод, обработка сообщений и собственно код отвечающий за передачу данных.
                                                      4. Местами не thread-safe код (список ников, например).

                                                      zheleznyak_oleg, удачи вам в профессии, у вас все получается ;)
                                                        0
                                                        Набросились-то, набросились. Паренек молодец, если не завяжет, то имеет все шансы вырасти в хорошего специалиста. Более, чем естественно, что ему хочется и погордиться написанным кодом, и влиться в профессиональную среду, в 14 лет-то. Только, Олег, тут такие же люди как и везде: многим лучше жевать, чем говорить. Ну а вам лично совет простой: хотите сделать лучше — целенаправленно ищите минусы.
                                                          0
                                                          Файлы перезалить надо. Не обнаружены.
                                                          :-(

                                                          Only users with full accounts can post comments. Log in, please.