文章目录
什么是websocket?
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
为什么有了HTTP协议还要WebSocket
HTTP协议采用的是客户端(浏览器)轮询的方式,即客户端发送请求,服务端做出响应,为了获取最新的数据,需要不断的轮询发出HTTP请求,占用大量带宽。
WebSocket采用了一些特殊的报头,使得浏览器和服务器只需要通过"握手"建立一条连接通道后,此链接保持活跃状态,之后的客户端和服务器的通信都使用这个连接,解决了Web实时性的问题,相比于HTTP有以下好处:
- 一个Web客户端只建立一个TCP连接
- WebSocket服务端可以主动推送(push)数据到Web客户端
- 有更加轻量级的头,减少了数据传输量
特点
- 建立在TCP协议只上,服务端比较容易实现
- 于HTTP协议有良好的兼容性,默认端口也是80和443,握手阶段使用HTTP协议,因此握手时不容
易屏蔽,能通过各种HTTP代理服务器 - 数据格式轻量,通信高效且节省带宽
- 支持传输文本数据和二进制数据
- 没有同源限制,客户端可以与任意服务器通信
- 也支持加密传输,WS+SSL,URL形如wss://
先建一个springboot项目
代码演示
引入pom文件
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version> <!-- 请使用当前最新稳定版 -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
新建spring文件夹
在com/hsh下新建
创建MyWsConfig
java
package com.mc.wsdemo.spring;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import javax.annotation.Resource;
@Configuration
@EnableWebSocket
public class MyWsConfig implements WebSocketConfigurer {
@Resource
MyWsHandler myWsHandler;
@Resource
MyWsInterceptor myWsInterceptor;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myWsHandler,"/myWs1").addInterceptors(myWsInterceptor).setAllowedOrigins("*");
}
}
创建SessionBean
java
package com.mc.wsdemo.spring;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.web.socket.WebSocketSession;
@AllArgsConstructor
@Data
public class SessionBean {
private WebSocketSession webSocketSession;
private Integer clientId;
}
创建MyWsHandler
java
package com.mc.wsdemo.spring;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* web socket 主处理程序
*/
@Slf4j
@Component
public class MyWsHandler extends AbstractWebSocketHandler {
private static Map<String,SessionBean> sessionBeanMap ;
private static AtomicInteger clientIdMaker;
private static StringBuffer stringBuffer;
static {
sessionBeanMap = new ConcurrentHashMap<>();
clientIdMaker = new AtomicInteger(0);
stringBuffer = new StringBuffer();
}
//连接建立
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
super.afterConnectionEstablished(session);
// (会话,id)
SessionBean sessionBean = new SessionBean(session,clientIdMaker.getAndIncrement());
// 加入map集合
sessionBeanMap.put(session.getId(),sessionBean);
// 打印日志
log.info(sessionBeanMap.get(session.getId()).getClientId()+"建立了连接");
// 谁进入群聊 String message = id+进入了群聊
stringBuffer.append(sessionBeanMap.get(session.getId()).getClientId()+"进入了群聊<br/>");
// 调用群发的方法 sendMessage(message)
sendMessage(sessionBeanMap);
}
//收到消息
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
super.handleTextMessage(session, message);
log.info(sessionBeanMap.get(session.getId()).getClientId()+":"+message.getPayload());
// String message = id+"前端发送的消息"
stringBuffer.append(sessionBeanMap.get(session.getId()).getClientId()+":"+message.getPayload()+"<br/>");
// 调用群发方法
sendMessage(sessionBeanMap);
}
//传输异常
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
super.handleTransportError(session, exception);
if(session.isOpen()){
session.close();
}
sessionBeanMap.remove(session.getId());
}
//连接关闭
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
super.afterConnectionClosed(session, status);
int clientId = sessionBeanMap.get(session.getId()).getClientId();
sessionBeanMap.remove(session);
log.info(clientId+"关闭了连接");
stringBuffer.append(clientId+"退出了群聊<br/>");
sendMessage(sessionBeanMap);
}
// //每2s发送给客户端心跳消息
// @Scheduled(fixedRate = 2000)
// public void sendMsg() throws IOException {
// for(String key:sessionBeanMap.keySet()){
// sessionBeanMap.get(key).getWebSocketSession().sendMessage(new TextMessage("心跳"));
// }
// }
/**
* 群发方法(包括谁进入聊天室,谁发送了信息,谁退出了聊天室)
* @param sessionBeanMap
*/
public void sendMessage(Map<String,SessionBean> sessionBeanMap){
for(String key:sessionBeanMap.keySet()){
try {
sessionBeanMap.get(key).getWebSocketSession().sendMessage(new TextMessage(stringBuffer.toString()));
} catch (IOException e) {
// e.printStackTrace();
log.error(e.getMessage());
}
}
}
}
创建MyWsInterceptor
java
package com.mc.wsdemo.spring;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import java.util.Map;
/**
* 握手拦截器
*/
@Component
@Slf4j
public class MyWsInterceptor extends HttpSessionHandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
log.info(request.getRemoteAddress().toString()+"开始握手");
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
log.info(request.getRemoteAddress().toString()+"完成握手");
super.afterHandshake(request, response, wsHandler, ex);
}
}
在static文件夹下新建ws-client.html
在resources/static文件夹下新建ws-client.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ws client</title>
</head>
<body>
<p style="border:1px solid black;width: 600px;height: 500px" id="talkMsg"></p>
<input id="message" /><button id="sendBtn" onclick="sendMsg()">发送</button>
</body>
<script>
// 给后端发消息说我建立连接
let ws = new WebSocket("ws://localhost:8080/myWs1")
// ws.onopen=function () {
// }
// 后端给前端发消息(相当于是前端监听作用,有消息进来就在P标签中 展示)
ws.onmessage=function (message) {
document.getElementById("talkMsg").innerHTML = message.data
}
// 前端给后端发消息(点击发送按钮)
function sendMsg() {
ws.send(document.getElementById("message").value)
document.getElementById("message").value=""
}
</script>
</html>
测试
输入http://localhost:8080/ws-client.html访问

在开一个浏览器窗口
