Http协议
- Http协议是互联网应用最广泛的超文本传输协议,以明文传输
- Https是在Http基础上增加了证书进行加密和解密操作,更安全,但是效率降低。
Http协议如何请求?
- 客户端请求服务器,建立连接(TCP/IP三次握手);
- 客户端发送请求(请求头,参数等信息)
- 服务器响应
- 断开连接(TCP/IP四次挥手)
WebSocket协议
- WebSocket是HTML5的一种新协议,实现了浏览器与服务器双工通信(full-duple 发送和接收同时进行)。开始的握手需要借助HTTP请求完成。
- WebSocket请求头比Http多了以下参数
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: tDL6HaxG2CTIoWBtHii8sA==
Sec-WebSocket-Version: 13
Upgrade:websocket
- WebSocket响应头比Http多了以下参数
Connection: upgrade
Sec-WebSocket-Accept: U7+5kuIizKwAIjggpJEGyx64E5w=
Upgrade: websocket
- WebSocket连接成功后,客户端和服务器都能主动的向对方发送或接收数据
WebSocket 使用
API
1. 创建WebSocket对象
var ws = new WebSocket(url,[protocols]);
url: 表示要连接的URL,这个URL应该为响应WebSocket的地址
protocols: 可接受的子协议,可以不写
2. WebSocket对象方法
1 关闭连接
close([code],[reason]);
code: 关闭的状态码,可以不写
reason: 关闭的原因,可以不写
2 发送数据
send(data);
data: 要发送的数据,可以是字符串或二进制
3. WebSocket属性
1. onclose 监听连接关闭事件,当对象的readyState状态变为CLOSED时触发,会接收到一个close event对象
2. onerror 当错误发生时用于监听error事件的监听器。会接收到error event对象
3. onmessage 监听消息事件,有消息时触发。会接收到message event对象
4. onopen 监听连接打开事件的监听器,当对象的readyState状态变为OPEN时触发,会接收到一个open event对象
5. readyState: 连接状态
0: 连接还未开启
1: 连接已开启并准备通信
2: 连接正在关闭的过程中
3: 连接已关闭或无法连接
java后台使用websocket
- 导入spring-boot-starter-websocket依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
- 配置websocket,添加ServerEndpointExporter实例
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
- 写WebSocketServer来实现交互
- websocket类似客户端服务器的形式(采用ws协议),WebSocketServer就类似于controller
- 在类上增加@ServerEndpoint("/ws/{fromId}")和@Component,内部用@OnOpen开启连接,@onClose关闭连接,@onMessage接收消息等方法。(内置tomcat需要@Component,war不能添加)
- 使用ConcurrentHashMap 来存放fromId和其WebSocketServer的实例,用来推送消息
package com.wzh.springboot.websocket.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnError;
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;
/**
* @author wzh
* @date 2020/3/7 - 14:28
*/
@ServerEndpoint("/ws/{fromId}")
@Component
@Slf4j
public class WebSocketServer {
/**
* 静态变量,记录在线人数
*/
private static volatile int onlineCount = 0;
/**
* 存放在线的用户的WebSocketServer
*/
private static ConcurrentHashMap<String, WebSocketServer> onlineUserMap = new ConcurrentHashMap<>();
/**
* 与客户端的会话,通过它来传输
*/
private Session session;
/**
* 用户id
*/
private String fromId;
/**
* 建立连接后调用该方法-对应前端JS
*/
@OnOpen
public void onOpen(@PathParam("fromId") String fromId, Session session) {
// 会话保存
this.session = session;
this.fromId = fromId;
if (!onlineUserMap.containsKey(fromId)) {
onlineUserMap.put(fromId, this);
addOnlineCount();
}
}
/**
* 关闭连接时调用该方法-对应前端JS
*/
@OnClose
public void onClose() {
if (onlineUserMap.containsKey(fromId)) {
onlineUserMap.remove(fromId);
subOnlineCount();
}
}
/**
* 接收客户端的message,根据是否有接收人选择进行广播还是指定发送
*/
@OnMessage
public void onMessage(String message) {
/**
* 1. 将message转化成对象,获取toId
* 2. 通过toId去onlineUserMap获取WebSocketServer对象
* 3. 调用sendMessage或sendAsyncMessage发送消息
*/
}
/**
* 出现错误时调用
*
* @param error
*/
@OnError
public void onError(Throwable error) {
log.error("fromId: " + this.fromId + "ws错误,原因:" + error.getMessage());
}
/**
* 发送异步消息
* @param message
*/
public void sendAsyncMessage(String message){
session.getAsyncRemote().sendText(message);
}
/**
* 发送消息
* @param message
*/
public void sendMessage(String message) throws IOException {
session.getBasicRemote().sendText(message);
}
/**
* 统计在线人数
*
* @return
*/
public static synchronized int getOnlineCount() {
return onlineCount;
}
/**
* 在线人数++
*/
public static synchronized void addOnlineCount() {
onlineCount++;
}
/**
* 在线人数--
*/
public static synchronized void subOnlineCount() {
onlineCount--;
}
}
前端对接
建立websocket.js文件,引入后,使用ws.openSocket打开连接,设置messageDto对象属性(与后端对接保持一致),调用sendMessage方法发送
var ws = function () {
var socket;
var messageDto = {
fromId: null,
toId: null,
message: null
};
function openSocket(socketUrl) {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else{
console.log("开启WebSocket");
socketUrl=socketUrl.replace("https","wss").replace("http","ws");
if(socket!=null){
socket.close();
socket=null;
}
socket = new WebSocket(socketUrl);
//打开事件
socket.onopen = function() {
console.log("websocket已打开");
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
socket.onmessage = function(msg) {
console.log(msg.data);
//发现消息进入 开始处理前端触发逻辑
};
//关闭事件
socket.onclose = function() {
console.log("websocket已关闭");
};
//发生了错误事件
socket.onerror = function() {
console.log("websocket发生了错误");
}
}
}
function sendMessage() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else {
console.log("您的浏览器支持WebSocket");
console.log(JSON.stringify(messageDto));
socket.send(JSON.stringify(messageDto));
}
}
return {
openSocket: function (socketUrl) {
openSocket(socketUrl);
},
sendMessage: function () {
sendMessage()
},
messageDto: messageDto
}
}();