Основа сервера для игры на 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.
Всем огромное спасибо за уделенное внимание, прошу вас критиковать, от этого будет зависеть улучшение моих постов. Еще раз огромное спасибо, ждите новых постов и удачи!
Контактные данные:
Я в Gmail - michael.vasukovff@gmail.com