【*】概念
WebSocket 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信
- 全双工和单工的区别
1.全双工:通信允许数据在两个方向上同时传输,它在能力上相当
于两个单工通信方式的结合。全双工指可以同时(瞬时)进行信号的双向传输(A→B且B→A)。指
A→B的同时B→A,是瞬时同步的。
2.单工、半双工(Half Duplex),所谓半双工就是指一个时间段内只有一个动作发生,举个简单例子,
一条窄窄的马路,同时只能有一辆车通过,当目前有两辆车对开,这种情况下就只能一辆先过,等到头
儿后另一辆再开,这个例子就形象的说明了半双工的原理。早期的对讲机、以及早期集线器等设备都是
基于半双工的产品。随着技术的不断进步,半双工会逐渐退出历史舞台。
- http与websocket的区别
1.http协议是短连接,因为请求之后,都会关闭连接,下次重新请求数据,需要再次打开链接。
2.WebSocket协议是一种长链接,只需要通过一次请求来初始化链接,然后所有的请求和响应都是通过这个TCP链接
进行通讯。
【*】WebSocket依赖
<!--添加websocket依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.3.8</version>
</dependency>
【*】websocket的相关注解说明
- @ServerEndpoint("/websocket/{uid}")
- 申明这是一个websocket服务
- 需要指定访问该服务的地址,在地址中可以指定参数,需要通过{}进行占位
- @OnOpen
- 用法:public void onOpen(Session session, @PathParam("uid") String uid) throws IOException{}
- 该方法将在建立连接后执行,会传入session对象,就是客户端与服务端建立的长连接通道
- 通过@PathParam获取url申明中的参数
- @OnClose
- 用法:public void onClose() {}
- 该方法是在连接关闭后执行
- @OnMessage
- 用法:public void onMessage(String message, Session session) throws IOException {}
- 该方法用于接收客户端发来的消息
- message:发来的消息数据
- session:会话对象(也是通道)
- 发送消息到客户端
- 用法:session.getBasicRemote().sendText("你好");
- 通过session进行发送。
【*】WebSocket在线测试地址
http://www.websocket-test.com/
【*】SpringBoot整合WebSocket实现同一账号只能一个设备登录功能
- 使用WebSocket核心类
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Component//项目启动,会自动扫描当前的类,将当前的类注入Spring容器
public class OneLoginHandshakeInterceptor implements HandshakeInterceptor {
/**
* 握手之前,若返回false,则不建立链接
*
* @param request
* @param response
* @param wsHandler
* @param attributes
* @return
* @throws Exception
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse
response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws
Exception {
//将用户id放入socket处理器的会话(WebSocketSession)中
HttpServletRequest httpRequest = ((ServletServerHttpRequest)request).getServletRequest();
String requestURL = ((ServletServerHttpRequest) request).getServletRequest().getRequestURL().toString();
String userId = requestURL.substring(requestURL.lastIndexOf('/')+1, requestURL.length());
attributes.put("userId", userId);
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse
response, WebSocketHandler wsHandler, Exception exception) {
}
}
- WebSocket配置类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration//SpringBoot项目启动时就会扫描此类,并将此类注册到Spring容器
@EnableWebSocket//开始WebSocket功能
public class OneLoginWebSocketConfig implements WebSocketConfigurer {
@Autowired
private OneLoginWsHandler oneLoginWsHandler;//
@Autowired
private OneLoginHandshakeInterceptor myHandshakeInterceptor;//注入拦截器
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(this.oneLoginWsHandler, "/ws/{userId}")//访问ws的请求路径,例如ws/10.1.1.226/ws/123
.setAllowedOrigins("*")//允许跨域,不配置不能访问
.addInterceptors(this.myHandshakeInterceptor);//将ws的拦截器,添加到ws
}
}
- WebSocket拦截器
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Component
public class OneLoginHandshakeInterceptor implements HandshakeInterceptor {
/**
* 握手之前,若返回false,则不建立链接
*
* @param request
* @param response
* @param wsHandler
* @param attributes
* @return
* @throws Exception
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse
response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws
Exception {
//将用户id放入socket处理器的会话(WebSocketSession)中
HttpServletRequest httpRequest = ((ServletServerHttpRequest)request).getServletRequest();
String requestURL = ((ServletServerHttpRequest) request).getServletRequest().getRequestURL().toString();
String userId = requestURL.substring(requestURL.lastIndexOf('/')+1, requestURL.length());
attributes.put("userId", userId);
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse
response, WebSocketHandler wsHandler, Exception exception) {
}
}
- 前端连接WS的HTML界面(连接WebSocket,)
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" http-equiv="Content-Type" content="text/html" charset="UTF-8"/>
<title>WebSocket 客户端</title>
</head>
<body>
<div>
WSParameter<input type="text" id="ws_value"/><br/><!--仅用来识别用户身份使用-->
<input type="button" id="btnConnection" value="connect" /><br/>
<input type="button" id="btnClose" value="close" /><br/>
<input type="button" id="btnSend" value="send" /><br/>
</div>
<script src="js/jquery-1.11.1.min.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" type="text/javascript" charset="utf-8">
var socket;
if(typeof(WebSocket) == "undefined") {
alert("您的浏览器不支持WebSocket");
}
$("#btnConnection").click(function() {
//实现化WebSocket对象,指定要连接的服务器地址与端口
var temp = $('#ws_value').val();
alert(temp);
socket = new WebSocket("ws://10.1.1.226:8081/ws/" + temp);
//打开事件
socket.onopen = function() {
alert("Socket 已打开");
};
//获得消息事件
socket.onmessage = function(msg) {
alert(msg.data);
};
socket.onclose = function(event) {
// 别处登录
if(event.code == 4001){
alert("当前账号已在别处登录");
location.replace("login.html");
}
// 正常退出
if(event.code == 4000){
location.replace("login.html");
}
// 用户信息失效
if(event.code == 4002){
location.replace("login.html");
}
}
//发生了错误事件
socket.onerror = function() {
alert("发生了错误");
}
});
//发送消息
$("#btnSend").click(function() {
socket.send("这是来自客户端的消息" + location.href + new Date());
});
//关闭
$("#btnClose").click(function() {
socket.close();
});
</script>
</body>
</html>
- 登录界面(并没有什么卵用,仅用来账号在其他设备登录,当前登录被登出时使用)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>这是登录界面</title>
</head>
<body>
<form action="http://10.1.1.226:8081/staff_archives/login2" method="get">
用户id:<input type="text" name="userId" /></p>
用户名: <input type="text" name="userName" /></p>
密码: <input type="text" name="password" /></p>
<input type="submit" value="登录" />
</form>
</body>
</html>
- 实验
①两个Parameter都输入123,代表时同一个用户,在不同的设备登录
②点击第一个连接成功,点击第二个连接成功,第一个界面显示账号在其他地方登陆