WebSocket入门

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";
    }
}

最终效果:

登陆:

在这里插入图片描述

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

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值