引言:什么是WebSocket?
WebSocket和http一样,都是一种网络传输协议,但是和Http协议相比,它有一点不同,它可以在单个TCP连接上进行全双工通信,通俗来说就是客户端可以向服务端发送请求,服务端也可以向客户端发送请求;
这张图网上有很多,完美展示了http和webSocket的区别:
我在这里再解释一下:
-
http协议:客户端需要向服务端发送request请求,然后服务端会对该请求进行相应的处理,处理完成后响应Response到客户端;这就是一个流程; 如果客户端不向服务端发送请求,那么服务端就不会进行响应; -
webSocket协议:首先客户端和服务端握手之后,它们之间的通道就打通了,此时客户端依然可以向服务端发送请求,服务端也可以主动的向客户端发送请求;这里是和http协议最大的差别;
总的来说:http协议服务端响应到客户端是被动的,而webSocket协议服务端请求到客户端是主动的;
案例说明
光说概念可能体会不出来,下面我来举个例子:
看直播时会有实时的弹幕,那么这个弹幕是怎么实时的显示的?
-
假设使用http协议,客户端请求服务端获取弹幕列表,服务端响应到客户端后确实可以获取到弹幕;但是一次请求就只有一次响应,但是弹幕是实时的,那么这样就无法实时获取到弹幕信息; ? 当然也有解决方法,可以通过轮询,一段时间内就自动发送一次获取弹幕的请求,但是这样的体验也不是特别好,因为只是请求获取的一个时间段的信息;弹幕还好,如果是游戏或者协同编辑等那么体验就非常不好了; -
那么使用webSocket协议就可以轻松实现这种操作,只要某个客户端A向服务端发送了弹幕,那么服务端就可以把该弹幕发送给每一个在直播间的客户端,每个客户端就可以接收到该客户端A发送的弹幕了;
所以正是因为WebSocket的这种双向通信的特点,它常用于以下领域:
- 聊天、消息、点赞
- 直播评论(弹幕)
- 游戏、协同编辑、基于位置的应用
代码展示
下面我就简单展示一下WebSocket的功能,这里就模拟一个弹幕的发送;
前后端分离项目主要技术:
后端:java8+springboot+jwt+websocket
前端:vue3+js+websocket
因为后端和前端都需要发送webSocket请求,所以前后端都需要配置websocket
首先介绍以下websocket库中几个重要的方法:
onOpen()
onClose()
onMessage()
onError()
send()
说一下前后端的交互,既然前后端有信息发送,那么信息的格式就需要确定;http请求时经常用JSON格式进行交互,所以这里前后端最好也使用JSON格式进行交互;
后端
后端java代码:
@Component
@ServerEndpoint("/websocket/message/{token}")
@Slf4j
public class WebSocketMessageServer {
private static final ConcurrentHashMap<Long, WebSocketMessageServer> onlineUsers = new ConcurrentHashMap<>();
private User user;
private Session session = null;
private static RedisCacheUtil redisCacheUtil;
@Autowired
public void setRedisCacheUtil(RedisCacheUtil redisCacheUtil) {
WebSocketMessageServer.redisCacheUtil = redisCacheUtil;
}
@OnOpen
public void onOpen(Session session, @PathParam("token") String token) throws IOException {
this.session = session;
log.info("connect user...");
Long userId = JwtAuthentication.getUserId(token);
this.user = redisCacheUtil.getCacheObject("login:" + userId);
if (this.user != null) {
onlineUsers.put(userId, this);
} else {
this.session.close();
}
log.info("当前建立连接的用户userId=>" + userId);
}
@OnClose
public void onClose() {
if (this.user != null) {
log.info("disconnect user..." + this.user.getId());
onlineUsers.remove(this.user.getId());
}
}
@OnMessage
public void onMessage(String message, Session session) {
log.info("receive match message...");
JSONObject data = JSON.parseObject(message);
String sendUser = data.getString("sendUser");
String sendMessage = data.getString("message");
onlineUsers.forEach((key, value) -> {
JSONObject responseMessage = new JSONObject();
responseMessage.put("userInfo", sendUser);
responseMessage.put("message", sendMessage);
value.sendMessage(responseMessage.toJSONString());
});
}
private void sendMessage(String message) {
synchronized (this.session) {
try {
this.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
}
?这个方法一定要理解,主要理解的是:一个用户对应一个WebSocketMessageServer对象;只要有了这个对象,那么这个用户就可以通过该对象给自己的客户端发送信息:
大致就是图中的意思,理解了这一点才能在服务端写好交互逻辑,不然你向客户端发送个信息都不知道到底发送给了谁;这一点我认为是最重要的,因为复杂的请求逻辑都是写在后端,只有理解了这里,才能写出逻辑更复杂的功能;
所以上面我的代码中:
这一段就是把从某个客户端A接收到的弹幕发送给所有客户端,这样就实现了所有客户端都会接收到客户端A发送的弹幕;
前端
<template>
<div>
<ContentField style="text-align: center">
<ul class="list-group" v-for="message in messages">
<li class="list-group-item">{{message}}</li>
</ul>
</ContentField>
<div class="col-12" style="text-align: center;">
<div class="col-sm-10" style="width: 500px; margin-left: 500px; margin-top: 20px;">
<input class="form-control" id="inputPassword" v-model="inputMessage">
<button @click="sendMessage" type="button" class="btn btn-warning" style="margin-top: 10px">发送</button>
</div>
</div>
</div>
</template>
<script>
import ContentField from "@/components/ContentField.vue";
import {onMounted, ref, onUnmounted} from "vue";
import { useStore } from 'vuex'
export default {
name: "MessageWall",
components: {
ContentField
},
setup() {
let messages = ref([])
let socket = null
const store = useStore()
let inputMessage = ref('')
onMounted(() => {
socket = new WebSocket(`ws://127.0.0.1:8080/service/websocket/message/${store.state.user.token}/`)
socket.onopen = () => {
console.log('connected...')
}
socket.onmessage= msg => {
const data = JSON.parse(msg.data);
let showMessage = data.userInfo + ':' + data.message
messages.value.push(showMessage)
showMessage = ''
}
socket.onclose = () => {
console.log("disconnected...");
}
})
onUnmounted(() => {
socket.close();
})
const sendMessage = () => {
console.log(inputMessage.value)
socket.send(JSON.stringify({
sendUser: store.state.user.username,
message: inputMessage.value
}))
}
return {
messages,
sendMessage,
inputMessage
}
}
}
</script>
前端代码没有什么难的,主要是前端向后端建立连接需要写后端的WebSocket对应的url;
然后就是正常的建立连接一套流程,接收到后端message就进行处理显示到前端界面;
而发送弹幕则需要向后端发送请求,携带JSON格式的弹幕数据,后端会通过它的onMessage接收该数据;
代码就是这样,看一看最后效果吧:
可以看到我开了三个不同浏览器窗口,每个浏览器窗口模拟一个客户端,三个浏览器都登录了不同的账号:ylx\test\admin三个账号,只要有一个客户端发送弹幕,那么另外两个也就可以接收到,这样就简单实现类一个弹幕发送;
因为案例比较简单,前端为了保证简洁,就用列表表示一条条弹幕,但是整体逻辑就是这样的,只要理解了这个,就可以做出对应的延伸拓展;
可以看一下后端日志:
开始三个客户端都进行了对应的连接,后面也都接收到了信息;
再次强调:一定要清楚一个客户端一个WebSocket对象,WebSocket对象可以为该客户端和服务端建立连接;
总结
之前了解过websocket,但是没有真正实操过,这两天也是在项目中使用到了websocket,感觉比较有意思,可以通过websocket做很多有趣的功能;所以简单总结复习一下;
如果有问题欢迎交流!
|