WebSocket入门

news2024/11/15 12:01:51

WebSocket

1.1websoket介绍

websocket是一种网络通信协议,RFC6455定义了它的通信标准

websocketHtml5开始提供的一种在单个TCP连接上进行全双工通讯的协议

Http协议是一种无状态、无连接、单向的应用层协议,它采用了请求/响应模型,通信请求只能有客户端发起,服务端对请求做出应答处理

这种通信模型有一个弊端: http协议无法实现服务器主动向客户端发送消息

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦,大多数Web应用程序将通过频繁的异步ajax请求实现长轮询,轮询的效率低,非常浪费资源(因为必须不停连接,或者Http连接始终打开)

Http协议:

在这里插入图片描述

Websocket:

在这里插入图片描述

** 1.2 websocket协议 **

本协议有两部分:握手和传输数据
握手是基于http协议的
来自客户端的握手看起来像如下形式:

在这里插入图片描述
来自服务器的握手看起来像如下形式:
在这里插入图片描述

字段说明:

在这里插入图片描述

1.3 客户端(浏览器)实现

1.3.1 websocket对象
实现Websockets的web浏览器通过WebSocket对象公开所有必需的客户端功能(主要指支持h5的浏览器)

以下API用于创建 Websocket对象:

var ws = new WebSocket(url)

参数url说明: ws: // ip地址: 端口号/资源名称

1.3.2 websocket事件

1.4 服务端实现

Tomcat的7.0.5版本开始支持WebSocket,并且实现了Java WebSocket规范(JSR356)

Java Websocket 应用由一系列的WebsocketEndPoint组成,EndPoint是一个Java对象,代表WebSocket链接的一端,对于服务端,我们可以视为处理具体WebSocket消息的接口,就像Servlet之与http请求一样

我们可以通过两种方式定义Endpoint:

  • 第一种是编程式,即继承类 javax.websocket.Endpoint并实现其方法
  • 第二种是注解类,即定义一个pojo,并添加@ServerEndpoint相关注解

EndPoint 实例在WebSocket握手时创建,并在客户端与服务端连接过程中有效,最后在连接关闭时结束,在Endpoint接口中明确定义了其生命周期的方法,规范实现者确保生命周期的各个阶段调用实例相关的方法,生命周期方法如下:

在这里插入图片描述

** 服务端如何接收客户端发送的消息呢?**

通过为 session(websocket协议中的session,不是http协议的session)添加MessageHandler消息处理器来接收消息,当采用注解方式定义Endpoint时,我们还可以通过@OnMessage 注解指定接收消息的方法

服务端如何推送数据给客户端呢?

发送消息则由RemoteEndpoint 完成,其实例由Session维护,根据使用情况,我们可以通过Session.getBasicRemote获取同步消息发送的实例,然后调用其 sendXxx() 方法就可以发送消息,可以通过Session.getAsyncRemote 获取异步消息发送实例

服务端代码:
在这里插入图片描述

在这里插入图片描述

下面演示一个简单的聊天系统:

浏览器发送给服务器的WebSocket数据:

/**
 * 浏览器发送给服务器的WebSocket数据
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Message {
    private String toName;
    private String message;
    private String fromName;
}

//登录时用到的信息
// 用于登陆响应给浏览器的数据

@Data
@AllArgsConstructor
@NoArgsConstructor
//登录时用到的信息
// 用于登陆响应给浏览器的数据
public class Result {
    private boolean flag;
    private String message;
}

//用户间传送的消息
// 服务器发送给浏览器的WebSocket数据

package com.websocket.websocketdemo.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
//用户间传送的消息
// 服务器发送给浏览器的WebSocket数据
public class ResultMessage {
    private boolean isSystem;
    private String fromName;
    //private String toName;
    private Object message;  // 如果是系统消息是数组
}

配置拦截器:

package com.websocket.websocketdemo.interceptor;


import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Slf4j
public class UserInterceptor implements HandlerInterceptor {
    //没用数据库,暂且用集合来存储已登录等用户,进行拦截。
    public static Map<String, String> onLineUsers = new ConcurrentHashMap<>();;



    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HttpSession httpSession = request.getSession();
        String username = (String) httpSession.getAttribute("user");
        log.info("进入拦截器"+"==="+"进入拦截器的用户是:"+username);
        if(username != null && !onLineUsers.containsKey(username)){
            onLineUsers.put(username,username);
            log.info("已进入拦截器判断");
            log.info("已存储的用户01"+onLineUsers);
            return true;
        }else {
            log.info("已存储的用户02" + onLineUsers);
            log.info("未进入判断,进行重定向");
            httpSession.removeAttribute("user");
            response.sendRedirect("/loginerror");
            return false;
        }
    }
}

使拦截器生效:

//配置拦截路径
@Configuration
public class MvcConfigurer implements WebMvcConfigurer {

    @Autowired
    private UserInterceptor userInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(userInterceptor)
                .addPathPatterns("/main");
    }
}

websocket配置类

@Configuration
//websocket要做的配置类
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

//封装发送的消息内容

/**

  • 用来封装消息的工具类
    */
//封装发送的消息内容

/**
 * 用来封装消息的工具类
 */
public class MessageUtils {
    public static String getMessage(boolean isSystemMessage,String fromName,Object message){
        try{
            ResultMessage resultMessage = new ResultMessage();
            resultMessage.setSystem(isSystemMessage);
            resultMessage.setMessage(message);
            if(fromName != null){
                resultMessage.setFromName(fromName);
            }
//            if(toName !=null ){
//                resultMessage.setToName(toName);
//            }
            ObjectMapper mapper = new ObjectMapper();
            return mapper.writeValueAsString(resultMessage);
        }catch (JsonProcessingException e){
            e.printStackTrace();
        }
        return null;
    }
}

websocket对应客户端某个对象:

@Slf4j
@ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfigurator.class)
@Component
public class ChatEndPoint {
    //用线程安全的map来保存当前用户
    private static Map<String,ChatEndPoint> onLineUsers = new ConcurrentHashMap<>();
    //声明一个session对象,通过该对象可以发送消息给指定用户,不能设置为静态,每个ChatEndPoint有一个session才能区分.(websocket的session)
    private Session session;
    //保存当前登录浏览器的用户
    private HttpSession httpSession;

    //建立连接时发送系统广播
    @OnOpen
    public void onOpen(Session session, EndpointConfig config){
        this.session = session;
        HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
        this.httpSession = httpSession;
        String username = (String) httpSession.getAttribute("user");
        log.info("上线用户名称:"+username);
        onLineUsers.put(username,this);
        String message = MessageUtils.getMessage(true,null,getNames());
        broadcastAllUsers(message);
    }
    //获取当前登录的用户
    private Set<String> getNames(){
        return onLineUsers.keySet();
    }
    //发送系统消息
    private void broadcastAllUsers(String message){
        try{
            Set<String> names = onLineUsers.keySet();
            for(String name : names){
                ChatEndPoint chatEndPoint = onLineUsers.get(name);
                chatEndPoint.session.getBasicRemote().sendText(message);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    //用户之间的信息发送
    @OnMessage
    public void onMessage(String message,Session session){
        try{
            ObjectMapper mapper = new ObjectMapper();
            Message mess = mapper.readValue(message,Message.class);
            String toName = mess.getToName();
            String data = mess.getMessage();
            String username = (String) httpSession.getAttribute("user");
            log.info(username + "向"+toName+"发送的消息:" + data);
            String resultMessage = MessageUtils.getMessage(false,username,data);
            if(StringUtils.hasLength(toName)) {
                onLineUsers.get(toName).session.getBasicRemote().sendText(resultMessage);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    //用户断开连接的断后操作
    @OnClose
    public void onClose(Session session){
        String username = (String) httpSession.getAttribute("user");
        log.info("离线用户:"+ username);
        if (username != null){
            onLineUsers.remove(username);
            UserInterceptor.onLineUsers.remove(username);
        }
        httpSession.removeAttribute("user");
        String message = MessageUtils.getMessage(true,null,getNames());
        broadcastAllUsers(message);
    }
}
import org.springframework.context.annotation.Configuration;

import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
//@Configuration
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {
    //此方法用来获取httpssion对象
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        if(httpSession != null) {
            sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
        }
    }
}

控制层:

package com.websocket.websocketdemo.controller;

import com.websocket.websocketdemo.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

@RestController
//模拟登录操作
@Slf4j
public class CertificationController {
    @RequestMapping("/toLogin")
    public Result toLogin(String user, String pwd, HttpSession httpSession){
        Result result = new Result();
        httpSession.setMaxInactiveInterval(30*60);
        log.info(user+"登录验证中..");
        if(httpSession.getAttribute("user") != null){
            result.setFlag(false);
            result.setMessage("不支持一个浏览器登录多个用户!");
            return result;
        }
        if ("张三".equals(user)&&"123".equals(pwd)){
            result.setFlag(true);
            log.info(user+"登录验证成功");
            httpSession.setAttribute("user",user);
        }else if ("李四".equals(user)&&"123".equals(pwd)){
            result.setFlag(true);
            log.info(user+"登录验证成功");
            httpSession.setAttribute("user",user);
        }else if ("田七".equals(user)&&"123".equals(pwd)){
            result.setFlag(true);
            log.info(user+"登录验证成功");
            httpSession.setAttribute("user",user);
        }
        else if ("王五".equals(user)&&"123".equals(pwd)){
            result.setFlag(true);
            log.info(user+"登录验证成功");
            httpSession.setAttribute("user",user);
        }else {
            result.setFlag(false);
            log.info(user+"验证失败");
            result.setMessage("登录失败");
        }
        return result;
    }
    @RequestMapping("/getUsername")
    public String getUsername(HttpSession httpSession){
        String username = (String) httpSession.getAttribute("user");
        return username;
    }
}

package com.websocket.websocketdemo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
//页面跳转
public class PageController {
    @RequestMapping("/login")
    public String login(){
        return "login";
    }
    @RequestMapping("/main")
    public String main(){
        return "main";
    }
    @RequestMapping("/loginerror")
    public String longinError(){
        return "loginerror";
    }
}

最终效果:

登陆:

在这里插入图片描述

在这里插入图片描述
具体代码地址:码云地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/453620.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Tomcat多实例部署实验

引言 本文主要内容是tomcat的多实例配置实验。 一、实验准备 Tomcat多实例是指在一台设备上运行多个Tomcat服务&#xff0c;这些Tomcat相互独立&#xff0c;互不影响。多实例与虚拟主机不同&#xff0c;虚拟主机的本质是在一个服务下有多个相对独立的目录&#xff0c;但是多实…

OFA(One-For-All)阿里达摩院实现架构、模态、任务的三个统一之Image Captioning

OFA(One-For-All) 通用多模态预训练模型&#xff0c;使用简单的序列到序列的学习框架统一模态&#xff08;跨模态、视觉、语言等模态&#xff09;和任务&#xff08;如图片生成、视觉定位、图片描述、图片分类、文本生成等&#xff09; 架构统一&#xff1a;使用统一的transfo…

何谓SRIO——RapidIO之旅从这里开始

何谓SRIO——RapidIO之旅从这里开始 SRIO&#xff08;Serial RapidIO&#xff09;协议是一种用于高速串行通信的协议&#xff0c;旨在连接数字信号处理器&#xff08;DSP&#xff09;、网络处理器、FPGA等芯片&#xff0c;以及它们之间的互连。SRIO协议具有低延迟、高带宽&…

【单链表】

单链表 1. 函数的声明部分2. 函数的实现部分&#xff08;1&#xff09;打印链表&#xff08;2&#xff09;头插&#xff08;3&#xff09;尾插&#xff08;3&#xff09;头删&#xff08;4&#xff09;尾删&#xff08;5&#xff09;单链表的查找&#xff08;6&#xff09;删除…

leetcode 879. Profitable Schemes(有利润的计划)

有几个工程&#xff0c;每个工程需要group[ i ]个人去做&#xff0c;做完了可以得到profit[ i ]的利润。 现有2个限制条件&#xff1a; 人数上限是n, 且参加了一个工程的人不能再参加其他工程。 利润下限minProfit, 至少要获得minProfit的利润。 问有多少种工程的选法&#xff…

Zuul源码解析(一)

说在前面 我们公司有一个线上服务报错通知群&#xff0c;经常报网关服务的一个 EOFException 异常。这个异常报出来好久了&#xff0c;如下图所示&#xff0c;艾特相关的人也不去处理&#xff0c;大概是不重要异常吧&#xff0c;反正看样子是不影响线上核心业务流程。 然后我上…

FreeRTOS学习笔记(一)——初识FreeRTOS

FreeRTOS官网&#xff1a;FreeRTOS - 适用于具有物联网扩展功能的嵌入式系统的市场领先 RTOS&#xff08;实时操作系统&#xff09; FreeRTOS源码下载&#xff1a;FreeRTOS Real Time Kernel (RTOS) - Browse /FreeRTOS at SourceForge.net 目录 0x01 FreeRTOS编程风格 一…

用CentOS服务器自己搭建部署个Discuz论坛网站,网站搭建教程

Linux系统CentOS服务器使用堡塔搭建论坛网站全套教程。服务器大本营&#xff0c;技术文章内容集合站发车啦&#xff01; 操作系统&#xff1a;Centos 7.6 网站程序&#xff1a;Discuz-X3.4 前言 首先&#xff0c;搭建一个网站需要准备&#xff1a;服务器、域名、网站程序。 …

PWM控制直流电机

一&#xff0c;TB6612电机驱动模块 直流电机属于大功率器件&#xff0c;GPIO无法直接驱动&#xff0c;需要电机驱动模块配合&#xff0c;才能驱动直流电机. TB6612可以驱动2个直流电机。由IN1&#xff0c;IN2控制电机旋转方向&#xff0c;由PWM控制电机旋转速度。 二&#xf…

基于Oracle VM VirtualBox的ubuntu的安装

基于Oracle VM VirtualBox的ubuntu的安装 感谢詹老师的帮助使我得以完成本次安装&#xff0c;以下为本次安装的一个小小的记录。 目录 基于Oracle VM VirtualBox的ubuntu的安装Oracle VM VirtualBox的下载与安装ubuntu的下载Oracle VM VirtualBox下安装ubuntu安装 ROS Melodi…

GitHub 开源神器 Bark模型,让文本转语音更简单

今天跟大家分享一个文本转语音的开源模型&#xff1a;Bark Bark 是由Suno创建的基于转换器的文本到音频模型。Bark 可以生成高度逼真的多语言语音以及其他音频 - 包括音乐、背景噪音和简单的音效。该模型还可以产生非语言交流&#xff0c;如大笑、叹息和哭泣。 该项目刚开源不…

二叉树OJ题(C++实现)

文章目录 1.二叉树创建字符串2. 二叉树的最近公共祖先3.二叉搜索树与双向链表4.从前序与中序遍历序列构造二叉树 1.二叉树创建字符串 二叉树的层序遍历 OJ连接 主要思路是借助一个队列&#xff0c;将每一层的数据以size统计&#xff0c;当size为0时说明该层数据已经输入完&…

Unity Physics2D 2d物理引擎游戏 笔记

2d 材质 里面可以设置 摩擦力 和 弹力 Simulated&#xff1a;是否在当前的物理环境中模拟&#xff0c;取消勾选该框类似于Disable Rigidbody&#xff0c;但使用这个参数更加高效&#xff0c;因为Disable会销毁内部产生的GameObject&#xff0c;而取消勾选Simulated只是禁用。…

详解C语言string.h中常用的14个库函数(四)

本篇博客会讲解最后4个函数&#xff0c;分别是memset, memcpy, memmove, memcmp。这4个函数开头都是mem&#xff0c;即memory&#xff08;内存&#xff09;的缩写。 memset void * memset ( void * ptr, int value, size_t num );memset可以用来设置内存中的值。该函数可以把从…

深度学习实战——循环神经网络(RNN、LSTM、GRU)

忆如完整项目/代码详见github&#xff1a;https://github.com/yiru1225&#xff08;转载标明出处 勿白嫖 star for projects thanks&#xff09; 目录 系列文章目录 一、实验综述 1.实验工具及内容 2.实验数据 3.实验目标 4.实验步骤 二、循环神经网络综述 1.循环神经…

【数据结构】第五章 树与二叉树

文章目录 知识体系5.1 树的基本概念5.1.1 树的定义5.1.2 基本术语5.1.3 树的性质 5.2 二叉树的概念5.2.1 二叉树的定义和主要特性5.2.2 二叉树的存储结构 5.3 二叉树的遍历和线索二叉树5.3.1 二叉树的遍历5.3.2 线索二叉树 5.4 树、森林5.4.1 树的存储结构5.4.2 树、森林与二叉…

uniapp踩坑之项目:各端条件编译

在 HBuilderX 中&#xff0c;ctrlalt/ 即可生成正确注释&#xff08;js&#xff1a;// 注释、css&#xff1a;/* 注释 */、vue/nvue模板&#xff1a; &#xff09;。 #ifdef&#xff1a;if defined 仅在某平台存在#ifndef&#xff1a;if not defined 除了某平台均存在%PLATFORM…

ARM busybox 的移植实战2

一、busybox 源码分析1 1、源码目录梳理 2、整个程序入口的确认 (1) 分析一个程序&#xff0c;不管多庞大还是小&#xff0c;最好的路线都是 按照程序运行时的逻辑顺序来。所以找到一个程序的入口至关重要。 (2) 学 C 语言的时候都知道&#xff0c;程序的主函数 main 函数就是…

机器学习算法 随机森林

文章目录 一、概述1.1 集成学习1.2 决策树1.3 随机森林 二、Sklearn中的随机森林2.1 分类树API2.2 参数 2.2 回归树API2.2.1 重要参数 2.3 随机森林调参 三、总结 一、概述 1.1 集成学习 多个模型集成成为的模型叫做集成评估器&#xff08;ensemble estimator&#xff09;&am…

车载软件架构——闲聊几句AUTOSAR BSW(二)

我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 我特别喜欢一个老话,来都来了。我觉得这就是松弛感,既然来了,就开心起来吧!松弛感来自于专注,焦虑不是靠克服的,是靠忘记的,当你很专注做一件事的时候…