Программирование
December 1, 2018

Основа сервера для игры на C# с использование сокетов для работы с протоколом UDP.

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

Сервер состоит из четырех классов: "Program.cs", "ServerObject.cs", "ClientObject" и "User.cs"

P.S. Shift + колесико мыши - листать код влево/вправо.

Program.cs:

Этот класс, запускает первый функционал сервера в отдельном потоке. Для того, что бы создать отдельный поток, необходимо подключить библиотеку "using System.Threading.Task;"

using System;
using System.Threading.Tasks;

namespace Realm
{
    class Program
    {
        static void Main()
        {
            Console.Title = "Realm Server | ver. 0.1 beta alpha";
            Console.WriteLine("Realm Server started...");
            Console.WriteLine();

            ServerObject server = new ServerObject();
            Task serverTask = new Task(server.StartServer);
            serverTask.Start();
            serverTask.Wait();

            Console.WriteLine("Realm Server stoped!");
            Console.ReadKey();
        }
    }
}

ServerObject.cs:

Вы увидите в комментариях, такие слава как: "приема" и "подключенных", это - визуальное имитирование подключения клиентов к серверу, хотя на самом деле в функционале никакого подключения нет. Так же этот класс обеспечивает основной функционал сервера, такой как: рассылка сообщений другим клиентам, "подключение" новых клиентов к серверу.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;

namespace Realm
{
    class ServerObject
    {
        private int acceptPort; // Порт для "приема" новых клиентов
        public int receivePort; // Порт для приема сообщений от "подключенных клиентов"
        private Socket listeningSocket; // Сокет для "приема" новых клиентов
        private List<User> users = new List<User>(); // Список всех "подключенных" клиентов 

        /// <summary>
        /// Запуск сервера
        /// </summary>
        public void StartServer()
        {
            Console.Write("Write port for accept users: ");
            acceptPort = Int32.Parse(Console.ReadLine());
            Console.Write("Write port for receive message: ");
            receivePort = Int32.Parse(Console.ReadLine());
            Console.WriteLine();

            try
            {
                listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); // Создаем сокет
                Task listenTask = new Task(Listen); // Создаем отдельный поток для метода
                listenTask.Start(); // Запускаем поток
                listenTask.Wait(); // Ожидаем завершения потока
            }
            catch(Exception ex)
            {
                Console.WriteLine("Error in StartServer(): " + ex.Message);
            }
            finally
            {
                StopServer(); // Останавливаем сервер
            }
        }

        /// <summary>
        /// Ожидание "подключений" к серверу
        /// </summary>
        private void Listen()
        {
            try
            {
                IPEndPoint acceptIP = new IPEndPoint(IPAddress.Parse("0.0.0.0"), acceptPort); // Создаем конечную точку на которую будут приходить сообщения
                listeningSocket.Bind(acceptIP);

                while(true)
                {
                    StringBuilder builder = new StringBuilder();
                    int bytes = 0;
                    byte[] data = new byte[64];
                    EndPoint senderIP = new IPEndPoint(IPAddress.Any, 0);

                    do
                    {
                        bytes = listeningSocket.ReceiveFrom(data, ref senderIP);
                        builder.Append(Encoding.Unicode.GetString(data, 0, bytes));
                    }
                    while (listeningSocket.Available > 0 && Regex.IsMatch(builder.ToString(), "\\bCODE:[NAME]\\b") == true); // Удостоверяемся, что все данные получены и сообщение содержит метку означающее новое подключение

                    IPEndPoint senderFullIP = senderIP as IPEndPoint;

                    // Добавляем пользователя в список "подключенных"
                    bool addNewUser = true;
                    bool firstUser = false;

                    if(users.Count == 0)
                    {
                        AddUser(senderFullIP, builder);
                        firstUser = true;
                        addNewUser = false;
                        Console.WriteLine("First connected {0}:{1} his name - {2}", senderFullIP.Address.ToString(), senderFullIP.Port.ToString(), builder.ToString());
                    }

                    if (firstUser == false)
                        for (int i = 0; i <= users.Count; i++)
                            if (users[i].FullInfoIP.Address.ToString() == senderFullIP.Address.ToString())
                                addNewUser = false;

                    if(addNewUser == true)
                    {
                        AddUser(senderFullIP, builder);
                        Console.WriteLine("Connected {0}:{1} his name - {2}", senderFullIP.Address, senderFullIP.Port, builder.ToString());
                    }
                }
            }
            catch(Exception ex)
            {
                Console.WriteLine("Error in Listen(): " + ex.Message);
            }
            finally
            {
                StopServer();
            }
        }

        /// <summary>
        /// Рассылка сообщений всем пользователям кроме одного
        /// </summary>
        /// <param name="message">Сообщение</param>
        /// <param name="address">Адрес отправителя ( на этот адрес не будет отправлено сообщение )</param>
        public void BroadcastMessage(string message, string address)
        {
            for(int i = 0; i < users.Count; i++)
                if(users[i].FullInfoIP.Address.ToString() != address)
                {
                    byte[] data = Encoding.Unicode.GetBytes(message);
                    listeningSocket.SendTo(data, users[i].FullInfoIP);
                }
        }

        /// <summary>
        /// Добавление клиента в список "подключенных"
        /// </summary>
        /// <param name="senderFullIP">Информация о новом клиенте</param>
        /// <param name="builder">Сообщение клиента</param>
        private void AddUser(IPEndPoint senderFullIP, StringBuilder builder)
        {
            User user = new User();
            user.Id = Guid.NewGuid().ToString();
            user.FullInfoIP = senderFullIP;
            user.Name = builder.ToString();

            users.Add(user);
            ClientObject client = new ClientObject(this, user, receivePort);
        }

        /// <summary>
        /// Запуск метода для прослушивания данного клиента в отдельном потоке
        /// </summary>
        /// <param name="client">Клиент для которого запускается отдельный поток</param>
        public void UserIsAdded(ClientObject client)
        {
            Task clientTask = new Task(client.Listen);
            clientTask.Start();
        }

        /// <summary>
        /// Остановка сервера
        /// </summary>
        private void StopServer()
        {
            if (listeningSocket != null)
            {
                listeningSocket.Shutdown(SocketShutdown.Both);
                listeningSocket.Close();
                listeningSocket = null;
            }
        }
    }
}

ClientObjcet.cs:

Данный класс обеспечивает получение сообщений от каждого "подключенного" пользователя, а после получения сообщения вызывает метод (server.BroadcastMessage()) для рассылки этого сообщения всем остальным "подключенным" клиентам.

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace Realm
{
    class ClientObject
    {
        ServerObject server;
        User user;
        int receivePort; // Порт который будет принимать сообщения от данного клиента

        public ClientObject(ServerObject _server, User _user, int _port)
        {
            server = _server;
            user = _user;
            receivePort = _port;
            server.UserIsAdded(this);
        }

        /// <summary>
        /// Прием сообщений от конкретного пользователя
        /// </summary>
        public void Listen()
        {
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            IPEndPoint receiveIP = new IPEndPoint(IPAddress.Parse("0.0.0.0"), receivePort);
            socket.Bind(receiveIP);

            while(true)
            {
                StringBuilder builder = new StringBuilder();
                int bytes = 0;
                byte[] data = new byte[64];
                EndPoint senderIP = new IPEndPoint(user.FullInfoIP.Address, user.FullInfoIP.Port);

                do
                {
                    bytes = socket.ReceiveFrom(data, ref senderIP);
                    builder.Append(Encoding.Unicode.GetString(data, 0, bytes));
                }
                while (socket.Available > 0);

                Console.WriteLine("{0}: {1}", user.Name, builder.ToString());
                server.BroadcastMessage(builder.ToString(), user.FullInfoIP.Address.ToString());
            }
        }
    }
}

User.cs

А этот класс служит в качестве структуры клиента.

using System.Net;

namespace Realm
{
    class User
    {
        public string Id { get; set; }
        public IPEndPoint FullInfoIP { get; set; }
        public string Name { get; set; }
    }
}

Я хочу с вами поделится тем, какой функционал я планирую добавить в данный сервер, это - шифрование пакетов и синхронизация местоположения персонажей в игре. Так же я перепишу клиент чата под игру и перенесу его в Unity.

Всем огромное спасибо за уделенное внимание, прошу вас критиковать, от этого будет зависеть улучшение моих постов. Еще раз огромное спасибо, ждите новых постов и удачи!

Контактные данные:

Telegram канал

Я в Telegram

Я в Gmail - [email protected]