webSocket原理及其案例

常见的消息推送方式

1:轮询方式

浏览器以指定的时间间隔向服务器发出HTTP请求,服务器实现试试返回数据给浏览器

缺点:数据有延时、服务器压力较大。

2:长轮询

浏览器发出ajax(异步)请求,服务器接收端接收到请求后,会阻塞请求直到有数据或者超时才返回。

缺点:

3:SSE服务器发送事件

SSE在服务器和客户端之间打开一个单向通道

服务端响应的不再是一次性的数据包,而是Text/event-stream类型的数据流信息

服务器有数据变更时将数据流式传输到客户端

4:webSocket

基于TCP连接上及逆行全双工通信的协议

全双工:允许数据在两个方向上同时传输。

半双工:允许数据在两个方向上传输,但是同一个时间段内只允许一个方向上的传输。

webSocket原理及其案例_第1张图片

websocket API

客户端【浏览器】API

webSocket原理及其案例_第2张图片

websocket对象提供的方法

send() 通过websocket对象调用该方法发送数据给服务端

案例

webSocket原理及其案例_第3张图片

服务端API

Tomcat从7.0.5才支持websocket

Java WebSocket应用由一系列的Endpoint组成,Endpoint是一个Java对象,代表WebSocket链接的一端,对于服务端,我们可以视为处理具体websocket消息的接口

定义Endpoint两种方式:

编程式,继承类javax.websocket.Endpoint并实现其方法。

注解式,定义一个POJO,并添加@ServiceEndpoint相关注解。

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

方法 描述 注解
onOpen() 当开启一个新的会话时调用,该方法是客户端与服务端握手成功后调用的方法 @OnOpen
onClose() 当会话关闭时调用 @OnClose
onError() 当连接过程异常时调用 @OnError

两个问题

服务端如何接收客户端发送的数据呢?

1:编程式

通过添加MessageHandler消息处理器来接收消息

2:注解式

在定义Endpoint时,通过@OnMessage注解指定接收消息的方法

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

发送消息则由RemoteEndpoint完成,其实例由Session维护。

发送消息有两种方式

1:通过session.getBasicRemote获取同步消息发送的实例,然后调用其sendXxx()方法发送消息

2:通过session.getAsyncRemote获取异步消息发送实例,然后调用其sendXxx()方法发送消息

例子

webSocket原理及其案例_第4张图片

代码实现

引入依赖资源


    org.springframework.boot
    spring-boot-starter-websocket
    2.0.0.RELEASE

编写配置类,扫描添加有@ServerEndpoint注解的Bean

@Configuration
public class FileManageWebSocketConfigMessage {
    /**
     * 注入ServerEndpointExporter,自动注册使用@ServerEndpoint注解的
     * @return
     */
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

编写配置类,获取HttpSession对象

public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {
    public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
        //强转
        HttpSession httpSession =(HttpSession) request.getHttpSession();
        //将httpSession对象存储到配置对象中
        config.getUserProperties().put(HttpSession.class.getName(),httpSession);
    }

使用

在@ServerEndpoint注解种引入配置器

@ServerEndpoint(value="/chat",configurator = GetHttpSessionConfigurator.class)

案例代码

webSocket.util

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

@Component
@ServerEndpoint("/webSocket/{uId}")
@Slf4j
public class WebSocketServerUtil {

    private Session session;
    private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet<>();
    private static ConcurrentHashMap webSocketMap  = new ConcurrentHashMap<>();
    private Long uId = null;


    @OnOpen
    public void onOpen(Session session, @PathParam("uId") Long uId){
        this.session = session;
        this.uId = uId;
        if(webSocketMap .containsKey(uId)){
            webSocketMap .remove(uId);
            webSocketMap .put(uId,this);
        }else{
            webSocketMap .put(uId,this);
            webSocketSet.add(this);
        }

        log.info("【websocket消息】有新的连接,总数:{}",webSocketMap.size());
    }

    @OnClose
    public void onClose(){
        if(webSocketMap.containsKey(uId)){
            webSocketMap.remove(uId);
            //从set中删除
            webSocketSet.remove(this);
        }
        log.info("【websocket消息】连接断开,总数:{}",webSocketSet.size());
    }

    @OnMessage
    public void onMessage(String message){
        log.info("【websocket消息】收到客户端发来的消息:{}",message);
    }

    public void sendMessage(String message){
        try {
            this.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    /**
     * 发送自定义消息
     * */
    public static void sendInfo(String message,Long uId) throws Exception {

        //log.info("发送消息到:"+uId+",报文:"+message);

            if(webSocketMap.containsKey(uId)){
                webSocketMap.get(uId).sendMessage(message);
            }else{
                log.error("用户"+uId+",不在线!");
                throw new Exception("连接已关闭,请刷新页面后重试");
            }

    }
}

调用Util方法

        Long uId = new Long("1");
		Map msgMap = new HashMap();
		msgMap.put("step",1);
		msgMap.put("type",2);
		msgMap.put("msg","hello");
		WebSocketServerUtil.sendInfo(JsonUtil.toJson(msgMap),uId);

前端JS代码

/**
 * 初始化websocket连接
 */
function initWebSocket() {
	let uId = 1;
	var websocket = null;
	if('WebSocket' in window) {
		websocket = new WebSocket("ws://localhost:8009/webSocket"+uId );
	} else {
		alert("该浏览器不支持websocket!");
	}
	websocket.onopen = function(event) {
		console.log("建立连接");
		websocket.send('Hello WebSockets!');
	}
	websocket.onclose = function(event) {
		console.log('连接关闭')
		reconnect(); //尝试重连websocket
	}
	//建立通信后,监听到后端的数据传递
	websocket.onmessage = function(event) {
		let data = JSON.parse(event.data);
		//业务处理....
		if(data.step == 1){
		   alert(data.msg);
		}
	}
	websocket.onerror = function() {
		// notify.warn("websocket通信发生错误!");
		// initWebSocket()
	}
	window.onbeforeunload = function() {
		websocket.close();
	}
// 重连
function reconnect() {
	console.log("正在重连");
	// 进行重连
	setTimeout(function () {
		initWebSocket();
	}, 1000);
}

你可能感兴趣的:(websocket,网络协议,网络)