一、websocket与http
http是基于短连接的,无法实现长连接,无法实现双工通信。
websocket是基于长连接的,可以双工通信。
这两个协议都属于应用层,都是基于tcp传输层协议实现的。
websocket第一次连接的时候,是发送的http请求,请求成功后,
升级为socket连接。
第一次请求头会带着两个参数,告诉服务器,协议要升级。
服务端确认并返回后,就升级(与http无关了)
Upgrade: websocket
Connection: Upgrade
二、Websocket的作用
1、long poll 和 ajax轮询 的原理。
ajax轮询就是客户端不断的发送请求
long poll也是客户端发送请求,只不过是阻塞的,没有数据就不返回,有了再返回。
缺点:
1、不能双工通信,服务端不能主动给客户端发送信息
2、耗费资源
3、消息推送有延迟,因为轮询是有间隔的。
2、websocket:
服务端就可以主动推送信息给客户端
只需要经过一次HTTP请求,就可以做到源源不断的信息传送了,避免了HTTP的非状态性。
无延迟,持久连接
3、WebSocket 如何工作?
(1)、原生H5加javax.websocket方式:
前端代码:
var Socket = new WebSocket(url, [protocol] );
然后基于事件,写回调函数:
// 初始化一个 WebSocket 对象
var ws = new WebSocket('ws://localhost:9998/echo');
// 建立 web socket 连接成功触发事件
ws.onopen = function() {
// 使用 send() 方法发送数据
ws.send('发送数据');
};
// 接收服务端数据时触发事件
ws.onmessage = function(evt) {
var received_msg = evt.data;
};
// 断开 web socket 连接成功触发事件
ws.onclose = function() {
};
后端代码:
声明一个处理类,写事件回调函数。
@ServerEndpoint
标记一个类是 WebSocket 的处理器
// 收到消息触发事件
@OnMessage
public void onMessage(String message, Session session){}
// 打开连接触发事件
@OnOpen
public void onOpen(Session session, EndpointConfig config, @PathParam("id") String id){}
// 关闭连接触发事件
@OnClose
public void onClose(Session session, CloseReason closeReason) {}
// 传输消息错误触发事件
@OnError
public void onError(Throwable error) {}
(2)、SockJS和Stomp+RabbitMQ的方式:
前端代码:
连接端点
var socket = new SockJS("/webServer");
//根据端点打开Stomp客户端
stompClient = Stomp.over(socket);
//客户端进行连接
stompClient.connect({}, function(frame) {
//客户端订阅消息
stompClient.subscribe(url, function(response){
}
}
// 连接消息服务器
stompClient .connect(login, password, on_connect, on_error, '/');
后台代码:
websocket配置类:
配置websocket端点,
配置请求路径关键字和请求前缀
配置点对点方式的前缀
发送消息到rabbit的工具类
通过simpmessagingtemplate发送消息到mq
发送的时候,destination 默认的模式有以下 3 种:
(1)、/exchange/<exchangeName>/[/routingKey]:
(指定交换机和路由key,就知道往哪里发了)
需要提前绑定路由key到exchange
先提前绑定路由key和交换机,然后发送的时候就根据pattern(路由key)
和<exchangeName>决定发送到哪个交换机里面的临时队列,。
(2)、/queue/<queueName>
(往默认交换机里面的指定队列发)
先在默认的交换机里面,创建名为queueName的队列,
路由key也是<queueName>,与默认交换机作绑定,发送消息就发送到这个队列。
(3)、/topic/<topicName>
(往amq.topic exchange交换机上的被topicName绑定的队列上发)
创建出临时的 queue,并根据 routingkey <topicName>绑定
到amq.topic exchange 上,同时实现对该 queue 的订阅。
总结:
rabbitmq的Stomp插件,就是在websocket和stomp之间做了一层桥接,
可以让浏览器订阅到rabbitmq里面的消息
在后端,使用simpmessagingtemplate往指定的队列发送消息
发送的路径destination有默认的格式,方便操作。
3、Stomp原理:
直接使用WebSocket(或SockJS)就很类似于使用TCP套接字来编写Web应用。
Stomp就类似于http一样,可以规定好消息协议格式。
就像HTTP在TCP套接字之上添加了请求-响应模型层一样,
STOMP在WebSocket之上提供了一个基于帧的线路格式层,用来定义消息的语义。
STOMP帧由命令、一个或多个头信息以及消息主体所组成。
>>> SEND
transaction:tx-0
destination:/app/marco
content-length:20
{"message":"Marco!"}
STOMP命令是send,表明会发送一些内容.
紧接着是三个头信息:
transaction:tx-0:表示消息的的事务机制
destination:/app/marco:表示消息要发送到哪里的目的地,
content-length:20:消息的大小
(2)、启用STOMP功能
以 /app 开头的消息都会被路由到带有@MessageMapping
或 @SubscribeMapping 注解的方法中
以/topic 或 /queue 开头的消息都会发送到STOMP代理
以/user开头的消息会将消息重路由到某个用户独有的目的地上。
(3)、处理来自客户端的STOMP消息
@MessageMapping("/marco")
@SendTo("/topic/marco")
public Shout stompHandle(Shout shout){
LOGGER.info("接收到消息:" + shout.getMessage());
Shout s = new Shout();
s.setMessage("Polo!");
return s;
}
@MessageMapping 指定目的地是“/app/marco”,用于客户端访问,
并且返回值会发到消息代理
@SendTo 注解重写了消息代理的目的地,
用于指定消息发送到哪里(simpmessagingtemplate)也可以实现同样的效果。
(4)、为指定用户发送消息
convertAndSendToUser
simpMessageSendingOperations.convertAndSendToUser(user.getName(), "/queue/shouts", shout);
最终会把消息发送到 /user/sername/queue/shouts
然后客户端去订阅这个路径的队列就好了。