首发在公众号【全栈开发日记】
WebSocket是一种网络通信协议。和HTTP协议一样,都是基于TCP协议来传输数据。
为什么需要WebSocket?因为有一些功能HTTP无法满足。
HTTP协议是一种无状态的、无连接的、单项的应用层协议。基于HTTP协议的通信请求只能由客户端发起,服务端对请求做出处理。这种通信模型有一个弊端,它无法实现服务器主动向客户端发起请求。
以微信举例,张三给李四发了一条消息,这条消息来到了服务器后无法给李四的客户端推送,只能等待李四去刷新客户端来询问服务器是否有新的消息。
怎么解决这种问题呢?
1、轮询:客户端定时向服务器发送请求,服务器会马上进行处理,并关闭连接。
2、长轮询:客户端向服务器发送HTTP请求,服务器接到请求后暂不返回响应信息,这时连接会一直保持,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
3、长连接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求或是采用xhr请求,服务器端就能源源不断地往客户端输入数据。对于服务器的要求太高了。
4、WebSocket:在WebSocket出来之前,服务器想要给客户端主动推送消息的话,需要一直保持连接。而现在服务器已经知道的客户端的门牌号了,那么它们就不需要再保持联系了,服务器直接根据门牌号送货上门。
let ws=new WebSocket(url);
参数url
格式说明:ws://IP地址:端口号/资源名称
事件 | 事件处理程序 | 描述 |
---|---|---|
open | websocket 对象.onopen |
连接建立时触发 |
message | websocket 对象.onmessage |
客户端接收服务端数据时触发 |
error | websocket 对象.onerror |
通信发生错误时触发 |
close | websocket 对象.onclose |
连接关闭时触发 |
WebSocket
方法WebSocket
对象的相关方法:
方法 | 描述 |
---|---|
send() |
使用连接发送数据 |
如下为Vue示例代码:
websocket
对象let ws=new WebSocket('ws://IP地址/接口');
ws
绑定事件 data() {
return {
socket:''
}
},
methods:{
init(){
// 实例化socket
this.socket = new WebSocket(this.$ws+'/news')
// 监听socket连接
this.socket.onopen = this.open
// 监听socket错误信息
this.socket.onerror = this.error
// 监听socket消息
this.socket.onmessage = this.getMessage
// 监听socket断开连接的消息
this.socket.close=this.close
},
open(){
console.log("socket连接成功")
},
error(){
console.log("连接错误")
},
getMessage(message){
console.log("收到消息");
console.log(message);
},
close(){
console.log("连接关闭");
},
},
mounted(){
setTimeout(()=>{
this.$root.loading=false
}, 1500);
this.init();
},
继承类javax.websocket.Endpoint
并实现其方法。
定义一个POJO,并添加ServerEndpoint
相关注解。
① 服务端如何接收客户端发送的数据?
通过为Session
添加MessageHandler
消息处理器来接收消息,当采用注解方式定义Endpoint
时,我们还可以通过OnMessage
注解指定接收消息的方法。
② 服务端如何推送数据给客户端?
发送消息由RemoteEndpoint
完成,其实例由Session
维护,根据使用情况,我们可以通过Session.getBasicRemote
获取同步消息发送的实例,然后调用其sendXxx()
方法就可以发送消息,可以通过Session.getAsyncRemote
获取异步消息发送实例。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
// 获取HttpSession对象
HttpSession httpSession = (HttpSession) request.getHttpSession();
// 将httpSession对象存储到配置对象中
sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* 这个Bean的作用是自动注册使用了@ServerEndpoint注解的Bean
* @author 二饭
* @email [email protected]
*/
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
import cn.tworice.blog.ws.GetHttpSessionConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint(value = "/news",configurator = GetHttpSessionConfig.class)
@Component
@Slf4j
public class WsController {
// 用来存储每一个客户端对象对应的WsController对象
private static Map<String, WsController> onlineUsers = new ConcurrentHashMap<>();
// 声明Session对象,通过该对象可以给指定的用户发送请求
private Session session;
// 声明一个HttpSession对象,之前在HttpSession中存储了用户名
private HttpSession httpSession;
/**
* 连接建立时被调用
* @author 二饭
* @email [email protected]
*/
@OnOpen
public void onOpen(Session session, EndpointConfig config){
// 将局部的session对象赋值给成员session对象
this.session=session;
// 获取httpSession对象
HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
this.httpSession = httpSession;
String username = (String) httpSession.getAttribute("user");
// 将当前对象存储到容器中
onlineUsers.put(username, this);
// 将当前在线用户的用户名推送给所有客户端
}
/**
* 接收到客户端消息时被调用
* @author 二饭
* @email [email protected]
*/
@OnMessage
public void onMessage(String message,Session session){
}
/**
* 连接被关闭时调用
* @author 二饭
* @email [email protected]
*/
@OnClose
public void onClose(Session session){
}
/**
* 将消息广播给所有用户
* 将对象转为json字符串传递
* @author 二饭
* @email [email protected]
*/
public void broadcastAllUser(String message){
try {
Set<String> strings = onlineUsers.keySet();
for(String name:strings){
WsController wsController = onlineUsers.get(name);
wsController.session.getBasicRemote().sendText(message);
}
}catch (Exception ex){
log.error("广播消息出现异常");
ex.printStackTrace();
}
}
}
{"toName":"张三","message":"你好"}
系统消息格式:
{"isSystem":true,"fromName":null,"message":{"李四","王五"}}
推送给某一个的消息格式:
{"isSystem":true,"fromName":"张三","message":"你好"}