• websocket 基本使用


    1 websocket基本使用

    websocket 是 javax.websocket下面的,不需要任何依赖,直接就可以使用

      @ServerEndpoint 标记声明一个websocket 服务 ,configurator 属性指定 鉴权 配置类,@ServerEndpoint 标记的类 为每个链接会创建一个该对象实例,也就是成员变量这个链接内私有。

      @OnOpen , @OnClose , @OnMessage , @OnError 4个事件方法,对应事件触发的时候调用 (除了@PathParam("path") 标记的参数以外,最多只能有 String message, Session session 两个参数)

      

    package com.lomi.websocket;
    
    import java.io.IOException;
    import java.util.concurrent.CopyOnWriteArraySet;
    
    import javax.websocket.OnClose;
    import javax.websocket.OnError;
    import javax.websocket.OnMessage;
    import javax.websocket.OnOpen;
    import javax.websocket.Session;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    
    
    /**
     * 每个连接会创建一个新的 ServerEndpoint 实例,所以成员变量 是当前ServerEndpoint私有的
     * websocket可以 带有用户参数的地方只有 自定义协议 和  PathParam,当然还有message 体
     * 
     * 
     * @author ZHANGYUKUN
     *
     *
     *
     *
     */
    @ServerEndpoint(value="/websocket/{path}",configurator = SocketServerConfigurator.class)
    @Component
    public class SimpleWebSocket {
        private static final Logger logger = LoggerFactory.getLogger(SimpleWebSocket.class);
        
        //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
        private static int onlineCount = 0;
         
        //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
        private static CopyOnWriteArraySet<SimpleWebSocket> webSocketSet = new CopyOnWriteArraySet<SimpleWebSocket>();
         
        //与某个客户端的连接会话,需要通过它来给客户端发送数据
        private Session session;
        
        private static int i = 0;
         
        /**
         * 连接建立成功调用的方法
         * @param session  可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
         */
        @OnOpen
        public void onOpen(Session session){
            this.session = session;
            webSocketSet.add(this);     //加入set中
            addOnlineCount();           //在线数加1
            logger.warn("有新连接加入!当前在线人数为" + getOnlineCount());
        }
         
        /**
         * 连接关闭调用的方法
         */
        @OnClose
        public void onClose(){
            webSocketSet.remove(this);  //从set中删除
            subOnlineCount();           //在线数减1    
            logger.warn("有一连接关闭!当前在线人数为" + getOnlineCount());
        }
         
        /**
         * 收到客户端消息后调用的方法
         * @param message 客户端发送过来的消息
         * @param session 可选的参数
         * @throws IOException 
         */
        @OnMessage
        public void onMessage(@PathParam("path") String path, String message, Session session) throws IOException {
            i++;
            logger.warn("来自客户端" + session.getId() + "的path:" + path+i);
            logger.warn("来自客户端" + session.getId() + "的消息:" + message+i);
            sendMessage(message+i);
        }
         
        /**
         * 发生错误时调用
         * @param session
         * @param error
         */
        @OnError
        public void onError(Session session, Throwable error){
            logger.warn("发生错误");
            error.printStackTrace();
        }
         
        /**
         * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
         * @param message
         * @throws IOException
         */
        public void sendMessage(String message) throws IOException{
            this.session.getBasicRemote().sendText(message);
            //this.session.getAsyncRemote().sendText(message);
        }
     
        public static synchronized int getOnlineCount() {
            return onlineCount;
        }
     
        public static synchronized void addOnlineCount() {
            SimpleWebSocket.onlineCount++;
        }
         
        public static synchronized void subOnlineCount() {
            SimpleWebSocket.onlineCount--;
        }
    }

    配置鉴权类

    package com.lomi.websocket;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.websocket.HandshakeResponse;
    import javax.websocket.server.HandshakeRequest;
    import javax.websocket.server.ServerEndpointConfig;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    /**
     * 鉴权
     * @author ZHANGYUKUN
     *
     */
    public class SocketServerConfigurator extends ServerEndpointConfig.Configurator {
    	private final Logger logger = LoggerFactory.getLogger(SocketServerConfigurator.class);
    
    	/**
    	 * token鉴权认证,我们可以在自定义协议里面带上用户标识,这样就可以识别用户了
    	 *
    	 * @param originHeaderValue
    	 * @return
    	 */
    	@Override
    	public boolean checkOrigin(String originHeaderValue) {
    		ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    		HttpServletRequest request = servletRequestAttributes.getRequest();
    		String url = request.getServletPath();
    		
    		//请求url中可以带参数
    		logger.warn( "请求url" +  url );
    		
    		//用自定义协议传达参数
    		String token = request.getHeader("Sec-WebSocket-Protocol");
    		logger.warn("Sec-WebSocket-Protocol是:{}" + token);
    		servletRequestAttributes.getResponse().setHeader("Sec-WebSocket-Protocol",   token );
    		
    		return true;
    	}
    
    	/**
    	 * Modify the WebSocket handshake response 修改websocket 返回值
    	 *
    	 * @param sec
    	 * @param request
    	 * @param response
    	 */
    	@Override
    	public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
    		super.modifyHandshake(sec, request, response);
    
    	}
    }
    

      测试客户端html代码:

        websocket地址格式:ws://localhost/te/websocket/pathParam/ ,他们的协议 是 ws ,如果使用安全套接字证书就是wss://localhost/te/websocket/pathParam/,类似 http 和 https

        客户端创建 websocket 的时候可以指定私有协议: new WebSocket("ws://localhost/te/websocket/pathParam",["token"])

        客户端创建 websocket 支持 pathParam: new WebSocket("ws://localhost/te/websocket/pathParam/?id=1")

        客户端创建 websocket 的时候可以指定请求参数: new WebSocket("ws://localhost/te/websocket/pathParam/1")

        当然这些参数都需要服务器端支持

    <!DOCTYPE HTML>
    <html>
      <head>
        <title>My WebSocket</title>
      </head>
       
      <body>
        Welcome<br/>
        <input id="text" type="text" /><button onclick="send()">Send</button>    <button onclick="closeWebSocket()">Close</button>
        <div id="message">
        </div>
      </body>
       
      <script type="text/javascript">
          var websocket = null;
           
          //判断当前浏览器是否支持WebSocket
          if('WebSocket' in window){
              //使用子协议 websocket = new WebSocket("ws://localhost/te/websocket/pathParam/",["token"]);
              websocket = new WebSocket("ws://localhost/te/websocket/pathParam/");
          }
          else{
              alert('Not support websocket')
          }
           
          //连接发生错误的回调方法
          websocket.onerror = function(){
              setMessageInnerHTML("error");
          };
           
          //连接成功建立的回调方法
          websocket.onopen = function(event){
              setMessageInnerHTML("open");
          }
           
          //接收到消息的回调方法
          websocket.onmessage = function(event){
              setMessageInnerHTML(event.data);
          }
           
          //连接关闭的回调方法
          websocket.onclose = function(){
              setMessageInnerHTML("close");
          }
           
          //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
          window.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>
    </html>
    

      



    2 websocket在使用内嵌容器的spring中使用需要指定WebsocketExporter,否者不能识别 websocket

      需要注意的是  @ServerEndpoint 标记的 类不要被AOP 切到,否者或异常

    package com.lomi.websocket;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.server.standard.ServerEndpointExporter;
    
    /**
     * 
     * @author ZHANGYUKUN
     *
     */
    @Configuration
    public class WebsocketExporter {
    	
    	/**
    	 * 必须有这个 @ServerEndpoint 才生效,ServerEndpointExporter 会把检查并注册 @ServerEndpoint (如果不是内嵌的tomcat,是外置的tomcat,不需要这个)
    	 * @return
    	 */
        @Bean
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }
    
    }
    

      

    3 Sec-WebSocket-Protocol 自定义协议参数传递

      客户端如果带了Sec-WebSocket-Protocol参数,服务器端响应必须带上同样的Sec-WebSocket-Protocol值才能正常的建立链接。否者服务器端的连接会立马断开。

    4 性能使用就meter测试,本地机器可以轻松支持2000线程,10条消息/线程/秒 的请求。(线程多余3000,请求每秒一条消息都卡,感觉是window端口的问题,按理说一个客户端请求占一个端口,window 端口有6W多个,不应该卡才对,可可能是就jmeter的限制,线程多了就卡死)

      2000线程,每秒10 条,应该远远不是 我本机 websocket 的性能瓶颈,目前测试的瓶颈应该是 客户端限制的,这时候 java程序的cpu占用,内存占用都不高。

      如果直接用nginx 做websocket  service 代理,明显会受到 客户端端口的限制(LVS 的 三种负载均衡策略给出了解决方案(DR模式)

      

    备注:和普通tcp socket 一样,服务器端只会占用一个端口,但是一个客户端会占用一个端口。

  • 相关阅读:
    标签平滑Label Smoothing
    py 中传参时**符号学习
    RoBERTa模型学习
    获取bert所有隐层的输出
    json.dumps||ensure_ascii
    nn.LSTM输入、输出、参数及pad
    py中的heapq操作
    fastnlp中的CRF补齐的padding?
    P3393 逃离僵尸岛 最短路dijkstra
    P2057 [SHOI2007]善意的投票 最小割
  • 原文地址:https://www.cnblogs.com/cxygg/p/16169361.html
Copyright © 2020-2023  润新知