WebSocket 理论+实践

1. 理论

1.1 Http和WebSocket

1.1.1 HTTP

http协议在通讯过程当中存在一个巨大的缺陷,通讯只能由客户端发起,服务器只能根据响应返回响应的结果。也就说,服务器端没法主动给客户端发送消息。html

对于服务器端连续的状态变化,http协议就显得有些力不从心了,固然也能够经过其余的方式实现。好比:java

  1. 轮询(每隔一段时候,就发出一个询问,了解服务器有没有新的信息)
  2. long poll(采用阻塞模式,客户端发起链接后,若是没消息,就一直不返回Response给客户端。直到有消息才返回,返回完以后,客户端再次创建链接,周而复始)。

虽然这样也能够实现咱们的要求,可是资源就在轮询的过程当中被大量浪费。web

1.1.1 WebSocket

WebSocket协议,2008年诞生,2011年称为国际标准,其最大的特色就是服务器端能够主动向客户端发送消息,实现真正的双向平等对话。主要特色:redis

  1. 创建在tcp协议之上,服务器端的实现比较容易
  2. 与http协议有很好的兼容性,握手阶段采用http协议
  3. 数据格式比较轻量,性能开销小,通讯高效
  4. 能够发送文本,也能够发送二进制
  5. 没有同源策略(htpp的同源策略主要是出于安全考虑)
  6. 协议标识是ws,如ws://127.0.0.1:8080/myHandler/{Id}"

理论没看懂的能够戳这 故事描述型spring

1.2 WebSocket工做方式

1.2.1 WebSocket 客户端

建立WebSocketapi

var Socket = new WebSocket(url, [protocol] );//协议能够为空

属性缓存

Socket.readyState//链接状态
Socket.bufferedAmount //队列中等待传输,可是尚未发出的 UTF-8 文本字节数。

事件,编写的时候要加上on 好比onOpen安全

open //链接创建时触发
message //客户端接收服务端数据时触发
error //通讯发生错误时触发
close //链接关闭时触发

方法服务器

Socket.send() //使用链接发送数据
Socket.close() //关闭链接

实例:websocket

// 初始化一个 WebSocket 对象
var ws = new WebSocket("ws://localhost:9998/echo");
// 创建 web socket 链接成功触发事件
ws.onopen = function () {
	// 使用 send() 方法发送数据
	ws.send("发送数据");
	alert("数据发送中...");
};
// 接收服务端数据时触发事件
ws.onmessage = function (evt) {
	var received_msg = evt.data;
	alert("数据已接收...");
};
// 断开 web socket 链接成功触发事件
ws.onclose = function () {
	alert("链接已关闭...");
};

1.2.2 WebSocket 服务器端

服务器端的就主要用代码来实现吧

2. 实践篇

源码地址 密码:f28e

服务端获取消息很简单,主要是向服务器端发送消息。须要向客户端发送消息,那么咱们须要知道客户端的某个惟一标识,那么这个标识用什么来表示呢,那就是session。

2.1 普通javaEE方式

直接贴码,里面注释很清晰 须要的依赖

<dependency>
	<groupId>javax</groupId>
	<artifactId>javaee-api</artifactId>
	<version>7.0</version>
	<scope>provided</scope>
</dependency>

java源码

package me.gacl.websocket;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;

/**
 * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
 * 注解的值将被用于监听用户链接的终端访问URL地址,客户端能够经过这个URL来链接到WebSocket服务器端
 */
@ServerEndpoint("/websocket")
public class WebSocketTest {
	//静态变量,用来记录当前在线链接数。应该把它设计成线程安全的。
	private static int onlineCount = 0;

	//concurrent包的线程安全Set,用来存放每一个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通讯的话,可使用Map来存放,其中Key能够为用户标识
	private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>();

	//与某个客户端的链接会话,须要经过它来给客户端发送数据
	private Session session;

	/**
	 * 链接创建成功调用的方法
	 * @param session  可选的参数。session为与某个客户端的链接会话,须要经过它来给客户端发送数据
	 */
	@OnOpen
	public void onOpen(Session session){
		this.session = session;
		webSocketSet.add(this);     //加入set中
		addOnlineCount();           //在线数加1
		System.out.println("有新链接加入!当前在线人数为" + getOnlineCount());
	}

	/**
	 * 链接关闭调用的方法
	 */
	@OnClose
	public void onClose(){
		webSocketSet.remove(this);  //从set中删除
		subOnlineCount();           //在线数减1
		System.out.println("有一链接关闭!当前在线人数为" + getOnlineCount());
	}

	/**
	 * 收到客户端消息后调用的方法
	 * @param message 客户端发送过来的消息
	 * @param session 可选的参数
	 */
	@OnMessage
	public void onMessage(String message, Session session) {
		System.out.println("来自客户端的消息:" + message);
		//群发消息
		for(WebSocketTest item: webSocketSet){
			try {
				item.sendMessage(message);
			} catch (IOException e) {
				e.printStackTrace();
				continue;
			}
		}
	}
	/**
	 * 发生错误时调用
	 * @param session
	 * @param error
	 */
	@OnError
	public void onError(Session session, Throwable error){
		System.out.println("发生错误");
		error.printStackTrace();
	}

	/**
	 * 这个方法与上面几个方法不同。没有用注解,是根据本身须要添加的方法。
	 * @param message
	 * @throws IOException
	 */
	public void sendMessage(String message) throws IOException{
		this.session.getBasicRemote().sendText(message);
		//this.session.getAsyncRemote().sendText(message);
	}

	public static synchronized int getOnlineCount() {
		return onlineCount;
	}

	public static synchronized void addOnlineCount() {
		WebSocketTest.onlineCount++;
	}

	public static synchronized void subOnlineCount() {
		WebSocketTest.onlineCount--;
	}
}

2.2 spring boot集成

这里经过从新写一个controller,直接给客户端发送消息,先贴这里的代码,应该大部分人都是想实现这个功能。这里的目的是,在处理一个其余请求以后,须要给原来的客户端发送消息,告诉它我已经处理完了,收到消息以后再处理后续的逻辑(扫码场景比较广泛)。

@GetMapping("/")
    public WebsocketResponse sendSuccess(){
        MyHandler send = new MyHandler();
        TextMessage msg = new TextMessage("发给客户端");
        send.sendMessageToUser("888",msg);
        return new WebsocketResponse(1);
    }

有须要的直到源码中拉取代码吧,服务器端的原理都相似

2.2.1 代码注意问题

  1. 这里的session必定是须要回调的那个客户端的session,因此第一次请求是须要保存客户端的session,公司通常放在redis中缓存。
相关文章
相关标签/搜索