• Познакомьтесь с пентестом веб-приложений на практике в нашем новом бесплатном курсе

    «Анализ защищенности веб-приложений»

    🔥 Записаться бесплатно!

  • CTF с учебными материалами Codeby Games

    Обучение кибербезопасности в игровой форме. Более 200 заданий по Active Directory, OSINT, PWN, Веб, Стеганографии, Реверс-инжинирингу, Форензике и Криптографии. Школа CTF с бесплатными курсами по всем категориям.

Сетевая Игра

  • Автор темы Toland
  • Дата начала
Статус
Закрыто для дальнейших ответов.
T

Toland

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

В клиенте создается TImage с изображением танка
(Tank:array [1..10] of TImage; ), его номер отправляется
на сервер и рассылается всем подключенным клиентам.
И на тех клиентах создаются TImage с таким же номером.

В клиенте передвигаю TImage по форме, её кординаты
передаются на сервер и рассылаются всем подключенным клиентам.
У меня есть вот такая структура, которую я передаю через сокеты:
Код:
type
PlayerPosition = packed record
PosX:integer; //Положение по X
PosY:integer; //Положение по Y
id:integer;  //id танка
end;
Сначала эта структура попадает на сервер и он рассылает её всем клиентам вот таким образом:
Код:
procedure TForm1.SendPosTank;
var 
i: integer;
Pos:PlayerPosition;
begin
for i := 0 to ServerSocket1.Socket.ActiveThreads - 1 do
begin
ServerSocket1.Socket.Connections[i].SendBuf(Pos,SizeOf(Pos));
end;
end;
Дело в том, что TServerSocket находится в режиме stThreadBlocking и такой способ является неправильным, так как каждый поток должен заниматься отправкой данных только своему клиенту.
Подскажите пожалуйста как можно реализовать данную задумку?
Заранее большое спасибо.
 
S

sinkopa

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

В клиенте передвигаю TImage по форме, её кординаты
передаются на сервер и рассылаются всем подключенным клиентам.
У меня есть вот такая структура, которую я передаю через сокеты:
Сначала эта структура попадает на сервер и он рассылает её всем клиентам вот таким образом:

Дело в том, что TServerSocket находится в режиме stThreadBlocking и такой способ является неправильным, так как каждый поток должен заниматься отправкой данных только своему клиенту.
Подскажите пожалуйста как можно реализовать данную задумку?
Заранее большое спасибо.
Не согласен. По моему это самый правильный способ...
Правда, я бы сделал несколько замечаний:
1. Координаты и id танка имеет смысл передавать только на старте (коннекте с сервером). после этого достаточно слать только вектор изменения коодинат танка (например: [+1:-1] - танк сдвинулся вправо на 1 и вниз на 1). Это сократит размер передаваемых данных. id танка в этом случае сервер может ассоциировать с сессией клиента.
2. Сервер (я бы во всяком случае так сделал) должен рассылать клиентам не отдельную (поступившую от клиента) новую координату, а битовую матрицу обновленного игрового поля целиком.
Поскольку (предположительно) два танка не могут занимать одну и ту же координату на игровом поле, то с точки зрения сервера, игровое поле может выглядеть приблизительно так
Код:
// битовая матрица 1-го игрока
0000000000000000
0000000000000000
0010000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000

// битовая матрица 2-го игрока
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000001000
0000000000000000
0000000000000000

// суммарная матрица игрового поля (1-цы - текущее положение танков)
0000000000000000
0000000000000000
0010000000000000
0000000000000000
0000000000000000
0000000000001000
0000000000000000
0000000000000000
3. Важный момент - синхронизация данных. Поэтому клиенты не должны слать сообщения "как вздумается".
Серсер должен организовать следующую цикличность действий:
1) Разослать Всем клиентам сообщение: "Эй парни! А ну все доложились!"
2) Принять измененные данные от каждого клиента (либо уведомление что изменений нет)
3) пересчитать матрицу (игровое поле)
4) Разослать клиентам данные либо уведомление что таковых нет (если все спали).
5) Запустить цикл по новой.
В этом случае, никаких "конфликтов" из за разделенности потоков клиентов не возникнет.

Я конечно оочень упрощенно излагаю... просто чтобы Вы поняли общий смысл.
Я понимаю что танки могут еще и поворачивать, стрелять, взрываться... Для этого у сервера должны быть "припасены" персональные клиентские сообщения для клиентов, типа "тебя подбили" и пр...
Но это уже, так сказать, Вам "все танки в руки"... :)
Главный смысл в том, что хоть клиент и "жмет на кнопки", управлять игрой (передвигать фигурки) должен сервер.
 
T

Toland

sinkopa, спасибо большое за советы, но у меня есть несколько вопросов по ним:

1)
1. Координаты и id танка имеет смысл передавать только на старте (коннекте с сервером). после этого достаточно слать только вектор изменения коодинат танка (например: [+1:-1] - танк сдвинулся вправо на 1 и вниз на 1). Это сократит размер передаваемых данных.
Может быть я чего-то не понимаю, но мне кажется, что смысл тот же :)

2)
Сервер (я бы во всяком случае так сделал) должен рассылать клиентам не отдельную (поступившую от клиента) новую координату, а битовую матрицу обновленного игрового поля целиком.
Мне кажется, что размер подобной матрицы будет намного больше чем размер координат и id танка.

3)
Серсер должен организовать следующую цикличность действий:
1) Разослать Всем клиентам сообщение: "Эй парни! А ну все доложились!"
Мне кажется, что сервер не должен ничего запрашивать у клиентов, а должно быть наоборот: клиент посылает запрос на сервер, а тот отвечает на него.

И кстати я еще делал так, что передвижения танков не отображались у клиентов, а только на сервере и в этом случае все работало просто идеально. А когда пытался рассылать клиентам позиции танков способом, который я писал выше - появлялись жуткие задержки, которые становились все больше и больше. Именно поэтому я и понял, что такой способ - неверный.

Завтра с утра я попробую сделать как Вы предложили. Мне понраивлся этот способ. Еще раз спасибо)
 
S

sinkopa

sinkopa, спасибо большое за советы, но у меня есть несколько вопросов по ним:
1) 1. Координаты и id танка имеет смысл передавать только на старте (коннекте с сервером). после этого достаточно слать только вектор изменения коодинат танка (например: [+1:-1] - танк сдвинулся вправо на 1 и вниз на 1). Это сократит размер передаваемых данных.
Может быть я чего-то не понимаю, но мне кажется, что смысл тот же :(
нет не тот же:
1) операция подключения клиента разовая.
2) операция инициализации игры (на старте) разовая.
Так? Даже если клиентов 1000 то в худшем случае пару секунд они (все разом) подождут начала игры.
Объем передаваемых данных (читать: Скорость обновления) критична только непосредственно в процессе игры.
Давайте посчитаем:
Код:
type
PlayerPosition = packed record
PosX:integer; //Положение по X = 4 байта
PosY:integer; //Положение по Y = 4 байта
id:integer;  //id танка = 4 байта
end;
Итого: 12 байт информации.
Мой вариант:
Код:
	PlayerPositionVector = packed record
PosVX: byte; //Смещение по X = 1 байт
PosVY: byte; //Смещение по Y = 1 байт
end;
Итого: 2 байта информации.
2) Сервер (я бы во всяком случае так сделал) должен рассылать клиентам не отдельную (поступившую от клиента) новую координату, а битовую матрицу обновленного игрового поля целиком.
Мне кажется, что размер подобной матрицы будет намного больше чем размер координат и id танка.
Неправда. Я бы даже сказал - Ложъ полнейшая... :)
Мой вариант:
Код:
0000000000000000 // В худшем варианте = 2 байта (16 бит). Если применить оптимизацию - пустые "строчки" можно не передавать
0000000000000000 // В худшем варианте = 2 байта (16 бит). Если применить оптимизацию - пустые "строчки" можно не передавать
0010000000000000 // В худшем варианте = 2 байта (16 бит). Если применить оптимизацию - можно обойтись 1-байтом
0000000000000000 // В худшем варианте = 2 байта (16 бит). Если применить оптимизацию - пустые "строчки" можно не передавать
0000000000000000 // В худшем варианте = 2 байта (16 бит). Если применить оптимизацию - пустые "строчки" можно не передавать
0000000000001000 // В худшем варианте = 2 байта (16 бит). Если применить оптимизацию - можно обойтись 1-байтом
0000000000000000 // В худшем варианте = 2 байта (16 бит). Если применить оптимизацию - пустые "строчки" можно не передавать
0000000000000000 // В худшем варианте = 2 байта (16 бит). Если применить оптимизацию - пустые "строчки" можно не передавать
Итого (в худшем случае) 14 байт информации * на кол-во клиентов.
В любом случае этот объем статичен.
"Мой траффик" считается по формуле 2 байта * на <кол-во изменений у клиентов> + 14 байт (на кол-во клиентов можно не умножать, т.к. отправка осуществляется ВСЕМ ОДНОВРЕМЕННО В РАЗДЕЛЕННЫХ ПОТОКАХ)

"Ваш траффик" считается по формуле 12 байт * на <кол-во изменений у клиентов> * на <кол-во клиентов> (потому как сервер вынужден перенаправлять каждые поступившие 12 байт, КАЖДОМУ КЛИЕНТУ ПО ОЧЕРЕДИ ) причем за то время, пока последний в очереди получит сообщение, в "хвост" уже может набежать еще с сотню новых (измененных другими клиентами) данных.
3)Серсер должен организовать следующую цикличность действий:
1) Разослать Всем клиентам сообщение: "Эй парни! А ну все доложились!"

Мне кажется, что сервер не должен ничего запрашивать у клиентов, а должно быть наоборот: клиент посылает запрос на сервер, а тот отвечает на него.
Опять-же не верно... Вы играли когда нибудь в сетевые игры? :)
Концептуально: есть ИГРОВОЙ СЕРВЕР к которому (подключаются/отключаются) ИГРОКИ.
Вопрос: Так кто все-таки управляет ВСЕЙ игрой (кто правила устанавливает)? Каждый новый подключившийся игрок?
Этож какой бардак получится? :)
И кстати я еще делал так, что передвижения танков не отображались у клиентов, а только на сервере и в этом случае все работало просто идеально. А когда пытался рассылать клиентам позиции танков способом, который я писал выше - появлялись жуткие задержки, которые становились все больше и больше. Именно поэтому я и понял, что такой способ - неверный.
Вот как раз и "тормоза" потому что каждый ИГРОК пытается СВОИ ПРАВИЛА навязать серверу... :)
Завтра с утра я попробую сделать как Вы предложили. Мне понраивлся этот способ. Еще раз спасибо)
Удачи... :)
Вы может быть действительно не поняли идеи?
1. "Танк" не должен сообщать о каждом изменении своей координаты на экране в пикселях...
Игоровое поле должно представлять их себя сетку... скажем 40-60 пикселей на клетку. (в шахматы/шашки играли когда нибудь?)
Попиксельную анимацию по перемещению танка из одной клетки в другую, в состоянии отрисовать сам клиент (не напрягая сервер).
Сервер просто должен указать клиенту направление (вектор)... типа "Белая пешка: Е2-Е4, Черный король: B12-B11"
2. В "общении" клиент-сервер должен соблюдаться определенный протокол. И протокол этот должен организовать/объявить именно сервер. Только так можно гарантировать стабильность работы сервера.
 
T

Toland

sinkopa, спасибо еще раз за советы, а главное, что они с примерами.

Итак вот что я понял:
1) В клиенте нужно игровое поле представить ввиде сетки.
2) Сервер должен сам запрашивать у игроков их позиции, заполнять битовую матрицу и отправлять её клиентам.

Я вот только не могу понять одного: Вот клиент подключился к серверу. На сервере создался новый поток. Этот поток ПОСТОЯННО запрашивает у своего клиента позицию его танка. Добавляет эту позицию в матрицу ко всем остальным танкам и отправляет этому же клиенту всю матрицу целиком и уже сам клиент через цикл расставляет танки на свои позиции.

А битовую матрицу надо представить ввиде двумерного массива?
Ну например так: TanksPosition: array [1..20,1..15] of byte;

А когда клиент получает её, он запускает цикл, который расставляет танки на свои позиции. Например так:
Код:
//Код писал от руки прям в браузере
for y:=1 to 15 do
for x:=1 to 20 do
begin
if TanksPosition[x,y] = 1 then 
begin //Тут надо еще подумать как впихнуть танк в свою ячейку. Пока от руки набросал что-то вроде этого:
Tank[id].left:=x*size; // тут size это размер танка (у него стороны равны)
Tank[id].top:=y*size; //Tank у меня просто обычная картинка Tank:array [1..10] of TImage;
end;
end;

Я правильно понял? Или опять натупил?:)
 
S

sinkopa

Я правильно понял? Или опять натупил?:facepalm:
Не надо столь буквально воспринимать мои "концептуальные" советы... :)
По идее, мне надо было сразу же посоветовать Вам "не изобретать велосипед", а поискать в сети код готового сетевого движка для игр.
Но кто знает... вдруг у Вас получится что нибудь кардинально новое... :facepalm:
Поэтому (если Вы не решите сдаться) вопросы что как представлять и как передавать серверу/клиенту Вам придется самому... Методом проб и ошибок...
Я могу лишь указать на концептуально важные "моменты":
В Вашем проекте есть три отдельных процесса (цикла):
1. Клиент (игрок):
- получили данные (от сервера)
- отрисовали (обновили игровое поле).
- получили данные (от клавиатуры/мыши).
- дождались запроса сервера.
- отправили данные на сервер.

2. Сервер (сеть):
- разослали запрос клиентам
- получили данные, сложили в "корзинки", уведомили "математика".
- дождались пока "математик" посчитает.
- получили результат (от математика)
- разослали клиентам.

3. Сервер (математик)
- получили наборы данных (* на колво игроков)
- посчитали общий результат
- отдали (в сеть).

Если внимательно просмотреть все этапы, становится очевидным (надеюсь) что одним универсальным представлением данных Вам не обойтись...

На каком-то этапе может быть действительно потребуется двухмерный массив. Например для представления игрового поля удобно по координатам[x,y] положить число обозначающее номер(id) танка.

В каком то "месте" будет эффективнее представить как одномерный массив, где номер элемента соответствует id танка а элементом массива будет рекорд с координатами танка (скажем array of TPoint).

Очевидно, что самым "узким" местом является "пропихивание" данных между клиентом и сервером. Тут необходимо обеспечить минимальный объем передаваемой по сети информации. Желательно уложиться в один-два сетевых пакета (в сторону сервера) и столько-же(* на колво игроков) в сторону клиента.

Здесь точно не подходят 2 предыдущих представления данных.
Возможно придется заморочиться с оптимизацией (исключить пустую информацию) или даже придумать собственный "язык жестов" типа:
0-стоит на месте;
1-сдвинулся влево;
2-сдвинулся влево и вверх;
3-сдвинулся вверх... и т.д.
Тогда общая "рассылка" сервера для 10 игроков уложится в 10 байт.
т.е. посылку вида 0,0,3,0,0,0,0,0,1,0 можно интерпретировать как: 3-й танк сдвинулся вверх, 9-й танк сдвинулся влево, остальные стоят и не шевелятся.
Развивая идею дальше могу предположить что при таком подходе достаточно будет и 4-х байт если представить посылку в виде целого числа где каждый десятичный разряд будет ассоциирован с ID танка. т.е. предыдущий пример будет выглядеть как целое число 100000300

Но, это только мои мысли... решать (как где что и куда) решать Вам...
 
T

Toland

sinkopa, спасибо Вам большое за помощь! Кажется я уже вижу свет в конце в конце тоннеля (всмысле начинаю понимать:angry2:). Буду стараться дальше что-то делать)) еще раз большое спасбо!
 
Статус
Закрыто для дальнейших ответов.
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!