Featured image of post WebSocket

WebSocket

Java后端实现Websocket服务

WebSocket概念

早期,很多网站为了实现推送技术,所用的技术都是轮询(也叫短轮询)。轮询是指由浏览器每隔一段时间向服务器发出 HTTP 请求,然后服务器返回最新的数据给客户端

这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而 HTTP 请求与响应可能会包含较长的头部,其中真正有效的数据可能只是很小的一部分,所以这样会消耗很多带宽资源

在这种情况下,HTML5 定义了 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯

  • WebSocket 与 HTTP 和 HTTPS 使用相同的 TCP 端口,可以绕过大多数防火墙的限制
  • Websocket 使用 ws 或 wss 的统一资源标志符(URI),其中 wss 表示使用了 TLS 的 Websocket

WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。WebSocket 协议在 2011 年由 IETF 标准化为 RFC 6455,后由 RFC 7936 补充规范。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输

websocketvshttp

WebSocket优点

  • 较少的控制开销:在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小
  • 更强的实时性:由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少
  • 保持连接状态:与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息
  • 更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容
  • 可以支持扩展:WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议

由于 WebSocket 拥有上述的优点,所以它被广泛地应用在即时通讯/IM、实时音视频、在线教育和游戏等领域

注解实现WebSocket

依赖引入

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

监听类

/**
 * 监听Ws地址
 */
@ServerEndpoint("/myWs")
@Component
@Slf4j
public class WsServerEndpoint {

    static Map<String,Session> sessionMap = new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(Session session){
        sessionMap.put(session.getId(),session);
        log.info("websocket is open: "+ session.getId());
    }

    @OnClose
    public void onClose(Session session){
        sessionMap.remove(session.getId());
        log.info("websocket is close");
    }

    @OnMessage
    public String onMessage(String text){
        log.info("接收到消息:"+text);
        return "已收到消息";
    }

    @Scheduled(fixedRate = 2000)
    public void sendMsg() throws IOException {
        for(String key : sessionMap.keySet()){
            sessionMap.get(key).getBasicRemote().sendText("111");
        }
    }
}

配置类

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

启动类

@SpringBootApplication
@EnableScheduling
public class WebsocketApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebsocketApplication.class, args);
    }
}

前端页面

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>

</body>

<script>
    let ws = new WebSocket("ws://localhost:8080/myWs")
    ws.onopen = function (){
        ws.send("hello")
    }
    ws.onmessage = function (message){
        console.log(message.data)
    }
</script>
</html>

Spring抽象类,接口实现WebSocket

依赖引入

同上

握手拦截器

@Slf4j
@Component
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);
    }
}

WebSocket拦截器

@Component
public class MyWsHandler extends AbstractWebSocketHandler {

    private static Map<String,SessionBean> sessionBeanMap;
    private static AtomicInteger clientIdMaker;
    static {
        sessionBeanMap=  new ConcurrentHashMap<>();
        clientIdMaker = new AtomicInteger(0);
    }
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        SessionBean sessionBean = new SessionBean(session,clientIdMaker.getAndIncrement());
        sessionBeanMap.put(session.getId(),sessionBean);
        super.afterConnectionEstablished(session);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        System.out.println(sessionBeanMap.get(session.getId()).getClientId() + ":" + message.getPayload());
    }

    @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);
        sessionBeanMap.remove(session.getId());
        System.out.println(sessionBeanMap.get(session.getId()).getClientId() + "关闭连接");
    }

    @Scheduled(fixedRate = 2000)
    public void sendMsg() throws IOException {
        for(String key : sessionBeanMap.keySet()){
            sessionBeanMap.get(key).getWebSocketSession().sendMessage(new TextMessage("111"));
        }
    }
}

配置类

@Configuration
@EnableWebSocket
public class MyWsConfig implements WebSocketConfigurer {

    @Resource
    private MyWsHandler myWsHandler;

    @Resource
    private MyWsInterceptor myWsInterceptor;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myWsHandler,"/myWs1").addInterceptors(myWsInterceptor)
                .setAllowedOriginPatterns("*");
    }
}

前端页面以及启动类

同上,但是前端页面需要修改WebSocket链接路径

版权所有,转载请注明出处!
渝ICP备2022006471
Built with Hugo
主题 StackJimmy 设计