WebSocket是一种网络通信协议,RFC6455定义了它的通信标准
WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议
HTTP协议是一种无状态的,无连接的,单向的应用层协议。
它采用了请求/响应模式,通信请求只能由客户端发起,服务端对请求做出应答处理
这种通信模型有一个弊端: HTTP协议无法实现服务器主动向客户端发起消息
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。
大多数Web应用程序将通过频繁的异步AJAX请求实现长轮询。
轮询的效率低,非常浪费资源(因为必须不停连接,获知HTTP连接始终打开)
http协议:
websocket协议:
总结:
WebSocket
是一种在单个TCP
连接上进行全双工通信的协议。WebSocket
通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API
也被W3C
定为标准。WebSocket
使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API
中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
。
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
总结:websocket主要是服务器主动向客户端推送消息,与客户端保持长连接,当然前提是客户端不刷新页面,否则无意义
本协议有两部分:握手和数据传输
握手是基于http协议的
来自客户端的握手看起来像如下的形式:
GET ws://localhost/chat HTTP/1.1
Host:localhost
Upgrade:websocket
Connection:Upgrade
Sec-Websocket-Accept: dGh1IHNhbxBsZSBub25jZQ==
Sec-Websocket-Extensions:permessage-deflate
来自服务器的握手看起来像如下形式:
HTTP/1.1 101 Switching Protocols
Upgrade:websocket
Connection:Upgrade
Sec-Websocket-Accept: s3pPLMiTxaQ9kYGzzhZRbk+xOo=
Sec-Websocket-Extensions:permessage-deflate
字段说明:
头名称 | 说明 |
---|---|
Connection:Upgrade | 标识该HTTP请求时一个协议升级请求 |
Upgrade:websocket | 协议升级为websocket协议 |
Sec-Websocket-Version: 13 | 客户端支持webSocket版本 |
Sec-Websocket-Key | 客户端采用base64编码的24位随机字符序列,服务器接受客户端HTTP协议升级的证明,要求服务器端响应一个对应加密的Sec-WebSocket-Accept头信息作为应答 |
Sec-Websocket-Extensions | 协议扩展类型 |
实现Websocket的Web浏览器将通过Websocket对象公开所有必须的客户端功能(主要指支持Html5的浏览器)
以下API用于创建Websocket对象:
var ws=new WebSocket(url);
参数url格式说明: ws://ip地址:端口号/资源名称
WebSocket对象的相关事件
事件 | 事件处理程序 | 描述 |
---|---|---|
open | websocket对象.onopen | 连接建立时触发 |
message | websocket对象.onmessgae | 客户端接收到服务端数据时触发 |
error | websocket对象.onerror | 通信发生错误时触发 |
close | websocket对象.onclose | 连接关闭时触发 |
WebSocket对象的相关方法:
方法 | 描述 |
---|---|
send() | 使用连接发送数据 |
close() | 关闭连接 |
Tomcat的7.0.5版本开始支持WebSocket,并且实现了JAVA WebSocket规范(JSR356)
Java``WebSocket
应用一系列的WebSocketEndpoint
组成,EndPoint
是一个java
对象,代表Websocket
链接的一端,对于服务端,我们可以视处理具体WebSocket
消息的接口,就像servelt
之与http
请求一样
EndPoint和唯一个连接的客户端一一对应,例如张三登录进聊天室,那么服务端就产生一个EndPoint对象与之对应,如果有多个人登录聊天室,那么服务端就会产生多个Endpoint对象
我们可以通过两种方式定义Endpoint
:
javax.websocket.Endpoint
并实现其方法POJO
,并添加@ServerEndpoint
相关注解Endpoint
实例在WebSocket
握手时创建,并在客户端与服务端链接过程中有效,最后再链接关闭时结束。
在Endpoint
接口中明确定义了与其生命周期相关的方法,规范实现这确保生命周期的各个阶段调用实例的相关方法。
生命周期的方法如下:
方法 | 含义描述 | 注解 |
---|---|---|
onClose | 当会话关闭时调用 | @OnClose |
onOpen | 当开启一个新的会话时调用,该方法是客户端与服务器端握手成功后调用的方法 | @OnOpen |
onError | 当链接过程中出现异常时调用 | @OnError |
通过为Session
添加MessageHandler
消息处理器来接收消息,当采用注解方式定义Endpoint
时,我们还可以通过@OnMessgae
注解指定接收消息的方法
该session不是属于http里面的session对象,而是属于websocket协议里面的session对象
发送消息则由RemoteEndpoint
完成,其实例由Session
维护,根据使用情况,我们可以通过Session.getBasicRemote
获取通过消息发送的实例,然后调用其sendXxx()
方法就可以发送消息,可以通过Session.getAsyncRemote
获取异步消息发送实例
服务端代码:
@ServerEndPonit("/robin")
public class ChatEndPonit
{
private static Set<ChatEndPonit> webSocketSet=new HashSet<>();
private Session session;
@OnMessgae
public void OnMessage(String messgae,Session session)
{
System.out.println("接收的消息是:"+messgae);
System.out.println(session);
//将消息发送给其他的用户
for(Chat chat:webSocketSet)
{
if(chat!=this)
{
chat.session.getBasicRemote().sendText(messgae);
}
}
}
@OnOpen
public void onOpen(Session session)
{
this.session=session;
webSocketSet.add(this);
}
@OnClose
public void onClose(Session session)
{
System.out.println("连接关闭了");
}
@OnError
public void OnError(Session session,Throwable error)
{
System.out.println("出错了...."+error.getMessage());
}
}
通过Websocket实现一个简易的聊天室功能
(1)登录聊天室
(2)登录之后,进入聊天界面进行聊天
登录成功以后,呈现出一下的效果
当我们想和李四聊天时就点击好友列表的李四,效果如下:
接下来就可以进行聊天了,张三的界面如下:
李四的界面如下:
最后一个是OnClose,不是OnError
客户端—>服务端
{“toName”:“张三”,“message”:“你好”}
服务端---->客户端
系统消息格式: {“isSystem”:true,“fromName”:null,“message”:{“李四”,“王五”}}
推送消息给某一个客户端 {“isSystem”:true,“formName”:“张三”,“message”:“你好”}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.5.5</version>
</parent>
<groupId>org.example</groupId>
<artifactId>WebSocket</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- devtools热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>true</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入Thymeleaf模板引擎启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 打jar包如何不配置该插件,打出来的jar包没有清单文件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
引入相关页面,可以自己准备
pojo类
//浏览器发送给服务器的websocket数据
@Data
public class Message
{
private String toName;
private String message;
}
//服务器发送给浏览器的websocket数据
@Data
public class ResultMessage
{
private boolean isSystem;//是否是系统消息
private String fromName;
private Object message;//如果是系统消息是数组
}
//用于登录响应给浏览器的数据
@Data
public class Result
{
private boolean flag;
private String message;
}
//用来封装消息的工具类
public class MessageUtils
{
public static String getMessage(boolean isSystemMessgae,String fromName,Object message)
{
try {
//服务端发送给浏览器的消息格式
ResultMessage result=new ResultMessage();
result.setSystem(isSystemMessgae);
result.setMessage(message);
//不是系统消息
if(fromName!=null)
{
result.setFromName(fromName);
}
//Jackson的主要类
ObjectMapper mapper=new ObjectMapper();
//将对象转换为json字符串
return mapper.writeValueAsString(result);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
login.html:使用异步进行请求发送
$('#login-button').click(function (event) {
//ajax发送异步登录请求
$.ajax({
url:"login",
type:"post",
data:$("form").serialize(),
success:function (res)
{
if(res.flag)
{
//跳转到main.html页面
location.href="main.html";
}
else
{
alert(res.message);
}
}
})
//阻止表单的默认提交行为
return false;
});
userController:进行登录逻辑处理
@RestController
public class UserController
{
@PostMapping("/login")
public Result login(User user, HttpSession httpSession)
{
Result result=new Result();
if(user!=null&&"123".equals(user.getPwd()))
{
result.setFlag(true);
//将用户名存储到session对象中
httpSession.setAttribute("user",user.getName());
}
else
{
result.setFlag(false);
result.setMessage("登录失败");
}
return result;
}
}
main.html:页面加载完成后,发送请求获取当前登录的用户名
var username;
$.ajax({
url:"getUserName",
type:"post",
success:function (res)
{
username=res
$("#userName").html("用户: "+res+"<span style='float: right;color: green'>在线</span>")
}
,async:false//同步请求
})
在userController中添加一个getUserName方法,用来从session中获取当前登录的用户名并响应给浏览器
@PostMapping("/getUserName")
public String getUserName(HttpSession httpSession)
{
String username=(String)httpSession.getAttribute("user");
return username;
}
代码太多,不方便贴出,具体可以去我的gitee仓库,下载查看
window-onbeforeunload 的使用
离开页面的判断:window.Onunload与window.onbeforeunload的区别(IE下a标签触发问题)
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function() {
ws.close();
}
@ServerEndpoint注解是服务端与客户端交互的关键,其值(/test/one)得与index页面中的请求路径对应。
(备注:服务端关闭或者浏览器关闭或者刷新的效果,都会导致连接断开)
@Configuration
public class WebSocketStompConfig {
//这个bean的注册,用于扫描带有@ServerEndpoint的注解成为websocket ,如果你使用外置的tomcat就不需要该配置文件
@Bean
public ServerEndpointExporter serverEndpointExporter()
{
return new ServerEndpointExporter();
}
}
@RequestParam,@PathParam,@PathVariable等注解区别
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator
{
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
HttpSession httpSession= (HttpSession) request.getHttpSession();
//将httpsession对象存储到配置对象中
//Map<String, Object> getUserProperties();
//向map集合中存放我们需要的数据,我们可以在其他使用到ServerEndpointConfig对象的类中取出该属性
//因为都是一个ServerEndpointConfig对象
sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
}
}
@ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfigurator.class)//指定请求路径
@Component
public class chatEndPoint
{
@OnOpen//链接建立时被调用
public void OnOpen(Session session, EndpointConfig config)
{
System.out.println("=====================");
System.out.println("连接建立");
//将局部的sesssion对象赋值给成员session
this.session=session;
//获取httpsession对象
httpSession=(HttpSession) config.getUserProperties().get(HttpSession.class.getName());
//从httpsession对象中获取用户名
String username=(String) httpSession.getAttribute("user");
}
}
什么两个地方的EndPointCong一致:
下面的EndpointConfig 是一个接口,里面定义了一个map用来存放当前客户端连接的数据
public interface EndpointConfig {
List<Class<? extends Encoder>> getEncoders();
List<Class<? extends Decoder>> getDecoders();
Map<String, Object> getUserProperties();
}
ServerEndpointConfig 继承EndpointConfig
public interface ServerEndpointConfig extends EndpointConfig {}
每天注解学习(八)获取HttpSession的工具类,源码详细分析@ServerEndpoint注解
window.location.reload(false);window.location.reload(true);history.Go(0)区别
SpringBoot2.0集成WebSocket,实现后台向前端推送信息
关于controller调用controller/service调用service/util调用service/websocket中autowired的解决方法
SpringBoot2+WebSocket之聊天应用实战(优化版本)
WebSocket 详解教程
SpringBoot 整合WebSocket 简单实战案例
WebSocket 教程—偏向前端代码实现—阮一峰的网络日志
Springboot2构建基于Netty的高性能Websocket服务器(netty-websocket-spring-boot-starter)
SpringBoot2+Netty+WebSocket(netty实现websocket,支持URL参数)
Spring websocket+Stomp+SockJS 实现实时通信 详解
Spring使用WebSocket、SockJS、STOMP实现消息功能
Springboot 整合Websocket+Stomp协议+RabbitMQ做消息代理 实例教程
Springboot 整合 WebSocket ,使用STOMP协议+Redis 解决负载场景问题(二)
Springboot 整合 WebSocket ,使用STOMP协议 ,前后端整合实战 (一)
使用websocket踩到的坑儿,,,
添加链接描述WebSocket区分不同客户端两种方法(HttpSession和@PathParam)
Websocket如何获取httpSession,区分不同客户端
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator
{
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
HttpSession httpSession= (HttpSession) request.getHttpSession();
//将httpsession对象存储到配置对象中
//Map<String, Object> getUserProperties();
//向map集合中存放我们需要的数据,我们可以在其他使用到ServerEndpointConfig对象的类中取出该属性
//因为都是一个ServerEndpointConfig对象
sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
super.modifyHandshake(sec, request, response);
}
}
这里GetHttpSessionConfigurator 对于所有客户端来说只有一份
ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response传入的这三个参数对于所有客户端来说只有一份
因此这里request.getHttpSession()得到的HttpSession对于所有客户端来说也只有一份
待实现功能:信息的存储,可以使用localstorage,但是不够安全,信息会丢失,更建议结合rebbitmq或者redis做消息的缓存处理
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/m0_53157173/article/details/120957578
内容来源于网络,如有侵权,请联系作者删除!