WebSocket 理论+实践

1. 理论

1.1 Http和WebSocket

1.1.1 HTTP

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

对于服务器端连续的状态变化,http协议就显得有些力不从心了,当然也可以通过其他的方式实现。比如:

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

虽然这样也可以实现我们的要求,但是资源就在轮询的过程中被大量浪费。

1.1.1 WebSocket

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

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

理论没看懂的可以戳这 故事描述型

1.2 WebSocket工作方式

1.2.1 WebSocket 客户端

创建WebSocket

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

属性

Socket.readyState//连接状态
Socket.bufferedAmount //队列中等待传输,但是还没有发出的 UTF-8 文本字节数。

事件,编写的时候要加上on 比如onOpen

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

方法

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

实例:

// 初始化一个 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中缓存。

猜你喜欢

转载自my.oschina.net/u/2615530/blog/1853494