[WebSocket] Многопользовательская игра-викторина в реальном времени с использованием WebSocket

WebSocket

предисловие

В первых двух главах руководства мы использовали базовые функции WebSocket для создания небольшого чата и группировали его во второй главе.

Обзор серии учебников:

[WebSocket] Глава 1. Создание многопользовательского онлайн-чата WebSocket (SpringBoot + WebSocket)

[WebSocket] Глава 2: Распределенная трансформация кластера WebSocket — реализация многопользовательского онлайн-чата

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

Это небольшой проект, который я разработал на недавнем конкурсе домашних заданий. Я разработал весь игровой процесс и внутренний код с моими друзьями. Страница внешнего интерфейса пока не будет вам предоставлена. Вы можете обратиться к первому две главы и напишите сами.

Краткое содержание этой статьи:

  • Общие схемы коммуникации для онлайн-игр
  • Как использовать WebSocket для реализации связи в реальном времени в игровых сражениях
  • Экранная демонстрация шагов игры и соответствующий дизайн интерфейса WebSocket

Исходный код этой статьи:(Маме больше не нужно беспокоиться о том, что я не могу воспроизвести код статьи)

GitHub.com/QQ Xianxia6661/Башня…

текст

WebSocket реализует многопользовательские онлайн-игры - боевая викторина

Общие схемы коммуникации для онлайн-игр

Ссылаться на:

blog.CSDN.net/honey199396…

HTTP

Преимущества: протокол является относительно зрелым, широко используется, основан на TCP/IP, имеет преимущества TCP, низкие затраты на исследования и разработки, быструю разработку и множество программ с открытым исходным кодом, таких как nginx, apache, tomact и т. д.

Недостатки: без сохранения состояния, без подключения, только режим PULL, не поддерживает PUSH, большие пакеты данных

Характеристики: на основе протокола прикладного уровня TCP/IP, без сохранения состояния, без установления соединения, поддержка режима C/S, подходит для передачи текста

TCP

Преимущества: надежность, полнодуплексный протокол, поддержка открытого исходного кода, широкое применение, ориентированность на соединение, низкая стоимость НИОКР, неограниченное содержимое пакета (автоматическое субконтрактирование на уровне IP, повторная передача, не более 1452 байт).

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

Особенности: ориентированный на соединение, надежный, полнодуплексный протокол, основанный на уровне IP, эталонная модель OSI расположена на транспортном уровне, подходит для двоичной передачи

WebScoket

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

недостаток:

Особенности: Stateful, ориентированный на соединение, небольшой заголовок данных, подходящий для WEB3.0 и других мгновенных сетевых коммуникаций.

UDP

Преимущества: Операционная система: высокий уровень параллелизма, низкое потребление памяти, передача: высокая эффективность, низкая задержка в сети, простая модель передачи, низкие затраты на НИОКР.

Недостатки: ненадежный протокол, односторонний протокол, меньшая поддержка открытого исходного кода, ограниченное содержимое сообщения, не более 1464 байт, дизайн: дизайн протокола более сложный, сеть: плохая сеть, пакеты данных теряются.

Особенности: без установления соединения, ненадежный, основанный на уровне протокола IP, эталонная модель OSI на транспортном уровне, доставка с максимальной эффективностью, подходит для двоичного транспорта

Суммировать

  • Для слабых сетевых игр необходимо исключить тип, а тип карты может напрямую использовать протокол HTTP.Если рассматривается безопасность, напрямую HTTPS или выполнять симметричное шифрование тела контента;
  • Для работы в реальном времени интерактивные требования высоки, вы можете сначала выбрать Websocket, а затем протокол TCP;
  • Для чрезвычайно высоких требований к реальному времени и доступности обычно можно выбрать протокол UDP;
  • Для сражений и гонок по локальной сети просто используйте протокол UDP;

WebSocket реализует связь в режиме реального времени для онлайн-игр для двух игроков.

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

В этом разделе подробно описано определение конкретного метода связи через веб-сокет в нашей онлайн-викторине.

Правила этой викторины следующие:

  • После того, как пользователь открывает страницу h5, он вводит свой никнейм и отправляет его на сервер, сервер сохраняет никнейм пользователя в хэш-карту и фиксирует статус пользователя (неактивен, в игре), после чего пользователь попадает в лобби.
  • Пользователи в лобби могут выбирать друг друга. Как только пользователь выбирает другого пользователя, игра запускается, и обе стороны переходят в режим ответа.
  • Каждый из двух пользователей, ответивших на вопрос, ответил на 10 вопросов, и каждый правильный ответ приносит 10 баллов, всего 100. На верхней левой странице отображаются их собственные баллы, а в верхнем правом углу — баллы другой стороны, а оценка другой стороны получена через веб-сокет в режиме реального времени.
  • В конце 10 вопросов обе стороны ждут общего счета друг друга и, наконец, определяют победителя и проигравшего и отображают интерфейс результатов.

Итак, нам нужно разработать три протокола WebSocket:

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

Далее мы подробно представим эти три интерфейса WebSocket.

Пользователь создает никнейм и входит в лобби игрока

Откройте интерфейс и войдите в игру:

Мы используем HashMap для хранения состояния пользователя,

private Map<String, StatusEnum> userToStatus = new HashMap<>();

Пользовательское состояние делится на простое и внутриигровое:

public enum StatusEnum {
    IDLE,
    IN_GAME
}

Интерфейс WebSocket устроен следующим образом:

Код интерфейса WebSocket выглядит следующим образом:

@MessageMapping("/game.add_user")
    @SendTo("/topic/game")
    public MessageReply addUser(@Payload ChatMessage chatMessage, SimpMessageHeaderAccessor headerAccessor) throws JsonProcessingException {
        MessageReply message = new MessageReply();
        String sender = chatMessage.getSender();
        ChatMessage result = new ChatMessage();
        result.setType(MessageTypeEnum.ADD_USER);
        result.setReceiver(Collections.singletonList(sender));
        if (userToStatus.containsKey(sender)) {
            message.setCode(201);
            message.setStatus("该用户名已存在");
            message.setChatMessage(result);
            log.warn("addUser[" + sender + "]: " + message.toString());
        } else {
            result.setContent(mapper.writeValueAsString(userToStatus.keySet().stream().filter(k -> userToStatus.get(k).equals(StatusEnum.IDLE)).toArray()));
            message.setCode(200);
            message.setStatus("成功");
            message.setChatMessage(result);
            userToStatus.put(sender, StatusEnum.IDLE);
            headerAccessor.getSessionAttributes().put("username",sender);
            log.warn("addUser[" + sender + "]: " + message.toString());
        }
        return message;
    }

Пользователь выбирает соперника, и обе стороны вступают в игру.

Выберите игрока в лобби, а затем войдите в бой:

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

private Map<String, String> userToPlay = new HashMap<>();

Интерфейс WebSocket устроен следующим образом:

Код интерфейса WebSocket выглядит следующим образом:

@MessageMapping("/game.choose_user")
    @SendTo("/topic/game")
    public MessageReply chooseUser(@Payload ChatMessage chatMessage) throws JsonProcessingException {
        MessageReply message = new MessageReply();
        String receiver = chatMessage.getContent();
        String sender = chatMessage.getSender();
        ChatMessage result = new ChatMessage();
        result.setType(MessageTypeEnum.CHOOSE_USER);
        if (userToStatus.containsKey(receiver) && userToStatus.get(receiver).equals(StatusEnum.IDLE)) {
            List<QuestionRelayDTO> list=new ArrayList<>();
            questionService.getQuestions(limit).forEach(item->{
                QuestionRelayDTO relayDTO=new QuestionRelayDTO();
                relayDTO.setTopic_id(item.getId());
                relayDTO.setTopic_name(item.getQuestion());
                List<Answer> answers=new ArrayList<>();
                answers.add(new Answer(1,item.getId(),item.getOptionA(),item.getResult()==1?1:0));
                answers.add(new Answer(2,item.getId(),item.getOptionB(),item.getResult()==2?1:0));
                answers.add(new Answer(3,item.getId(),item.getOptionC(),item.getResult()==3?1:0));
                answers.add(new Answer(4,item.getId(),item.getOptionD(),item.getResult()==4?1:0));
                relayDTO.setTopic_answer(answers);
                list.add(relayDTO);
            });
            result.setContent(mapper.writeValueAsString(list));
            result.setReceiver(Arrays.asList(sender, receiver));
            message.setCode(200);
            message.setStatus("匹配成功");
            message.setChatMessage(result);
            userToStatus.put(receiver, StatusEnum.IN_GAME);
            userToStatus.put(sender, StatusEnum.IN_GAME);
            userToPlay.put(receiver,sender);
            userToPlay.put(sender,receiver);
            log.warn("chooseUser[" + sender + "," + receiver + "]: " + message.toString());
        } else {
            result.setContent(mapper.writeValueAsString(userToStatus.keySet().stream().filter(k -> userToStatus.get(k).equals(StatusEnum.IDLE)).toArray()));
            result.setReceiver(Collections.singletonList(sender));
            message.setCode(202);
            message.setStatus("该用户不存在或已在游戏中");
            message.setChatMessage(result);
            log.warn("chooseUser[" + sender + "]: " + message.toString());
        }
        return message;
    }

Во время боя в режиме реального времени отображаются очки обеих сторон.

Демонстрационная карта во время боя: левая сторона показывает наш счет, правая сторона показывает счет противника

Интерфейс WebSocket устроен следующим образом:

Код интерфейса WebSocket выглядит следующим образом:

@MessageMapping("/game.do_exam")
    @SendTo("/topic/game")
    public MessageReply doExam(@Payload ChatMessage chatMessage) throws JsonProcessingException {
        MessageReply message = new MessageReply();
        String sender = chatMessage.getSender();
        String receiver = userToPlay.get(sender);
        ChatMessage result = new ChatMessage();
        result.setType(MessageTypeEnum.DO_EXAM);
        log.warn("userToStatus:" + mapper.writeValueAsString(userToStatus));
        if (userToStatus.containsKey(receiver) && userToStatus.get(receiver).equals(StatusEnum.IN_GAME)) {
            result.setContent(chatMessage.getContent());
            result.setSender(sender);
            result.setReceiver(Collections.singletonList(receiver));
            message.setCode(200);
            message.setStatus("成功");
            message.setChatMessage(result);
            log.warn("doExam[" + receiver + "]: " + message.toString());
        }else{
            result.setReceiver(Collections.singletonList(sender));
            message.setCode(203);
            message.setStatus("该用户不存在或已退出游戏");
            message.setChatMessage(result);
            log.warn("doExam[" + sender + "]: " + message.toString());
        }
        return message;
    }

дальше

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

  • Реализовать автоматическое сопоставление/таблицу лидеров
  • Оптимизация связи через WebSocket: в некоторых местах используйте двухточечную связь, а не всю широковещательную связь.

Мы можем использовать метод convertAndSendToUser(), о чем можно судить по названию, а метод convertAndSendToUser() позволяет нам отправить сообщение конкретному пользователю.

Spring webscoket может идентифицировать и обрабатывать пути подписки с помощью «/user», например, если клиент браузера подписывается на путь «/user/topic/greetings»,

stompClient.subscribe('/user/topic/greetings', function(data) {
    //...
});

Он будет преобразован в «/topic/greetings-usererbgz2rq», «usererbgz2rq» весенним веб-сокетом с использованием UserDestinationMessageHandler, user — ключевое слово, erbgz2rq — идентификатор сеанса, так что пользователь и путь подписки однозначно совпадают.

использованная литература

Одноранговая связь:

blog.CSDN.net/Следует уволить/Ах…

Суммировать

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

Исходный код этого проекта:

GitHub.com/QQ Xianxia6661/Башня…

Подписывайтесь на меня

В настоящее время я backend-разработчик. Основное внимание уделяется back-end разработке, безопасности данных, поисковым роботам, граничным вычислениям и другим направлениям.

WeChat: yangzd1102 (укажите цель)

Гитхаб:@qqxx6661

личный блог:

Оригинальное основное содержание блога

  • Полное руководство по обзору знаний Java
  • Анализ вопросов алгоритма Leetcode
  • Меч относится к анализу проблемы алгоритма предложения.
  • Боевая серия для новичков SpringCloud
  • Боевая серия для новичков SpringBoot
  • Технические статьи о краулерах
  • Технические статьи, связанные с бэкенд-разработкой

Личный публичный аккаунт: разговор о технологиях back-end

个人公众号:后端技术漫谈

Если статья была вам полезна, пожалуйста, соберите ее и перешлите своим друзьям~