Darksoul11111

На Пикабу
рейтинг 2 подписчика 8 подписок 3 поста 0 в горячем
Награды:
5 лет на Пикабу

Синхронизация клиентов с сервером | Часть 1.

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


!!! ВАЖНО !!!


Эта статья не является уроком, так как это, пока что, показ частичного выполнения моего плана ( который я выкладывал в нашем telegram канале ). Как только весь план будет выполнен, по нему обязательно выйдет урок! А как правило, если все пойдет по плану, урок выйдет в субботу или же как только будет выполнен весь план.


Что сделано

Измененный код я буду помечать восклицательными знаками.


Немного изменен сервер:


Сейчас я продемонстрирую вам, что именно в сервере поменялось


1) Изменен класс User

2) Изменен класс ServerObject


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


В начале класса появились эти строки, для MemoryStream, BinaryReader и BinaryWriter необходимо подключить библиотеку System.IO. Пока что останавливаться на MemoryStream не будем, он заслуживает отдельного поста, который выйдет позже, но знайте, он сильно упрощает запись и чтение массива данных.

Изменен метод Listen

Изменен метод BroadcastMessage и добавлена перегрузка этого метода

И последний метод который изменен в данном классе, это AddUser

3) А класс ClientObject был не большой, а изменений потерпел много, так что, этот класс я покажу весь.

Вот и все изменения, которые на данный момент потерпел сервер.


Скрин сервера на момент прекращения работы над ним

Клиент перенесен в Unity:


Немного скринов из Unity:


1) Как выглядел клиент в Unity, на момент завершения работы над ним.

2) Интерфейс клиента и его составляющие UI элементы, по поводу InputField'а со ScrollBar'ом я расскажу в последней части данного плана.

3) Иерархия клиента в Unity.

4) MainCamera и скрипт общения с сервером ( скрипт в самом низу, под названием "Client_TEST_UDP".

5) Скрипт на кнопке "Подключиться!".

6) Ошибка, которую я буду исправлять.

Возникающая при нажатии на кнопку "Статус", ее задача, вывести в InputField информацию о всех подключенных игроках в виде "NAME:COLOR".


Клиент в Unity:


Состоит он из 1 скрипта, под названием "Client_TEST_UDP"


Сейчас я покажу вам исходники данного скрипта, и сразу скажу, он сейчас находится в стадии разработки и он очень и очень грязный и плохой, это пример как писать не надо! К последней части статьи о плане, он будет выглядеть намного лучше, а еще я рекомендую убрать от экрана впечатлительных программистов :)


Строку, в которой возникает ошибка, я помечу символом 'X', может быть, кому то будет интересно. А возникает она, только при вызове метода Btn_Status_Click

Какие цели этими изменениями достигнуты:


1) Клиент успешно подключается к серверу, если следующие условия верны:


Сервер запущен

Клиент запущен и правильно указан адрес севера, порт 1 ( acceptPort) с портом 2 ( receivePort ) совпадают с указанными на сервере.

Нажата кнопка "Подключится!"

После нажатия на кнопку, на сервере появляется характерное сообщение о новом подключении и так же, появляется характерное сообщение на клиенте. Все наглядно видно на скрине сервера и первых 2-ух скринах клиента в Unity.


На этом все изменения подошли к концу


C изменениями и багом вы знакомы, теперь я познакомлю вас с оставшимися задачами.


1) Исправить баг. После исправления заработает получения информации о подключенных клиентах в виде "NAME:COLOR".


3) Добавить шифрование пакетов.


Заключение!


На этом, данный пост подходит к концу, я хочу поблагодарить вас за уделенное внимание и понимание. Надеюсь вам было интересно, а если нет - критикуйте, это очень важно! Благодаря вашим мнениям, посты будут меняться в лучшую сторону! Оставляйте свои комментарии, делитесь своим мнением, обращайтесь с вопросами ко мне ( контактные данные указаны ниже ), я обязательно на них отвечу!


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

Показать полностью 19
0

Многопоточный чат с использование протокола UDP | C#

Доброго времени суток, дорогие подписчики! Сегодня я хочу вам рассказать и показать как написать чат с использованием протокола UDP на C#, это приложение, а точнее два приложения, клиент и сервер, послужат нам основой для realm server’a.


Для начала давайте разберемся, что из себя представляет протокол UDP.

Лучше Википедии я вам рассказать не смогу, так, что вот кусочек информации из википедии — UDP (англ. User Datagram Protocol — протокол пользовательских датаграмм) — один из ключевых элементов TCP/IP, набора сетевых протоколов для Интернета. С UDP компьютерные приложения могут посылать сообщения (в данном случае называемые датаграммами) другим хостам по IP-сети без необходимости предварительного сообщения для установки специальных каналов передачи или путей данных. Протокол был разработан Дэвидом П. Ридом в 1980 году и официально определён в RFC 768.


Теперь скажу своими словами, помните, когда я рассказывал про TCP, говорил, что, для начала общения между 2-мя приложениями необходимо установить связь. А используя протокол UDP, никакой связи устанавливать не надо! Я буду использовать сокеты для работы с UDP протоколом.


На 1 приложение приходится 1 класс. Я решил выкладывать код не скринами, а в GitHub Gist. Вот сервер, а вот клиент. А вот видеозапись как это работает:

https://vimeo.com/302489273


Так же сервер можно поставить на удаленную машину, и открыть необходимы для вас порт, а на клиенте ввести ip адрес удаленной машины и порт.


В скором времени сервер этого чата будет переписан, под синхронизацию игроков, а клиент будет перенесен в Unity и так же будет переписан.


Статью написал Миша, по всем вопросам обращайтесь к нему в телеграмм: @michael_vv


Все вопросы в коменнтах.

Показать полностью 1

Основа сервера для игры на 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; }

}

}


Контактные данные:
Телега - https://tele.gg/prog_and_mod

Или

@prog_and_mod

Показать полностью
Отличная работа, все прочитано!