WebSocket

一、简介

WebSocket是一种在单个TCP连接上进行全双工通信的协议。它允许在浏览器和服务器之间建立实时的、双向的通信,从而使得实时的Web应用程序成为可能。

主要用途:实现实时的Web应用程序,例如在线游戏、聊天应用程序、股票市场行情等等。

(传统的HTTP协议是一种请求-响应协议,客户端需要不断地向服务器发送请求,服务器才能返回响应。这种方式在实现实时通信时效率很低,因为客户端需要不断地发送请求,而服务器也需要不断地返回响应。而WebSocket协议可以在客户端和服务器之间建立一条持久的连接,从而实现实时通信,避免了频繁的请求和响应,提高了通信效率。)

特点:

  1. 实时性:WebSocket可以实时地传输数据,不需要等待请求和响应的过程。

  2. 双向通信:WebSocket支持双向通信,客户端和服务器可以同时发送和接收数据。

  3. 轻量级:WebSocket协议比传统的HTTP协议更轻量级,可以减少通信的开销。

  4. 安全性:WebSocket支持加密传输,可以保证数据的安全性。

注意点:

  1. 兼容性:WebSocket协议在一些旧浏览器中不被支持,需要使用polyfill或者fallback方案来兼容旧浏览器。

  2. 资源占用:WebSocket协议需要建立一条持久连接,会占用一定的服务器资源,需要在设计应用程序时考虑并发性的问题,以保证服务器的稳定性和性能。

  3. 安全性:WebSocket协议支持加密传输,但是需要在服务器端进行配置和实现,否则会存在安全隐患。

  4. 心跳机制:由于WebSocket连接是长连接,需要使用心跳机制来保持连接的稳定性。

  5. 断线重连:由于网络环境的不稳定性,WebSocket连接可能会断开,需要使用断线重连机制来保证连接的稳定性。

  6. 跨域:由于WebSocket协议需要在服务器端进行实现,因此可能会存在跨域问题。需要在服务器端进行相关配置,以允许跨域访问。

  7. 数据格式:WebSocket协议传输的数据格式可以是文本或二进制数据,可以传输图片、音频、视频等多媒体文件,需要在应用程序中进行相应的解析和处理。

二、服务端实现

使用ServerEndpoint方式实现WebSocket服务端的步骤:

1.引入依赖

org.springframework.boot
spring-boot-starter-websocket

2.配置类

@Configuration
public class WebSocketConfig {

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

 3.ServerEndpoint方式实现

  1. 创建一个Java类,并在类上添加@ServerEndpoint注解,指定WebSocket服务端的访问路径。

  2. 在类中定义一个或多个方法,用于处理WebSocket客户端的连接、消息和关闭事件。可以使用@OnOpen、@OnMessage、@OnClose等注解来标记这些方法,以便WebSocket容器自动调用它们。

  3. 在方法中,可以使用Session对象表示WebSocket客户端的会话,通过调用Session的getBasicRemote()方法获取到一个RemoteEndpoint.Basic对象,然后使用它的sendText()、sendBinary()等方法向客户端发送消息。

下面是一个简单的示例代码,演示了如何使用ServerEndpoint方式实现一个WebSocket服务端:

@ServerEndpoint("/websocket")
public class MyWebSocketServer {

    @OnOpen
    public void onOpen(Session session) {
        System.out.println("WebSocket opened: " + session.getId());
    }

    @OnMessage
    public void onMessage(Session session, String message) {
        System.out.println("Received message: " + message);
        try {
            session.getBasicRemote().sendText("Server received message: " + message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @OnClose
    public void onClose(Session session) {
        System.out.println("WebSocket closed: " + session.getId());
    }
}

三、前端实现

类封装websocket的前端js实现:

class WebSocketClient {
  constructor(url) {
    this.url = url;
    this.ws = null;
  }

  connect() {
    this.ws = new WebSocket(this.url);
    this.ws.onopen = () => {
      console.log('WebSocket connected!');
    };
    this.ws.onmessage = (event) => {
      console.log(`Received message: ${event.data}`);
    };
    this.ws.onclose = () => {
      console.log('WebSocket closed!');
    };
    this.ws.onerror = (error) => {
      console.error(`WebSocket error: ${error}`);
    };
  }

  send(message) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(message);
    } else {
      console.error('WebSocket is not open!');
    }
  }

  close() {
    if (this.ws) {
      this.ws.close();
    }
  }
}

// 使用示例
const ws = new WebSocketClient('ws://localhost:8080');
ws.connect();
ws.send('Hello, WebSocket!');

四、心跳与断线重连

心跳:客户端和服务器之间定时发送的一些数据包,用于保持连接状态。由于WebSocket是基于TCP协议的,TCP协议本身是没有心跳机制的,因此需要应用层自己实现心跳机制,通过定时发送心跳包,可以防止因为长时间没有通信而导致的连接断开

断线重连:在WebSocket连接断开后,客户端自动尝试重新连接服务器的过程。由于网络环境不稳定,很容易出现连接断开的情况,如果没有断线重连机制,就需要手动重新连接,非常不方便。通过断线重连机制,可以让客户端自动尝试重新连接服务器,提高应用的稳定性和可靠性

前端实现:

  • 心跳:使用setInterval()函数定时发送心跳包
setInterval(function() {
  if (websocket.readyState === WebSocket.OPEN) {
    websocket.send('heartbeat');
  }
}, 30000); // 每30秒发送一次心跳包
  • 断线重连:使用onclose事件监听WebSocket连接断开事件,在事件处理函数中尝试重新连接服务器
function connect() {
  websocket = new WebSocket('ws://example.com');
  websocket.onopen = function(event) {
    console.log('WebSocket连接成功');
  };
  websocket.onclose = function(event) {
    console.log('WebSocket连接断开');
    setTimeout(function() {
      connect(); // 重新连接服务器
    }, 5000); // 5秒后重新连接
  };
  websocket.onmessage = function(event) {
    console.log('收到消息:' + event.data);
  };
}
connect(); // 第一次连接服务器

 后端实现:

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;

//通过@ServerEndpoint注解将WebSocketEndpoint类标记为WebSocket端点。
@ServerEndpoint("/websocket")
public class WebSocketEndpoint {

  private static ConcurrentHashMap sessions = new ConcurrentHashMap<>();
  private final int HEARTBEAT_INTERVAL = 30000; // 心跳包发送间隔,单位毫秒
  private final int MAX_IDLE_TIME = 60000; // 最大空闲时间,单位毫秒

//在onOpen()方法中,我们创建了一个WebSocketSession对象,将其加入到sessions集合中,并启动心跳包。
  @OnOpen
  public void onOpen(Session session) {
    WebSocketSession websocketSession = new WebSocketSession(session);
    sessions.put(session.getId(), websocketSession);
    startHeartbeat(websocketSession); // 连接成功后启动心跳包
  }

//在onClose()方法中,我们从sessions集合中移除WebSocketSession对象。
  @OnClose
  public void onClose(Session session) {
    WebSocketSession websocketSession = sessions.get(session.getId());
    if (websocketSession != null) {
      websocketSession.close();
      sessions.remove(session.getId());
    }
  }

//在onError()方法中,我们处理传输错误。
  @OnError
  public void onError(Session session, Throwable error) {
    System.out.println("WebSocket传输错误");
  }

//在onMessage()方法中,我们处理接收到的消息,如果是心跳包则更新最后活跃时间。
  @OnMessage
  public void onMessage(Session session, String message) {
    WebSocketSession websocketSession = sessions.get(session.getId());
    if (message.equals("heartbeat")) {
      System.out.println("收到心跳包");
      websocketSession.updateLastActiveTime();
    } else {
      System.out.println("收到消息:" + message);
    }
  }

//在startHeartbeat()方法中,我们使用一个新的线程来定时发送心跳包,如果WebSocket连接已经关闭,则退出循环。
  private void startHeartbeat(WebSocketSession websocketSession) {
    Runnable task = new Runnable() {
      @Override
      public void run() {
        while (true) {
          try {
            Thread.sleep(HEARTBEAT_INTERVAL);
            if (websocketSession.isOpen()) {
              websocketSession.send("heartbeat");
            } else {
              break;
            }
          } catch (Exception e) {
            e.printStackTrace();
            break;
          }
        }
      }
    };
    new Thread(task).start();
  }

//在WebSocketSession内部类中,封WebSocket会话的基本操作,并实现了更新最后活跃时间和判断是否超时的方法。
private static class WebSocketSession {

    private Session session;
    private Long lastActiveTime;

    public WebSocketSession(Session session) {
      this.session = session;
      this.lastActiveTime = System.currentTimeMillis();
    }

    public void send(String message) {
      try {
        session.getBasicRemote().sendText(message);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

    public void close() {
      try {
        session.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

    public boolean isOpen() {
      return session.isOpen();
    }

    public void updateLastActiveTime() {
      this.lastActiveTime = System.currentTimeMillis();
    }

    public boolean isIdleTimeout() {
      return (System.currentTimeMillis() - lastActiveTime) > MAX_IDLE_TIME;
    }
}


//遍历sessions集合,判断每个WebSocketSession对象是否已经超时,如果超时则关闭WebSocket连接并从sessions集合中移除。可以通过定时任务或者Servlet过滤器定期调用WebSocketEndpoint.checkIdleTimeout()方法,实现WebSocket的断线重连。
    public static void checkIdleTimeout() { 
        for (WebSocketSession websocketSession : sessions.values()) { 
            if (websocketSession.isIdleTimeout()) { 
                websocketSession.close(); 
                sessions.remove(websocketSession.session.getId()); 
            } 
        } 
    }
}

你可能感兴趣的:(后端,前端,websocket,网络协议,网络)