SpringBoot集成WebSocket实现简易的聊天室
简介
WebSocket简介
WebSocket 协议在2008年诞生,2011年成为国际标准。目前主流浏览器都有良好的支持。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
WebSocket特点
- 建立在 TCP 协议之上,服务器端的实现比较容易
- 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器
- 数据格式比较轻量,性能开销小,通信高效
- 可以发送文本,也可以发送二进制数据
- 没有同源限制,客户端可以与任意服务器通信
- 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
整合实例
前期准备
1、pom.xml中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2、在SpringBoot配置文件中配置页面访问的端口
server:
port: 8888
3、创建WebSocket配置文件
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
自己给自己发消息
1、创建index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My WebSocket</title>
</head>
<body>
消息内容:<input id="text" type="text" />
<button onclick="send()">发送</button>
<button onclick="closeWebSocket()">关闭连接</button>
<div id="message"></div>
<script type="text/javascript">
var websocket=null;
if("WebSocket" in window){
websocket=new WebSocket("ws://localhost:8888/websocket/one");
}else {
alert('不支持WebSocket');
}
websocket.onerror=function () {
setMessageInnerHTML('websocket连接发生错误!');
};
websocket.onopen=function (event) {
setMessageInnerHTML('websocket连接建立成功!');
};
websocket.onmessage=function (event) {
var message=event.data;
setMessageInnerHTML(message);
};
websocket.onclose=function () {
setMessageInnerHTML('websocket连接关闭!');
};
websocket.onbeforeunload=function () {
websocket.close();
};
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML+=innerHTML+'<br/>';
}
function closeWebSocket() {
websocket.close();
}
function send(){
var message=document.getElementById('text').value;
websocket.send(message);
}
</script>
</body>
</html>
2、创建一个服务端,@ServerEndpoint注解是服务端与客户端交互的关键,其值(/websocket/one)要与index页面中的请求路径对应
@Slf4j
@ServerEndpoint(value = "/websocket/one")
@Component
public class OneWebSocket {
//当前在线连接数
private static AtomicInteger onlineCount=new AtomicInteger(0);
@OnOpen
public void onOpen(Session session){
onlineCount.incrementAndGet(); //在线数+1
log.info("增加连接:{},当前在线人数:{}",session.getId(),onlineCount.get());
}
@OnClose
public void onClose(Session session){
onlineCount.decrementAndGet(); //在线数-1
log.info("关闭连接:{},当前在线人数:{}",session.getId(),onlineCount.get());
}
@OnMessage
public void onMessage(String message,Session session){
log.info("服务端收到客户端【{}】的消息:{}",session.getId(),message);
sendMessage("Hello, "+message,session);
}
@OnError
public void one rror(Session session,Throwable error){
log.error("发送错误!");
error.printStackTrace();
}
private void sendMessage(String message,Session toSession){
try {
log.info("服务端给客户端【{}】发送消息{}",toSession.getId(),message);
toSession.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error("服务端发送消息给客户端失败:{}",e);
}
}
}
3、访问index页面,进入页面时就会建立服务端与客户端的连接,在文本框中输入要发送的内容后,点击发送就能看到下面的效果了
群发消息
1、修改index.html的连接地址
websocket=new WebSocket("ws://localhost:8888/websocket/oneToMany");
2、创建服务端
@Slf4j
@ServerEndpoint(value = "/websocket/oneToMany")
@Component
public class OneToManyWebSocket {
//当前在线连接数
private static AtomicInteger onlineCount=new AtomicInteger(0);
//当前在线的客户端
private static Map<String, Session> clients=new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session){
onlineCount.incrementAndGet(); //在线数+1
clients.put(session.getId(),session);
log.info("增加连接:{},当前在线人数:{}",session.getId(),onlineCount.get());
}
@OnClose
public void onClose(Session session){
onlineCount.decrementAndGet(); //在线数-1
clients.remove(session.getId());
log.info("关闭连接:{},当前在线人数:{}",session.getId(),onlineCount.get());
}
@OnMessage
public void onMessage(String message,Session session){
log.info("服务端收到客户端【{}】的消息:{}",session.getId(),message);
sendMessage("Hello, "+message,session);
}
@OnError
public void one rror(Session session,Throwable error){
log.error("发送错误!");
error.printStackTrace();
}
private void sendMessage(String message,Session fromSession){
for (Map.Entry<String,Session> sessionEntry:clients.entrySet()){
Session toSession=sessionEntry.getValue();
if (!fromSession.getId().equals(toSession.getId())){
log.info("服务端给客户端【{}】发送消息{}",toSession.getId(),message);
toSession.getAsyncRemote().sendText(message);
}
}
}
}
3、在浏览器中打开多个页面,选择一个页面发送消息
一对一聊天室
1、修改index.html页面代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My WebSocket</title>
</head>
<body>
<div>
ID:<span id="id"></span>
</div>
消息内容:<input id="text" type="text" />
目标ID:<input id="toId" type="text" />
<button onclick="send()">发送</button>
<button onclick="closeWebSocket()">关闭连接</button>
<div id="message"></div>
<script type="text/javascript">
var websocket=null;
if("WebSocket" in window){
websocket=new WebSocket("ws://localhost:8888/websocket/oneToOne");
}else {
alert('不支持WebSocket');
}
websocket.onerror=function () {
setMessageInnerHTML('websocket连接发生错误!');
};
websocket.onopen=function (event) {
setMessageInnerHTML('websocket连接建立成功!');
};
websocket.onmessage=function (event) {
var message=event.data;
if (message.length>11 && message.substring(0,12)=="send userId:"){
document.getElementById('id').innerText=message.substring(12,message.length);
}else{
setMessageInnerHTML(message);
}
};
websocket.onclose=function () {
setMessageInnerHTML('websocket连接关闭!');
};
websocket.onbeforeunload=function () {
websocket.close();
};
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML+=innerHTML+'<br/>';
}
function closeWebSocket() {
websocket.close();
}
function send(){
var id=document.getElementById('toId').value;
var message=document.getElementById('text').value;
var sendMsg="{\"userId\":"+id+",\"message\":\""+message+"\"}";
websocket.send(sendMsg);
}
</script>
</body>
</html>
2、创建服务端和MyMessage类
@Slf4j
@ServerEndpoint(value = "/websocket/oneToOne")
@Component
public class OneToOneWebSocket {
//当前在线连接数
private static AtomicInteger onlineCount=new AtomicInteger(0);
//当前在线的客户端
private static Map<String, Session> clients=new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session){
onlineCount.incrementAndGet(); //在线数+1
clients.put(session.getId(),session);
log.info("增加连接:{},当前在线人数:{}",session.getId(),onlineCount.get());
sendMessage("send userId:"+session.getId(),session);
}
@OnClose
public void onClose(Session session){
onlineCount.decrementAndGet(); //在线数-1
clients.remove(session.getId());
log.info("关闭连接:{},当前在线人数:{}",session.getId(),onlineCount.get());
}
@OnMessage
public void onMessage(String message,Session session){
log.info("服务端收到客户端【{}】的消息:{}",session.getId(),message);
try{
MyMessage myMessage= JSON.parseObject(message, MyMessage.class);
if (myMessage!=null){
Session toSession=clients.get(myMessage.getUserId());
if (toSession!=null){
sendMessage(session.getId()+":"+myMessage.getMessage(),toSession);
}
sendMessage(session.getId()+":"+myMessage.getMessage(),session);
}
}catch (Exception e){
log.error("解析失败:{}",e);
}
}
@OnError
public void one rror(Session session,Throwable error){
log.error("发送错误!");
error.printStackTrace();
}
private void sendMessage(String message,Session toSession){
try {
log.info("服务端给客户端【{}】发送消息{}",toSession.getId(),message);
toSession.getBasicRemote().sendText(message);
} catch (Exception e) {
log.error("服务端发送消息给客户端失败:{}",e);
}
}
}
@Data
@NoArgsConstructor
public class MyMessage {
private String userId;
private String message;
}
3、浏览器中打开两个页面