精华内容
下载资源
问答
  • 参考教程:http://jmesnil.net/stomp-websocket/doc/ 在使用 stomp.js 时,能...WebSocket是一个消息架构,不强制使用任何特定的消息协议,它依赖于应用解释消息的含义; 与处在应用的HTTP不同,WebSocket处在TCP

    参考教程:http://jmesnil.net/stomp-websocket/doc/

    在使用 stomp.js 时,能找到的较完整的 API 说明基本都是英文,中文资料比较少,因此,参考上边的教程做了以下的笔记和总结

    介绍

    STOMP(Simple Text-Orientated Messaging Protocol) 面向消息的简单文本协议

    WebSocket是一个消息架构,不强制使用任何特定的消息协议,它依赖于应用层解释消息的含义;

    与处在应用层的HTTP不同,WebSocket处在TCP上非常薄的一层,会将字节流转换为文本/二进制消息,因此,对于实际应用来说,WebSocket的通信形式层级过低,因此,可以在 WebSocket 之上使用 STOMP协议,来为浏览器 和 server间的 通信增加适当的消息语义。

    如何理解 STOMP 与 WebSocket 的关系:
    1) HTTP协议解决了 web 浏览器发起请求以及 web 服务器响应请求的细节,假设 HTTP 协议 并不存在,只能使用 TCP 套接字来 编写 web 应用,你可能认为这是一件疯狂的事情;
    2) 直接使用 WebSocket(SockJS) 就很类似于 使用 TCP 套接字来编写 web 应用,因为没有高层协议,就需要我们定义应用间所发送消息的语义,还需要确保连接的两端都能遵循这些语义;
    3) 同 HTTP 在 TCP 套接字上添加请求-响应模型层一样,STOMP 在 WebSocket 之上提供了一个基于帧的线路格式层,用来定义消息语义;

    STOMP帧

    STOMP帧由命令,一个或多个头信息、一个空行及负载(文本或字节)所组成;

    其中可用的COMMAND 包括:

    CONNECT、SEND、SUBSCRIBE、UNSUBSCRIBE、BEGIN、COMMIT、ABORT、ACK、NACK、DISCONNECT;

    例:
    发送消息

    SEND
    destination:/queue/trade
    content-type:application/json
    content-length:44
    {“action”:”BUY”,”ticker”:”MMM”,”shares”,44}^@

    订阅消息

    SUBSCRIBE
    id:sub-1
    destination:/topic/price.stock.*
    ^@

    服务器进行广播消息

    MESSAGE
    message-id:nxahklf6-1
    subscription:sub-1
    destination:/topic/price.stock.MMM
    {“ticker”:”MMM”,”price”:129.45}^@

    客户端 API

    引入stomp.js

    <script type="application/javascript" src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
    • 1

    发起连接

    客户端可以通过使用Stomp.js和sockjs-client连接

     
    1. // 建立连接对象(还未发起连接)

    2. var socket=new SockJS("/spring-websocket-portfolio/portfolio");

    3.  
    4. // 获取 STOMP 子协议的客户端对象

    5. var stompClient = Stomp.over(socket);

    6.  
    7. // 向服务器发起websocket连接并发送CONNECT帧

    8. stompClient.connect(

    9. {},

    10. function connectCallback (frame) {

    11. // 连接成功时(服务器响应 CONNECTED 帧)的回调方法

    12. document.getElementById("state-info").innerHTML = "连接成功";

    13. console.log('已连接【' + frame + '】');

    14. stompClient.subscribe('/topic/getResponse', function (response) {

    15. showResponse(response.body);

    16. });

    17. },

    18. function errorCallBack (error) {

    19. // 连接失败时(服务器响应 ERROR 帧)的回调方法

    20. document.getElementById("state-info").innerHTML = "连接失败";

    21. console.log('连接失败【' + error + '】');

    22. }

    23. );

    说明:
    1) socket连接对象也可通过WebSocket(不通过SockJS)连接

    var socket=new WebSocket("/spring-websocket-portfolio/portfolio");

    2) stompClient.connect()方法签名:

    client.connect(headers, connectCallback, errorCallback);

    其中
    headers表示客户端的认证信息,如:

     
    1. var headers = {

    2. login: 'mylogin',

    3. passcode: 'mypasscode',

    4. // additional header

    5. 'client-id': 'my-client-id'

    6. };

    若无需认证,直接使用空对象 “{}” 即可;

    connectCallback 表示连接成功时(服务器响应 CONNECTED 帧)的回调方法;
    errorCallback 表示连接失败时(服务器响应 ERROR 帧)的回调方法,非必须;

    断开连接

    若要从客户端主动断开连接,可调用 disconnect() 方法

     
    1. client.disconnect(function () {

    2. alert("See you next time!");

    3. };

    该方法为异步进行,因此包含了回调参数,操作完成时自动回调;

    心跳机制

    若使用STOMP 1.1 版本,默认开启了心跳检测机制,可通过client对象的heartbeat field进行配置(默认值都是10000 ms):

     
    1. client.heartbeat.outgoing = 20000; // client will send heartbeats every 20000ms

    2. client.heartbeat.incoming = 0; // client does not want to receive heartbeats from the server

    3. // The heart-beating is using window.setInterval() to regularly send heart-beats and/or check server heart-beats

    发送信息

    连接成功后,客户端可使用 send() 方法向服务器发送信息:

    client.send(destination url[, headers[, body]]);

    其中
    destination url 为服务器 controller中 @MessageMapping 中匹配的URL,字符串,必须参数;
    headers 为发送信息的header,JavaScript 对象,可选参数;
    body 为发送信息的 body,字符串,可选参数;

    例:

     
    1. client.send("/queue/test", {priority: 9}, "Hello, STOMP");

    2. client.send("/queue/test", {}, "Hello, STOMP");

    订阅、接收信息

    STOMP 客户端要想接收来自服务器推送的消息,必须先订阅相应的URL,即发送一个 SUBSCRIBE 帧,然后才能不断接收来自服务器的推送消息;
    订阅和接收消息通过 subscribe() 方法实现:

    subscribe(destination url, callback[, headers])

    其中
    destination url 为服务器 @SendTo 匹配的 URL,字符串;
    callback 为每次收到服务器推送的消息时的回调方法,该方法包含参数 message;
    headers 为附加的headers,JavaScript 对象;什么作用?
    该方法返回一个包含了id属性的 JavaScript 对象,可作为 unsubscribe() 方法的参数;

    例:

     
    1. var headers = {ack: 'client', 'selector': "location = 'Europe'"};

    2. var callback = function(message) {

    3. if (message.body) {

    4. alert("got message with body " + message.body)

    5. } else {

    6. alert("got empty message");

    7. }

    8. });

    9. var subscription = client.subscribe("/queue/test", callback, headers);

    取消订阅

     
    1. var subscription = client.subscribe(...);

    2.  
    3. subscription.unsubscribe();

    JSON 支持

    STOMP 帧的 body 必须是 string 类型,若希望接收/发送 json 对象,可通过 JSON.stringify() and JSON.parse() 实现;
    例:

     
    1. var quote = {symbol: 'APPL', value: 195.46};

    2. client.send("/topic/stocks", {}, JSON.stringify(quote));

    3.  
    4. client.subcribe("/topic/stocks", function(message) {

    5. var quote = JSON.parse(message.body);

    6. alert(quote.symbol + " is at " + quote.value);

    7. });

    事务支持

    STOMP 客户端支持在发送消息时用事务进行处理:
    举例说明:

     
    1. // start the transaction

    2. // 该方法返回一个包含了事务 id、commit()、abort() 的JavaScript 对象

    3. var tx = client.begin();

    4. // send the message in a transaction

    5. // 最关键的在于要在 headers 对象中加入事务 id,若没有添加,则会直接发送消息,不会以事务进行处理

    6. client.send("/queue/test", {transaction: tx.id}, "message in a transaction");

    7. // commit the transaction to effectively send the message

    8. tx.commit();

    9. // tx.abort();

    Debug 信息

    STOMP 客户端默认将传输过程中的所有 debug 信息以 console.log() 形式输出到客户端浏览器中,也可通过以下方式输出到 DOM 中:

     
    1. client.debug = function(str) {

    2. // str 参数即为 debug 信息

    3. // append the debug log to a #debug div somewhere in the page using JQuery:

    4. $("#debug").append(str + "\n");

    5. };

    认证

    这一部分内容看的不是很理解,因此直接将原文放在这里了,待补充。
    By default, STOMP messages will be automatically acknowledged by the server before the message is delivered to the client.

    The client can chose instead to handle message acknowledgement by subscribing to a destination and specify a ack header set to client or client-individual.

    In that case, the client must use the message.ack() method to inform the server that it has acknowledge the message.

     
    1. var subscription = client.subscribe("/queue/test",

    2. function(message) {

    3. // do something with the message

    4. ...

    5. // and acknowledge it

    6. message.ack();

    7. },

    8. {ack: 'client'}

    9. );

    The ack() method accepts a headers argument for additional headers to acknowledge the message. For example, it is possible to acknowledge a message as part of a transaction and ask for a receipt when the ACK STOMP frame has effectively be processed by the broker:
    var tx = client.begin();
    message.ack({ transaction: tx.id, receipt: ‘my-receipt’ });
    tx.commit();
    The nack() method can also be used to inform STOMP 1.1 brokers that the client did not consume the message. It takes the same arguments than the ack() method.

     

     
    1. 1、JavaScript 依赖

    2.  
    3. STOMP 依赖 sockjs.js 和 stomp.min.js。stomp.min.js的下载链接:http://www.bootcdn.cn/stomp.js/

    4.  
    5. <script type="text/javascript" src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>

    6. <script type="text/javascript" src="/js/stomp.min.js"></script>

    7.  
    8. 2、JavaScript 客户端实现

    9.  
    10. [点击并拖拽以移动]

    11.  
    12. /*STOMP*/

    13. var url = 'http://localhost:8080/stomp';

    14. var sock = new SockJS(url);

    15. var stomp = Stomp.over(sock);

    16.  
    17. var strJson = JSON.stringify({'message': 'Marco!'});

    18.  
    19. //默认的和STOMP端点连接

    20. /*stomp.connect("guest", "guest", function (franme) {

    21.  
    22. });*/

    23.  
    24. var headers={

    25. username:'admin',

    26. password:'admin'

    27. };

    28.  
    29. stomp.connect(headers, function (frame) {

    30.  
    31. //发送消息

    32. //第二个参数是一个头信息的Map,它会包含在STOMP的帧中

    33. //事务支持

    34. var tx = stomp.begin();

    35. stomp.send("/app/marco", {transaction: tx.id}, strJson);

    36. tx.commit();

    37.  
    38.  
    39. //订阅服务端消息 subscribe(destination url, callback[, headers])

    40. stomp.subscribe("/topic/marco", function (message) {

    41. var content = message.body;

    42. var obj = JSON.parse(content);

    43. console.log("订阅的服务端消息:" + obj.message);

    44. }, {});

    45.  
    46.  
    47. stomp.subscribe("/app/getShout", function (message) {

    48. var content = message.body;

    49. var obj = JSON.parse(content);

    50. console.log("订阅的服务端直接返回的消息:" + obj.message);

    51. }, {});

    52.  
    53.  
    54. /*以下是针对特定用户的订阅*/

    55. var adminJSON = JSON.stringify({'message': 'ADMIN'});

    56. /*第一种*/

    57. stomp.send("/app/singleShout", {}, adminJSON);

    58. stomp.subscribe("/user/queue/shouts",function (message) {

    59. var content = message.body;

    60. var obj = JSON.parse(content);

    61. console.log("admin用户特定的消息1:" + obj.message);

    62. });

    63. /*第二种*/

    64. stomp.send("/app/shout", {}, adminJSON);

    65. stomp.subscribe("/user/queue/notifications",function (message) {

    66. var content = message.body;

    67. var obj = JSON.parse(content);

    68. console.log("admin用户特定的消息2:" + obj.message);

    69. });

    70.  
    71. /*订阅异常消息*/

    72. stomp.subscribe("/user/queue/errors", function (message) {

    73. console.log(message.body);

    74. });

    75.  
    76. //若使用STOMP 1.1 版本,默认开启了心跳检测机制(默认值都是10000ms)

    77. stomp.heartbeat.outgoing = 20000;

    78.  
    79. stomp.heartbeat.incoming = 0; //客户端不从服务端接收心跳包

    80. });

    STOMP 服务端整理

    一、STOMP 简介

        直接使用WebSocket(或SockJS)就很类似于使用TCP套接字来编写Web应用。因为没有高层级的线路协议(wire protocol),因此就需要我们定义应用之间所发送消息的语义,还需要确保连接的两端都能遵循这些语义。

        就像HTTP在TCP套接字之上添加了请求-响应模型层一样,STOMP在WebSocket之上提供了一个基于帧的线路格式(frame-based wire format)层,用来定义消息的语义。

        与HTTP请求和响应类似,STOMP帧由命令、一个或多个头信息以及负载所组成。例如,如下就是发送数据的一个STOMP帧:

    >>> SEND
    transaction:tx-0
    destination:/app/marco
    content-length:20
    
    {"message":"Marco!"}

     

        在这个例子中,STOMP命令是send,表明会发送一些内容。紧接着是三个头信息:一个表示消息的的事务机制,一个用来表示消息要发送到哪里的目的地,另外一个则包含了负载的大小。然后,紧接着是一个空行,STOMP帧的最后是负载内容。

     

    1、启用STOMP功能

        STOMP 的消息根据前缀的不同分为三种。如下,以 /app 开头的消息都会被路由到带有@MessageMapping 或 @SubscribeMapping 注解的方法中;以/topic 或 /queue 开头的消息都会发送到STOMP代理中,根据你所选择的STOMP代理不同,目的地的可选前缀也会有所限制;以/user开头的消息会将消息重路由到某个用户独有的目的地上。

     View Code

    2、处理来自客户端的STOMP消息

        服务端处理客户端发来的STOMP消息,主要用的是 @MessageMapping 注解。如下:

     

      @MessageMapping("/marco")
      @SendTo("/topic/marco")
      public Shout stompHandle(Shout shout){
          LOGGER.info("接收到消息:" + shout.getMessage());
          Shout s = new Shout();
          s.setMessage("Polo!");
          return s;
      }

     

        2.1、@MessageMapping 指定目的地是“/app/marco”(“/app”前缀是隐含的,因为我们将其配置为应用的目的地前缀)。

        2.2、方法接收一个Shout参数,因为Spring的某一个消息转换器会将STOMP消息的负载转换为Shout对象。Spring 4.0提供了几个消息转换器,作为其消息API的一部分:

        2.3、尤其注意,这个处理器方法有一个返回值,这个返回值并不是返回给客户端的,而是转发给消息代理的,如果客户端想要这个返回值的话,只能从消息代理订阅。@SendTo 注解重写了消息代理的目的地,如果不指定@SendTo,帧所发往的目的地会与触发处理器方法的目的地相同,只不过会添加上“/topic”前缀。

        2.4、如果客户端就是想要服务端直接返回消息呢?听起来不就是HTTP做的事情!即使这样,STOMP 仍然为这种一次性的响应提供了支持,用的是@SubscribeMapping注解,与HTTP不同的是,这种请求-响应模式是异步的...

     

       @SubscribeMapping("/getShout")
       public Shout getShout(){
           Shout shout = new Shout();
           shout.setMessage("Hello STOMP");
           return shout;
       }

     

    3、发送消息到客户端

    3.1 在处理消息之后发送消息

        正如前面看到的那样,使用 @MessageMapping 或者 @SubscribeMapping 注解可以处理客户端发送过来的消息,并选择方法是否有返回值。

        如果 @MessageMapping 注解的控制器方法有返回值的话,返回值会被发送到消息代理,只不过会添加上"/topic"前缀。可以使用@SendTo 重写消息目的地;

        如果 @SubscribeMapping 注解的控制器方法有返回值的话,返回值会直接发送到客户端,不经过代理。如果加上@SendTo 注解的话,则要经过消息代理。

    3.2 在应用的任意地方发送消息

        spring-websocket 定义了一个 SimpMessageSendingOperations 接口(或者使用SimpMessagingTemplate ),可以实现自由的向任意目的地发送消息,并且订阅此目的地的所有用户都能收到消息。

     

      @Autowired
      private SimpMessageSendingOperations simpMessageSendingOperations;
    
    
      /**
      * 广播消息,不指定用户,所有订阅此的用户都能收到消息
      * @param shout
      */
      @MessageMapping("/broadcastShout")
      public void broadcast(Shout shout) {
          simpMessageSendingOperations.convertAndSend("/topic/shouts", shout);
      }

     

    3.3 为指定用户发送消息

        3.2介绍了如何广播消息,订阅目的地的所有用户都能收到消息。如果消息只想发送给特定的用户呢?spring-websocket 介绍了两种方式来实现这种功能,一种是 基于@SendToUser注解和Principal参数,一种是SimpMessageSendingOperations 接口的convertAndSendToUser方法。

    • 基于@SendToUser注解和Principal参数

        @SendToUser 表示要将消息发送给指定的用户,会自动在消息目的地前补上"/user"前缀。如下,最后消息会被发布在  /user/queue/notifications-username。但是问题来了,这个username是怎么来的呢?就是通过 principal 参数来获得的。那么,principal 参数又是怎么来的呢?需要在spring-websocket 的配置类中重写 configureClientInboundChannel 方法,添加上用户的认证。

     spring-websocket 用户认证

     

      @MessageMapping("/shout")
      @SendToUser("/queue/notifications")
      public Shout userStomp(Principal principal, Shout shout) {
            String name = principal.getName();
            String message = shout.getMessage();
            LOGGER.info("认证的名字是:{},收到的消息是:{}", name, message);
            return shout;
      }

     

    • convertAndSendToUser方法

        除了convertAndSend()以外,SimpMessageSendingOperations 还提供了convertAndSendToUser()方法。按照名字就可以判断出来,convertAndSendToUser()方法能够让我们给特定用户发送消息。

     

        @MessageMapping("/singleShout")
        public void singleUser(Shout shout, StompHeaderAccessor stompHeaderAccessor) {
            String message = shout.getMessage();
            LOGGER.info("接收到消息:" + message);
            Principal user = stompHeaderAccessor.getUser();
            simpMessageSendingOperations.convertAndSendToUser(user.getName(), "/queue/shouts", shout);
        }

     

        如上,这里虽然我还是用了认证的信息得到用户名。但是,其实大可不必这样,因为 convertAndSendToUser 方法可以指定要发送给哪个用户。也就是说,完全可以把用户名的当作一个参数传递给控制器方法,从而绕过身份认证!convertAndSendToUser 方法最终会把消息发送到 /user/sername/queue/shouts 目的地上。

    4、处理消息异常

        在处理消息的时候,有可能会出错并抛出异常。因为STOMP消息异步的特点,发送者可能永远也不会知道出现了错误。@MessageExceptionHandler标注的方法能够处理消息方法中所抛出的异常。我们可以把错误发送给用户特定的目的地上,然后用户从该目的地上订阅消息,从而用户就能知道自己出现了什么错误啦...

     

         @MessageExceptionHandler(Exception.class)
         @SendToUser("/queue/errors")
         public Exception handleExceptions(Exception t){
             t.printStackTrace();
             return t;
         }

     

    原文转发自:https://blog.csdn.net/m0_37542889/article/details/83750665(如有侵权、烦请告知处理)

    展开全文
  • websocket

    2017-09-06 10:58:49
    Socket是传输控制协议,WebSocket是应用协议。 在讲Websocket之前,我就顺带着讲下 long poll 和 ajax轮询 的原理。

    Socket是传输控制层协议,WebSocket是应用层协议
    在讲Websocket之前,我就顺带着讲下 long poll 和 ajax轮询 的原理。

    long poll 其实原理跟 ajax轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起连接后,如果没消息,就一直不返回Response给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。

    从上面可以看出其实这两种方式,都是在不断地建立HTTP连接,然后等待服务端处理,可以体现HTTP协议的另外一个特点,被动性。
    何为被动性呢,其实就是,服务端不能主动联系客户端,只能有客户端发起。

    Websocket的作用在讲Websocket之前,我就顺带着讲下 long poll 和 ajax轮询 的原理。首先是 ajax轮询 ,ajax轮询 的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。
    场景再现:
    客户端:啦啦啦,有没有新信息(Request)
    服务端:没有(Response)
    客户端:啦啦啦,有没有新信息(Request)
    服务端:没有。。(Response)
    客户端:啦啦啦,有没有新信息(Request)
    服务端:你好烦啊,没有啊。。(Response)
    客户端:啦啦啦,有没有新消息(Request)
    服务端:好啦好啦,有啦给你。(Response)
    客户端:啦啦啦,有没有新消息(Request)
    服务端:。。。。。没。。。。没。。。没有(Response) ---- looplong poll long poll 其实原理跟 ajax轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起连接后,如果没消息,就一直不返回Response给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。

    场景再现客户端:啦啦啦,有没有新信息,没有的话就等有了才返回给我吧(Request)
    服务端:额。。 等待到有消息的时候。。来 给你(Response)
    客户端:啦啦啦,有没有新信息,没有的话就等有了才返回给我吧(Request)
    -loop从上面可以看出其实这两种方式,都是在不断地建立HTTP连接,然后等待服务端处理,可以体现HTTP协议的另外一个特点,被动性。何为被动性呢,其实就是,服务端不能主动联系客户端,只能有客户端发起。简单地说就是,服务器是一个很懒的冰箱(这是个梗)(不会、不能主动发起连接),但是上司有命令,如果有客户来,不管多么累都要好好接待。说完这个,我们再来说一说上面的缺陷(原谅我废话这么多吧OAQ)从上面很容易看出来,不管怎么样,上面这两种都是非常消耗资源的。ajax轮询 需要服务器有很快的处理速度和资源。(速度)long poll 需要有很高的并发,也就是说同时接待客户的能力。(场地大小)所以ajax轮询 和long poll 都有可能发生这种情况。
    客户端:啦啦啦啦,有新信息么?
    服务端:月线正忙,请稍后再试(503 Server Unavailable)
    客户端:。。。。好吧,啦啦啦,有新信息么?
    服务端:月线正忙,请稍后再试(503 Server Unavailable)

    我们来说Websocket吧通过上面这个例子,我们可以看出,这两种方式都不是最好的方式,需要很多资源。一种需要更快的速度,一种需要更多的’电话’。这两种都会导致’电话’的需求越来越高。哦对了,忘记说了HTTP还是一个无状态协议。(感谢评论区的各位指出OAQ)通俗的说就是,服务器因为每天要接待太多客户了,是个健忘鬼,你一挂电话,他就把你的东西全忘光了,把你的东西全丢掉了。你第二次还得再告诉服务器一遍。所以在这种情况下出现了,Websocket出现了。他解决了HTTP的这几个难题。首先,被动性,当服务器完成协议升级后(HTTP->Websocket),服务端就可以主动推送信息给客户端啦。所以上面的情景可以做如下修改。

    客户端:啦啦啦,我要建立Websocket协议,需要的服务:chat,Websocket协议版本:17(HTTP Request)
    服务端:ok,确认,已升级为Websocket协议(HTTP Protocols Switched)
    客户端:麻烦你有信息的时候推送给我噢。。
    服务端:ok,有的时候会告诉你的。
    服务端:balabalabalabala
    服务端:balabalabalabala
    服务端:哈哈哈哈哈啊哈哈哈哈
    服务端:笑死我了哈哈哈哈哈哈哈就变成了这样,只需要经过一次HTTP请求,就可以做到源源不断的信息传送了。(在程序设计中,这种设计叫做回调,即:你有信息了再来通知我,而不是我傻乎乎的每次跑来问你)这样的协议解决了上面同步有延迟,而且还非常消耗资源的这种情况。那么为什么他会解决服务器上消耗资源的问题呢?其实我们所用的程序是要经过两层代理的,即HTTP协议在Nginx等服务器的解析下,然后再传送给相应的Handler(PHP等)来处理。简单地说,我们有一个非常快速的接线员(Nginx),他负责把问题转交给相应的客服(Handler)。本身接线员基本上速度是足够的,但是每次都卡在客服(Handler)了,老有客服处理速度太慢。,导致客服不够。Websocket就解决了这样一个难题,建立后,可以直接跟接线员建立持久连接,有信息的时候客服想办法通知接线员,然后接线员在统一转交给客户。这样就可以解决客服处理速度过慢的问题了。同时,在传统的方式上,要不断的建立,关闭HTTP协议,由于HTTP是非状态性的,每次都要重新传输identity info(鉴别信息),来告诉服务端你是谁。虽然接线员很快速,但是每次都要听这么一堆,效率也会有所下降的,同时还得不断把这些信息转交给客服,不但浪费客服的处理时间,而且还会在网路传输中消耗过多的流量/时间。但是Websocket只需要一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的非状态性,服务端会一直知道你的信息,直到你关闭请求,这样就解决了接线员要反复解析HTTP协议,还要查看identity info的信息。同时由客户主动询问,转换为服务器(推送)有信息的时候就发送(当然客户端还是等主动发送信息过来的。。),没有信息的时候就交给接线员(Nginx),不需要占用本身速度就慢的客服(Handler)了

    展开全文
  • WebSocket

    2018-12-26 10:48:48
    WebSocket 是什么? WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准。 WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。 为什么需要 WebSocket ? 了解计算机网络协议...

    概述

    WebSocket 是什么?

    WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准。

    WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

    为什么需要 WebSocket ?

    了解计算机网络协议的人,应该都知道:HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。

    这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。

    这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。

     

    因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。

    WebSocket 如何工作?

    Web浏览器和服务器都必须实现 WebSockets 协议来建立和维护连接。由于 WebSockets 连接长期存在,与典型的HTTP连接不同,对服务器有重要的影响。

    基于多线程或多进程的服务器无法适用于 WebSockets,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接。任何实际的 WebSockets 服务器端实现都需要一个异步服务器。

    WebSocket 客户端

    在客户端,没有必要为 WebSockets 使用 JavaScript 库。实现 WebSockets 的 Web 浏览器将通过 WebSockets 对象公开所有必需的客户端功能(主要指支持 Html5 的浏览器)。

    客户端 API

    以下 API 用于创建 WebSocket 对象。

    var Socket = new WebSocket(url, [protocol] );

    以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。

    WebSocket 属性

    以下是 WebSocket 对象的属性。假定我们使用了以上代码创建了 Socket 对象:

    属性描述
    Socket.readyState只读属性 readyState 表示连接状态,可以是以下值:0 - 表示连接尚未建立。1 - 表示连接已建立,可以进行通信。2 - 表示连接正在进行关闭。3 - 表示连接已经关闭或者连接不能打开。
    Socket.bufferedAmount只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。

    WebSocket 事件

    以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:

    事件事件处理程序描述
    openSocket.onopen连接建立时触发
    messageSocket.onmessage客户端接收服务端数据时触发
    errorSocket.onerror通信发生错误时触发
    closeSocket.onclose连接关闭时触发

    WebSocket 方法

    以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:

    方法描述
    Socket.send()使用连接发送数据
    Socket.close()关闭连接

    示例

    // 初始化一个 WebSocket 对象
    var ws = new WebSocket("ws://localhost:9998/echo");
    
    // 建立 web socket 连接成功触发事件
    ws.onopen = function () {
      // 使用 send() 方法发送数据
      ws.send("发送数据");
      alert("数据发送中...");
    };
    
    // 接收服务端数据时触发事件
    ws.onmessage = function (evt) {
      var received_msg = evt.data;
      alert("数据已接收...");
    };
    
    // 断开 web socket 连接成功触发事件
    ws.onclose = function () {
      alert("连接已关闭...");
    };

    WebSocket 服务端

    WebSocket 在服务端的实现非常丰富。Node.js、Java、C++、Python 等多种语言都有自己的解决方案。

    以下,介绍我在学习 WebSocket 过程中接触过的 WebSocket 服务端解决方案。

    Node.js

    常用的 Node 实现有以下三种。

    Java

    Java 的 web 一般都依托于 servlet 容器。

    我使用过的 servlet 容器有:Tomcat、Jetty、Resin。其中Tomcat7、Jetty7及以上版本均开始支持 WebSocket(推荐较新的版本,因为随着版本的更迭,对 WebSocket 的支持可能有变更)。

    此外,Spring 框架对 WebSocket 也提供了支持。

    虽然,以上应用对于 WebSocket 都有各自的实现。但是,它们都遵循RFC6455 的通信标准,并且 Java API 统一遵循 JSR 356 - JavaTM API for WebSocket 规范。所以,在实际编码中,API 差异不大。

    Spring

    Spring 对于 WebSocket 的支持基于下面的 jar 包:

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-websocket</artifactId>
      <version>${spring.version}</version>
    </dependency>

    在 Spring 实现 WebSocket 服务器大概分为以下几步:

    创建 WebSocket 处理器

    扩展 TextWebSocketHandler 或 BinaryWebSocketHandler ,你可以覆写指定的方法。Spring 在收到 WebSocket 事件时,会自动调用事件对应的方法。

    import org.springframework.web.socket.WebSocketHandler;
    import org.springframework.web.socket.WebSocketSession;
    import org.springframework.web.socket.TextMessage;
    
    public class MyHandler extends TextWebSocketHandler {
    
        @Override
        public void handleTextMessage(WebSocketSession session, TextMessage message) {
            // ...
        }
    
    }

    WebSocketHandler 源码如下,这意味着你的处理器大概可以处理哪些 WebSocket 事件:

    public interface WebSocketHandler {
    
       /**
        * 建立连接后触发的回调
        */
       void afterConnectionEstablished(WebSocketSession session) throws Exception;
    
       /**
        * 收到消息时触发的回调
        */
       void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;
    
       /**
        * 传输消息出错时触发的回调
        */
       void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;
    
       /**
        * 断开连接后触发的回调
        */
       void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;
    
       /**
        * 是否处理分片消息
        */
       boolean supportsPartialMessages();
    
    }

    配置 WebSocket

    配置有两种方式:注解和 xml 。其作用就是将 WebSocket 处理器添加到注册中心。

    1. 实现 WebSocketConfigurer
    import org.springframework.web.socket.config.annotation.EnableWebSocket;
    import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
    import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
    
    @Configuration
    @EnableWebSocket
    public class WebSocketConfig implements WebSocketConfigurer {
    
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            registry.addHandler(myHandler(), "/myHandler");
        }
    
        @Bean
        public WebSocketHandler myHandler() {
            return new MyHandler();
        }
    
    }
    1. xml 方式
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:websocket="http://www.springframework.org/schema/websocket"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/websocket
            http://www.springframework.org/schema/websocket/spring-websocket.xsd">
    
        <websocket:handlers>
            <websocket:mapping path="/myHandler" handler="myHandler"/>
        </websocket:handlers>
    
        <bean id="myHandler" class="org.springframework.samples.MyHandler"/>
    
    </beans>

    更多配置细节可以参考:Spring WebSocket 文档

    javax.websocket

    如果不想使用 Spring 框架的 WebSocket API,你也可以选择基本的 javax.websocket。

    首先,需要引入 API jar 包。

    <!-- To write basic javax.websocket against -->
    <dependency>
      <groupId>javax.websocket</groupId>
      <artifactId>javax.websocket-api</artifactId>
      <version>1.0</version>
    </dependency>

    如果使用嵌入式 jetty,你还需要引入它的实现包:

    <!-- To run javax.websocket in embedded server -->
    <dependency>
      <groupId>org.eclipse.jetty.websocket</groupId>
      <artifactId>javax-websocket-server-impl</artifactId>
      <version>${jetty-version}</version>
    </dependency>
    <!-- To run javax.websocket client -->
    <dependency>
      <groupId>org.eclipse.jetty.websocket</groupId>
      <artifactId>javax-websocket-client-impl</artifactId>
      <version>${jetty-version}</version>
    </dependency>

    @ServerEndpoint

    这个注解用来标记一个类是 WebSocket 的处理器。

    然后,你可以在这个类中使用下面的注解来表明所修饰的方法是触发事件的回调

    // 收到消息触发事件
    @OnMessage
    public void onMessage(String message, Session session) throws IOException, InterruptedException {
        ...
    }
    
    // 打开连接触发事件
    @OnOpen
    public void onOpen(Session session, EndpointConfig config, @PathParam("id") String id) {
        ...
    }
    
    // 关闭连接触发事件
    @OnClose
    public void onClose(Session session, CloseReason closeReason) {
        ...
    }
    
    // 传输消息错误触发事件
    @OnError
    public void onError(Throwable error) {
        ...
    }

    ServerEndpointConfig.Configurator

    编写完处理器,你需要扩展 ServerEndpointConfig.Configurator 类完成配置:

    public class WebSocketServerConfigurator extends ServerEndpointConfig.Configurator {
        @Override
        public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
            HttpSession httpSession = (HttpSession) request.getHttpSession();
            sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
        }
    }

    然后就没有然后了,就是这么简单。

    WebSocket 代理

    如果把 WebSocket 的通信看成是电话连接,Nginx 的角色则像是电话接线员,负责将发起电话连接的电话转接到指定的客服。

    Nginx 从 1.3 版开始正式支持 WebSocket 代理。如果你的 web 应用使用了代理服务器 Nginx,那么你还需要为 Nginx 做一些配置,使得它开启 WebSocket 代理功能。

    以下为参考配置:

    server {
      # this section is specific to the WebSockets proxying
      location /socket.io {
        proxy_pass http://app_server_wsgiapp/socket.io;
        proxy_redirect off;
    
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 600;
      }
    }

    更多配置细节可以参考:Nginx 官方的 websocket 文档

    FAQ

    HTTP 和 WebSocket 有什么关系?

    Websocket 其实是一个新协议,跟 HTTP 协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是 HTTP 协议上的一种补充。

    Html 和 HTTP 有什么关系?

    Html 是超文本标记语言,是一种用于创建网页的标准标记语言。它是一种技术标准。Html5 是它的最新版本。

    Http 是一种网络通信协议。其本身和 Html 没有直接关系。

    展开全文
  • Java WebSocket 实践2 - 基于Spring 低WebSocket API 实现WebSocket 基于Spring 低WebSocket API 实现WebSocket 基于Spring 低级实现一个WebSoket Demo 不基于注解,而是需要用到一些Spring 提供的接口和...

    Java WebSocket 实践2 - 基于Spring 低层级WebSocket API 实现WebSocket


    Spring Weboscket Api 接口分析

    WebSocketHandler: 这是一个接口,提供了五个实现类:
    接口定义了处理websoket 消息和 websoket 的生命周期事件。

    public interface WebSocketHandler {
    
    
    	void afterConnectionEstablished(WebSocketSession session) throws Exception;
    
    	void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;
    
    	void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;
    
    	void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;
    	
    	boolean supportsPartialMessages();
    }
    
    

    WebSocketHandler的类关系图
    在这里插入图片描述
    AbstractWebSocketHandler 是一个抽象类,这个抽象类中除了实现WebSocketHandler中定义的那几个方法外,最重要的是这一段代码:

       @Override
       public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
       	if (message instanceof TextMessage) {
       		handleTextMessage(session, (TextMessage) message);
       	}
       	else if (message instanceof BinaryMessage) {
       		handleBinaryMessage(session, (BinaryMessage) message);
       	}
       	else if (message instanceof PongMessage) {
       		handlePongMessage(session, (PongMessage) message);
       	}
       	else {
       		throw new IllegalStateException("Unexpected WebSocket message type: " + message);
       	}
       }
    
       protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
       }
    
       protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
       }
    
       protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
       }
    

    AbstractWebSocketHandler 覆写了 handleMessage()方法,每个方法对应于
    某一种特定类型的消息。


    ws 服务端实现

    AbstractWebSocketHandler 下,可以看到BinaryWebSocketHandlerTextWebSocketHandler两个实现类,根据需要继承一个既可以。这里选择``TextWebSocketHandler`.
    具体实现代码如下:

    public class MyWebScoketHandler extends TextWebSocketHandler {
    
        public MyWebScoketHandler() {
            super();
        }
    
        @Override
        protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
            super.handleBinaryMessage(session, message);
        }
    
        @Override
        public void afterConnectionEstablished(WebSocketSession session) throws Exception {
            super.afterConnectionEstablished(session);
            System.out.println("客户端连接成功");
        }
    
    
    
        @Override
        protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
            super.handleTextMessage(session, message);
            System.out.println("收到消息客户端消息 : "+ message.getPayload());
            session.sendMessage(new TextMessage(message.getPayload()));
        }
    
    
    
        @Override
        public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
            super.handleTransportError(session, exception);
        }
    
        @Override
        public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
            super.afterConnectionClosed(session, status);
            System.out.println("连接已经断开");
        }
    
        @Override
        public boolean supportsPartialMessages() {
            return super.supportsPartialMessages();
        }
    }
    

    配置类

    package com.example.websocket_demo.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Component;
    import org.springframework.web.socket.config.annotation.EnableWebSocket;
    import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
    import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
    
    @Component
    // 开启WebSocket 
    @EnableWebSocket
    public class WebScoketConfig implements WebSocketConfigurer {
    
        /**
         *  register  message handler and  webSocket Uri path
         * @param registry
         */
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            registry.addHandler(myWebScoketHandler(),"/test/websocket").setAllowedOrigins("*");
        }
    
        /**
         * register MyWebSocketHandler to Spring container
         * @return  MyWebSocketHandler
         */
        @Bean
        public MyWebScoketHandler myWebScoketHandler(){
            return  new MyWebScoketHandler();
        }
    
    }
    

    这个配置中定义消息处理器和webSoket 的URI 。这个Handler 的含义和JavaX中的@ServerEndpoint 作用一致。
    @EnableWebSocket 的作用:名字已经说明了,启用WebSocket


    ws 客户端实现

    Handler 定义

    package com.example.websocket_client.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.util.concurrent.ListenableFuture;
    import org.springframework.web.socket.TextMessage;
    import org.springframework.web.socket.WebSocketHttpHeaders;
    import org.springframework.web.socket.WebSocketSession;
    import org.springframework.web.socket.client.WebSocketClient;
    import org.springframework.web.socket.client.standard.StandardWebSocketClient;
    
    import java.io.IOException;
    import java.net.URI;
    import java.util.concurrent.ExecutionException;
    
    @Component
    public class MyWebSocketClient  {
    
        @Autowired
        private MyWebSocketHandler myWebSocketHandler ;
        private WebSocketSession webSocketSession;
        private boolean isConnected  = false ;
    
        public void connected(){
            WebSocketClient webSocketClient = new StandardWebSocketClient();
            WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
            ListenableFuture<WebSocketSession> future = webSocketClient.doHandshake(myWebSocketHandler, headers, URI.create("ws://localhost:8080/test/websocket"));
            try {
                webSocketSession = future.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            isConnected = true ;
    
        }
    
        public void sendMessage() throws IOException {
            int id =  0 ;
            while (true){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
                }
                webSocketSession.sendMessage(new TextMessage("消息序列:id = " + id));
                id++ ;
            }
        }
    }
    
    
    

    这是一个自定义的处理类,用到了一个WebSocketClient 的对象, 需要用这个对象来和服务端发起连接和通信。用WebSocketSession 来发送消息。

    展开全文
  • Websocket

    2019-02-19 17:37:45
    New Document 看完让你彻底搞懂Websocket原理 https://www.toutiao.com/i6658478665484993037/ 一、websocket与http ...
  • Websocket是什么

    2019-04-04 10:19:47
    一、websocket与http WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算) 首先HTTP有 1.1 和 1.0 之说,也就是所谓的 keep-alive ,...
  • webSocket

    2018-05-26 19:31:00
    什么是webSocket WebSocket是为Html5提供了一种在单个TCP上实现全双工的网络通信协议。 为什么需要webSocket http协议是一种无状态、无连接的通信协议,使用的是请求/应答模型,客户端发出一次请求,服务器端作出...
  • WebSocket获取service对象,操作数据库,WebSocke在websocket中操作数据库,需要加载已经配置好的spring中的配置文件,获取service对象。在搭建好websocket条件下以下种方法可实现 一种,通过ContextLoader...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,301
精华内容 5,720
关键字:

websocket是第几层