WebSocket实现在线聊天
前两天在公司接到一个需求,使用
WebSocket
实现微信扫码登陆,当时了解了一下WebSocket
,都说WebSocket
可以实现在线聊天,所以我自己也写了一个。(发个帖子证明我还活着😂)
简单介绍下什么是WebSocket
。
我们的都知道,前端向后端发起请求一般都是使用的Http
协议,但是呢Http
协议有一个不好的地方那就是,只能由客户端主动发起请求,服务器收到客户端的请求后才会返回结果数据。这种单向请求的特点,就造就了一个问题,如果服务器端数据发生了变化,客户端就很难获知。所以一般都是使用轮询的方式,不断的向后端发起请求查询是否有数据变化。但是轮询的效率非常低,严重的浪费资源。所以在这种情况下,就出现了WebSocket
协议的请求。WebSocket
协议的最大特点就是,只要客户端和服务器端建立了连接,服务器端可以主动向客户端推送消息,客户端也可以向服务器端发送消息。本文章就是要基于这个特点实现在线聊天。
环境
服务器端
- Java1.8
- Maven
- SpringBoot
客户端:
- Vue(直接用原生Html配合JavaScript也可以,我使用Vue是为了使用双向绑定)
服务器的搭建
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--WebSocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
注入Bean对象
Bean
对象的注入用法就不用介绍了吧,直接在启动类中添加以下代码就好了。此处说明一下为什么要注入此对象,
SpringBoot
的一大特点就是可以使用注解来简化配置,所以在注入这个对象之后,就可以使用WebSocket
的注解。
/**
* 用于扫描带有ServerEndpoint注解成为WebSocket
* @return ServerEndpointExporter
*/
@Bean
public ServerEndpointExporter serverEndpointExporter () {
return new ServerEndpointExporter();
}
配置Socket
通过这一个类实现Socket请求。
package cn.yanghuisen.websocketdemo.socket;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Y
*/
@Slf4j
@Component
@ServerEndpoint("/socket/chat/{userName}")
public class WebSocket {
/**
* 所有的客户端
*/
public static Map<String,Session> sessions = new HashMap<>();
/**
* 建立连接时的回调
* @param session session(客户端)
* @param userName 用户名
*/
@OnOpen
public void onOpen(Session session,@PathParam("userName") String userName) {
log.info("{},进入聊天室",userName);
if (sessions.containsKey(userName)){
session.getAsyncRemote().sendText("用户名已经存在");
return;
}
sessions.put(userName,session);
senMessage(String.format("%s:加入群聊",userName));
}
/**
* 断开连接时的回调
* @param userName 用户名
*/
@OnClose
public void onClose(Session session,@PathParam("userName") String userName) throws IOException {
session.close();
log.info("{},断开连接",userName);
sessions.remove(userName);
senMessage(String.format("%s:离开群聊",userName));
}
/**
* 发生错误时的回调
* @param session session(客户端)
* @param throwable 错误
*/
@OnError
public void onError(Session session, Throwable throwable,@PathParam("userName") String userName) throws IOException {
log.info("{},出现错误", userName);
session.close();
sessions.remove(userName);
}
/**
* 收到消息时的回调
* @param message 消息
* @param userName 用户名
*/
@OnMessage
public void onMessage(String message,@PathParam("userName") String userName) {
log.info("{}:{}",userName,message);
senMessage(String.format("%s:%s",userName,message));
}
/**
* 发送给指定客户端
* @param message 消息
* @param session session(客户端)
*/
public void senMessage(String message,Session session) {
// 发送消息
session.getAsyncRemote().sendText(message);
}
/**
* 发送给所有的客户端
* @param message 消息
*/
public void senMessage(String message) {
sessions.forEach((k,v) -> {
if (v.isOpen()) {
// 发送消息
v.getAsyncRemote().sendText(message);
}
});
}
}
- @OnOpen:此注解使用在方法上,当服务器端和客户端建立起连接时会回调配置了该注解的方法。
- @OnClose:此注解使用在方法上,当服务器端和客户端断开连接时会回调配置了该注解的方法。
- @OnError:此注解使用在方法上,当服务器端发生了异常时会回调配置了该注解的方法。
- @OnMessage:此注解使用在方法上,当服务器端收到客户端发送的消息时会回调配置了该注解的方法。
- @ServerEndpoint:类似于
@RequestMapping
注解,用于声明该请求是一个WebSocket协议的请求。
客户端的搭建
<template>
<div id="app">
<div class="left">
<input type="text" v-model="userName"> <button @click="add">加入</button>
<br><br><br>
<textarea
cols="30"
style="height: 50px"
v-model="message"
></textarea>
<br>
<button class="send" @click="sendMessage">发送</button>
<button class="reset" @click="message = null">重置</button>
<button class="close" @click="close">断开</button>
</div>
<div class="right">
<p>{{isOpenMessage}}</p>
<p v-for="(message,index) in messageList" :key="index"> {{ message }}</p>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
userName: null,
url: 'ws://localhost:8888/socket/chat/',
ws: null,
isOpenMessage: '暂未连接到服务器',
messageList: [],
message: null
}
},
created () {
this.isSocket()
},
methods: {
// 加入聊天室
add() {
// 创建WebSocket连接
this.ws = new WebSocket(this.url+this.userName);
// 建立连接成功回调
this.ws.onopen = () =>{
this.isOpenMessage = '服务器连接成功'
}
// 客户端收到服务器端消息时回调
this.ws.onmessage = (message) => {
this.messageList.push(message.data)
console.log(message.data);
}
},
// 判断是否支持WebSocket
isSocket() {
if (!('WebSocket' in window)) {
alert('该浏览器不支持WebSocket')
return false
}
return true
},
sendMessage() {
this.ws.send(this.message)
},
close() {
this.ws.close()
}
}
}
</script>
<style>
.send, .reset, .close {
margin: 10px;
}
.left, .right {
width: 300px;
float: left
}
</style>
- new WebSocket(this.url+this.userName):建立客户端和服务器端的连接,建立成功后,前端会回调onopen的方法。
- onopen:连接建立成功后回调的方法。
- onmessage:客户端收到服务器端推送的消息时回调的方法。
- this.ws.send(this.message):服务器端发送消息。
- this.ws.close():断开服务器端和客户端的连接。
客户端中有些没有写到比如onerror
和onclose
等,感兴趣的可以自己加上试试。
End
整个下来还是非常简单的,毕竟 我是一个啥也不会的程序员/(ㄒoㄒ)/~~,学的太多掉头发。