精华内容
下载资源
问答
  • 关于WebSocket不太懂,上网搜了一些资料,说是 WebSocket 是 html5 规范发布的新协议,和 http协议完全是两个不同的概念,或者说基本没关系;WebSocket 协议 和 http协议的唯一... 那WebSocket通信跟http协议有什么...

    关于WebSocket不太懂,上网搜了一些资料,说是 WebSocket 是 html5 规范发布的新协议,和 http协议完全是两个不同的概念,或者说基本没关系;WebSocket 协议 和 http协议的唯一联系点在于,WebSocket 协议为了兼容现有浏览器的握手规范而采用了 http协议中的握手规范 以建立WebSocket连接;

    那WebSocket通信跟http协议有什么不一样的呢?

    这篇文章中有详细介绍:http://blog.csdn.net/pacosonswjtu/article/details/52035252

    HTML5 WebSocket 设计出来的目的就是要取代轮询和 Comet 技术,使客户端浏览器具备像 C/S 架构下桌面系统的实时通讯能力。 浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。因为 WebSocket 连接本质上就是一个 TCP 连接,所以在数据传输的稳定性和数据传输量的大小方面,和轮询以及 Comet 技术比较,具有很大的性能优势.

    WebSocket 规范

    WebSocket 协议本质上是一个基于 TCP 的协议。为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息”Upgrade: WebSocket”表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。

    请求数据的方式可以用WebSocket通信方式,那么WCF服务可以支持WebSocket通信吗

    在这个篇文章中找到了答案:https://msdn.microsoft.com/zh-cn/library/hh977020.aspx

    发现WCF4.5中新增了一个NetHttpBinding协议,支持Websocket的。但是需要在在windows 8及以上的系统中支持。

    Websocket通信协议,需要浏览器的支持,且对硬件服务器要求:server2012或window 8以上操作系统才能支持。

     

    转载于:https://www.cnblogs.com/zhouhongqian/p/8204296.html

    展开全文
  • WebSocket 通信

    2019-03-18 15:25:47
    websocket实现后台给web端推送消息 2018年07月27日 11:24:43 kebo_china 阅读数:1243 </div> 网络上的两个程序...

    websocket实现后台给web端推送消息

    网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。

        建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员

    做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,

    提供了网络通信的能力。

    连接过程:

    (1)服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。

    (2)客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

    (3)连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

            HTTP协议是非持久化的,单向的网络协议,在建立连接后只允许浏览器向服务器发出请求后,服务器才能返回相应的数据。
    当需要即时通讯时,通过轮询在特定的时间间隔(如1秒),由浏览器向服务器发送Request请求,然后将最新的数据返回给浏览器。这样的方法最明显的缺点就是需要不断的发送请求,而且通常HTTP request的Header是非常长的,为了传输一个很小的数据 需要付出巨大的代价,是很不合算的,占用了很多的宽带。会导致过多不必要的请求,浪费流量和服务器资源,每一次请求、应答,都浪费了一定流量在相同的头部信息上
           WebSocket的出现可以弥补这一缺点。在WebSocket中,只需要服务器和浏览器通过HTTP协议进行一个握手的动作,然后单独建立一条TCP的通信通道进行数据的传送。
    1、WebSocket同HTTP一样也是应用层的协议,但是它是一种双向通信协议,是建立在TCP之上的。
    2、WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息。HTTP是单向的。
    3、WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的。

    使用websocket完成后台给web端推送消息:
    pom依赖,使用的版本是4.1.4.RELEASE

    
     
    1. <!-- WebSocket -->
    2. <dependency>
    3. <groupId>org.springframework </groupId>
    4. <artifactId>spring-websocket </artifactId>
    5. <version>${webSocket.version} </version>
    6. </dependency>
    7. <dependency>
    8. <groupId>org.springframework </groupId>
    9. <artifactId>spring-messaging </artifactId>
    10. <version>${webSocket.version} </version>
    11. </dependency>

    处理消息的MyMessageHandler

    
     
    1. package com.yofc.cloud.websocket;
    2. import org.apache.log4j.Logger;
    3. import org.springframework.stereotype.Service;
    4. import org.springframework.web.socket.*;
    5. import java.io.IOException;
    6. import java.util.Map;
    7. import java.util.concurrent.ConcurrentHashMap;
    8. /**
    9. * @program: cloudDisk
    10. * @description: websocket handler
    11. * @author: Mr.liu
    12. * @create: 2018-07-26 16:50
    13. **/
    14. @Service
    15. public class MyMessageHandler implements WebSocketHandler {
    16. private static final Logger log = Logger.getLogger(MyMessageHandler.class);
    17. public static final String SESSION_KEY = "current_session";
    18. private final static Map<String, WebSocketSession> sessionMap;
    19. static {
    20. sessionMap = new ConcurrentHashMap<String, WebSocketSession>( 30);
    21. }
    22. /**
    23. * @Description: 建立websocket连接时调用该方法
    24. * @Param: [webSocketSession]
    25. * @return: void
    26. * @Author: Mr.liu
    27. * @Date: 2018/7/26
    28. */
    29. @Override
    30. public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
    31. sessionMap.put(SESSION_KEY, webSocketSession);
    32. log.info( "websocket connection success");
    33. // webSocketSession.sendMessage(new TextMessage("websocket connection success"));
    34. }
    35. /**
    36. * 客户端调用websocket.send时候,会调用该方法,进行数据通信(客户端给服务端发请求调用的方法)
    37. *
    38. * @param webSocketSession
    39. * @param webSocketMessage
    40. * @throws Exception
    41. */
    42. @Override
    43. public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
    44. String msg = webSocketMessage.toString();
    45. log.info( "服务端已经接收到消息,msg=" + msg);
    46. webSocketMessage = new TextMessage( "服务端已经接收到消息,msg=" + msg);
    47. webSocketSession.sendMessage(webSocketMessage);
    48. }
    49. /**
    50. * 传输过程出现异常时,调用该方法
    51. *
    52. * @param webSocketSession
    53. * @param throwable
    54. * @throws Exception
    55. */
    56. @Override
    57. public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
    58. log.info( "websocket链接出现异常");
    59. WebSocketMessage<String> message = new TextMessage( "异常信息:" + throwable.getMessage());
    60. webSocketSession.sendMessage(message);
    61. }
    62. /**
    63. * 关闭websocket时调用该方法
    64. *
    65. * @param webSocketSession
    66. * @param closeStatus
    67. * @throws Exception
    68. */
    69. @Override
    70. public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
    71. log.info( "websocket connection close");
    72. }
    73. @Override
    74. public boolean supportsPartialMessages() {
    75. return false;
    76. }
    77. public void sendMsgToUser(String contents) {
    78. WebSocketSession session = sessionMap.get(SESSION_KEY);
    79. if (session != null && session.isOpen()) {
    80. try {
    81. TextMessage message = new TextMessage(contents);
    82. session.sendMessage(message);
    83. } catch (IOException e) {
    84. e.printStackTrace();
    85. }
    86. }
    87. }
    88. }

    配置websocket链接地址 WebSocketConfig

    
     
    1. package com.yofc.cloud.websocket;
    2. import org.springframework.context.annotation.Bean;
    3. import org.springframework.context.annotation.Configuration;
    4. import org.springframework.web.socket.WebSocketHandler;
    5. import org.springframework.web.socket.config.annotation.EnableWebSocket;
    6. import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
    7. import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
    8. /**
    9. * @program: cloudDisk
    10. * @description: WebSocketConfig
    11. * @author: Mr.liu
    12. * @create: 2018-07-26 17:11
    13. **/
    14. @Configuration
    15. @EnableWebSocket
    16. public class WebSocketConfig implements WebSocketConfigurer {
    17. /**
    18. * register handler
    19. * @param webSocketHandlerRegistry
    20. */
    21. @Override
    22. public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
    23. webSocketHandlerRegistry.addHandler(myHandler(), "cloudDiskHandler").addInterceptors( new WebSocketInterceptor());
    24. webSocketHandlerRegistry.addHandler(myHandler(), "/socketJs/cloudDiskHandler").addInterceptors( new WebSocketInterceptor()).withSockJS();
    25. }
    26. @Bean
    27. public WebSocketHandler myHandler(){
    28. return new MyMessageHandler();
    29. }
    30. }

    websocket适配器

    
     
    1. package com.yofc.cloud.websocket;
    2. import org.springframework.http.server.ServerHttpRequest;
    3. import org.springframework.http.server.ServerHttpResponse;
    4. import org.springframework.http.server.ServletServerHttpRequest;
    5. import org.springframework.web.socket.WebSocketHandler;
    6. import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
    7. import java.util.Map;
    8. /**
    9. * @program: cloudDisk
    10. * @description: websocket adapter
    11. * @author: Mr.liu
    12. * @create: 2018-07-26 17:16
    13. **/
    14. public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {
    15. @Override
    16. public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
    17. Map<String, Object> attributes) throws Exception {
    18. /* if (request instanceof ServletServerHttpRequest) {
    19. ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request;
    20. //获取参数
    21. String userId = serverHttpRequest.getServletRequest().getParameter("userId");
    22. attributes.put(MyMessageHandler.USER_KEY, userId);
    23. }*/
    24. return true;
    25. }
    26. }

    调用方法给前段发送消息

    
     
    1. //注入
    2. @Autowired
    3. private MyMessageHandler handler;
    4. //这个方法现在任何位置都可以给web端发送消息
    5. handler.sendMsgToUser(answerMessage.toString());

    web端代码 页面创建时执行此方法

    
     
    1. websocketListener() {
    2. console.log( window.location.host);
    3. var websocket;
    4. var url = window.location.host;
    5. //var url = "localhost:8080";
    6. let self = this;
    7. // 首先判断是否 支持 WebSocket
    8. if ( "WebSocket" in window) {
    9. // websocket = new WebSocket("ws://localhost:8080/cloudDiskHandler");
    10. websocket = new WebSocket( "ws://" + url + "/cloudDiskHandler");
    11. } else if ( "MozWebSocket" in window) {
    12. websocket = new MozWebSocket( "ws://" + url + "/cloudDiskHandler");
    13. } else {
    14. websocket = new SockJS( "http://" + url + "/socketJs/cloudDiskHandler");
    15. }
    16. // 打开连接时
    17. websocket.onopen = function(evnt) {
    18. console.log( " websocket.onopen ");
    19. };
    20. // 收到消息时
    21. websocket.onmessage = function(evnt) {
    22. console.log(evnt.data);
    23. self.$message({
    24. showClose: true,
    25. message: evnt.data
    26. });
    27. };
    28. websocket.onerror = function(evnt) {
    29. console.log( " websocket.onerror ");
    30. };
    31. websocket.onclose = function(evnt) {
    32. console.log( " websocket.onclose ");
    33. };
    34. function say() {
    35. //客户端主动发消息
    36. // websocket.send("client socket success");
    37. }
    38. },

     

    展开全文
  • 在vue中使用SockJS实现webSocket通信

    千次阅读 2019-01-21 15:42:45
    基于webSocket通信的库主要有 socket.io,SockJS,这次用的是 SockJS。 前提 这里我们使用sockjs-client、stomjs这两个模块,要实现webSocket通信,需要后台配合,也使用相应的模块。 关于实时通信 实现实时...

    简单介绍

    基于webSocket通信的库主要有 socket.ioSockJS,这次用的是 SockJS。

    前提

    这里我们使用sockjs-clientstomjs这两个模块,要实现webSocket通信,需要后台配合,也使用相应的模块。

    关于实时通信

    实现实时通信,我们通常有三种方法:

    • ajax轮询 ajax轮询的原理非常简单,让浏览器每隔几秒就像服务器发送一个请求,询问服务器是否有新的信息.
    • http 长轮询 长轮询的机制和ajax轮询差不多,都是采用轮询的方式,不过才去的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起链接后,如果没有消息,就一直不返回response给客户端.知道有新的消息才返回,返回完之后,客户端再此建立连接,周而复始.
    • WebSocket WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议.在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送,不需要繁琐的询问和等待. 从上面的介绍很容易看出来,ajax轮询和长轮询都是非常耗费资源的,ajax轮询需要服务器有很快的处理速度和资源,http长轮询需要有很高的并发,也就是同时接待客户的能力.而WebSocket,只需要经过一次HTTP请求,就可以与服务端进行源源不断的消息收发了.

    sockjs-client

    sockjs-client是从SockJS中分离出来的用于客户端使用的通信模块.所以我们就直接来看看SockJS. SockJS是一个浏览器的JavaScript库,它提供了一个类似于网络的对象,SockJS提供了一个连贯的,跨浏览器的JavaScriptAPI,它在浏览器和Web服务器之间创建了一个低延迟,全双工,跨域通信通道. 你可能会问,我为什么不直接用原生的WebSocket而要使用SockJS呢?这得益于SockJS的一大特性,一些浏览器中缺少对WebSocket的支持,因此,回退选项是必要的,而Spring框架提供了基于SockJS协议的透明的回退选项。SockJS提供了浏览器兼容性,优先使用原生的WebSocket,如果某个浏览器不支持WebSocket,SockJS会自动降级为轮询.

    stomjs

    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之上提供了一个基于帧的线路格式层,用来定义消息语义.

    代码实现

    代码中除了最基本的连接,还设置了一个定时器,每隔十秒发送一条数据到服务器端,如果发生错误,catch这个错误,重新建立连接.

    这是一个公告通知案例:

    // 安装并引入相关模块
    import SockJS from  'sockjs-client';  
    import  Stomp from 'stompjs';
    export default {
        data() {
          return {
            stompClient:'',
            timer:''
          };
        },
        methods: {
          initWebSocket() {
            this.connection();
            let self = this;
            // 断开重连机制,尝试发送消息,捕获异常发生时重连
            this.timer = setInterval(() => {
              try {
                self.stompClient.send("test");
              } catch (err) {
                console.log("断线了: " + err);
                self.connection();
              }
            }, 10000);
          },   
          connect() {
          	//连接服务端提供的通信接口,连接以后才可以订阅广播消息和个人消息
    	      var sock = new SockJS('http://127.0.0.1::8080/websocket/endpoint')
    	      // 获取STOMP子协议的客户端对象
    	      this.stompClient = Stomp.over(sock)
    	      var token = sessionStorage.getItem('cookieaccess_token')
    	       // 向服务器发起websocket连接
    	      var connection = this.stompClient.connect({ Authorization: "Bearer " + token }, (message) => {
    	        this.subscribe();
    	        this.sendMessage();
    	      });
    	  },  
    	  sendMessage() {  // 发送消息
    	      this.stompClient.send("/welcome", {}, JSON.stringify({ 'name': 'Marvin' }));
    	  },
    	  subscribe() { // 订阅公告
    	  	this.stompClient.subscribe('/user/notice', function (greeting) {
    	        var obj = JSON.parse(greeting.body)
    	        obj.id = id_string;
    	        var _type = ''
    	        var _title = ''
    	        if (greeting.headers.msg_type == 'announce') {
    	          _type = '公告'
    	          _title = obj.title              
    	        } else if (greeting.headers.msg_type == 'notice') {       
    	          if (obj.deploymentKey == 'customerReName') {   // 客户更名申请
    	            _type = '通知'
    	            _title = obj.title           
    	          } else if (obj.deploymentKey == 'customerChangeBelong') {  // 客户转归属申请
    	            _type = '通知'
    	            _title = obj.title          
    	          }
    	        }   
    	        this.clear1=setTimeout(() => {
    		          alert('【' + _type + '】' + _title);
    		    }, 3000)  
             });
    	  }, 
          // 断开连接
          disconnect() {
            if (this.stompClient != null) {
              this.stompClient.disconnect();
              console.log("Disconnected");
            }
          }
        },
        mounted:function(){
          this.initWebSocket();
        },
        beforeDestroy: function () {
          // 页面离开时断开连接,清除定时器
          this.disconnect();
          clearInterval(this.timer);
        },
    };

     

    展开全文
  • 随着跨端技术的发展,前端开发职能不再局限于浏览器,而是具备了很多客户端开发的能力,比如桌面应用框架Electorn,移动App框架React native. 一般而言,前端同学对http协议非常熟悉,在平时的工作中使用http与后端通信...

    前言

    随着跨端技术的发展,前端开发职能不再局限于浏览器,而是具备了很多客户端开发的能力,比如桌面应用框架Electorn,移动App框架React native.

    一般而言,前端同学对http协议非常熟悉,在平时的工作中使用http与后端通信居多.但在原生客户端领域,比如Java语言开发的安卓应用,与后端通信的方式大多采用socket.

    众所周知,http连接是一种短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断掉.而socket连接是一种长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉.

    前端领域存在一个和socket连接功能相似的通信协议,即WebSocket.WebSocket创建了一种持久性的连接,后端不仅能正常处理客户端发送的消息,还能主动向客户端推送消息.

    后端主动推送消息的能力在一些特殊的场景中太重要了,比如App接受到的信息通知,即时通讯接受的好友信息,另外面板上实时展现波动的金融数据.

    不管是桌面应用框架Electron,还是App开发框架React native,它们都拥有基于原生平台封装的WebSocket.比起浏览器端开放的WebSocket,原生平台提供的支持要稳定很多.

    因此在使用前端技术开发客户端应用时,完全可以使用WebSocket协议作为前后端通信的主要方式,不再需要往项目中引入http,因为http拥有的能力WebSocket同样也能找到替代方案.

    本文接下来将详细介绍用react hook开发一款客户端应用时,如何在项目中搭建有效的通信机制,让WebSocketredux有机结合,在不影响前端习惯的编程风格下,建立起客户端与服务器的全双工通信.

    实现

    数据格式

    前后端约定连接建立后,使用WebSocket协议通信的数据格式(参考如下).

    {
       request_id,
       command,
       data,  
    }
    
    • request_id是一段随机生成的字符串,用来标识本次客户端向服务器请求的id值.
    • command是命令关键词,类似于接口中的url,后端根据此标识来决定返回接口数据.
    • data是发送的参数.

    前端向后端发起请求时,以上3个参数都得携带,其他参数可根据业务需要增添.

    后端主动向前端推送消息时,只需要发送commanddata参数即可.客户端监听command,根据值的不同进行相应的操作.

    在整个项目的通信架构下,前端需要搭建好以下两种通信机制.

    • 客户端向服务器发送请求,服务器处理请求并返回响应结果,客户端接受响应结果再做后续处理.这种机制模拟了类似于前端ajax的通信方式,客户端除了发送请求,还要负责接受该请求的响应.

    • 服务器主动向客户端推送消息,客户端接受消息.

    以上两种机制基本满足了开发的需要,接下来在项目实战中实现以上两种机制(源代码贴在了文章结尾).

    登录功能

    登录页如下,页面内容很简答,两个输入框,输入账号和密码.还有一个登录按钮.

    鼠标点击登录按钮时,dispatch触发LoginAction,此时客户端向服务器发起登录请求,请求成功后进入then的回调函数,打印出登录成功并且路由跳转到首页home.

    import { LoginAction } from "../../redux/actions/login"; 
    
    export default function Login() {
    
      const dispatch = useDispatch();
      const history = useHistory();
      
      //省略
      ...
      
      //登录
      const login = ()=>{
        dispatch(LoginAction()).then(()=>{
          console.log("登录成功!");
          history.push("/home");
        })
      }
    
      return (
              <div>
                <ul>
                  <li>登录账号:</li>
                  <li>
                      <div><input onChange={updateUser} type="text" placeholder="请输入账号" /></div>
                  </li>
                  <li>登录密码:</li>
                  <li>
                      <div><input onChange={updatePwd} type="text" placeholder="请输入密码" /></div>
                  </li>
                </ul>
                <button onClick={login}>立即登录</button>
              </div>  
      )
    }
    

    现在进入LoginAction函数的内部实现,探索它是如何实现发起请求 - 接受响应(代码如下).

    LoginAction内部调用了fetch函数,actiontype值为"PUSH_MESSAGE".

    从这里大概可以推断出fetch内部调用了dispatch,派发了一个type"PUSH_MESSAGE"action.

    另外fetch函数返回了一个Promise,在then回调函数里接受后端返回的响应.

    import { fetch } from "./global";
    
    // 组装action数据类型
    const loginType = (username,password)=>{
        return {
            type:"PUSH_MESSAGE", // 实际开发中这里应该用变量替代
            value:{
                command:"login",
                data:{
                    username,
                    password
                }
            }
        }
    }
    
    export const LoginAction = ()=>(dispatch,getState)=>{
        const { username,password } = getState().login;
        return fetch({
            dispatch,
            action:loginType(username,password)
        }).then((response:any)=>{
           console.log("接受后端响应,请求成功!");
        })
    }
    

    由此可见,fetch函数它能实现向后端发起请求,并且在返回的then回调函数里获取响应的能力.

    fetch函数代码如下,调用fetch时如果传递了第三个参数loading,就调用openLoading(dispatch),从而修改reducer中定义的一个全局状态loading,页面就可以根据loaidng值做加载中的样式变换.

    fetch函数内部主要返回了一个Promise,核心代码便是将resolve赋予了action,那么这将意味着何时调用action.resolve(),fetch函数返回的then回调函数将何时执行.

    代码接下来执行dispatch(action),将传递给fetch函数的参数action派发了.

    从上面代码可知,派发的action.type的值为PUSH_MESSAGE,那么肯定在redux中有一个地方会监听到派发的action,并触发对后端的请求.

    
    /**
     *  loading 决定本次请求需不需要让页面出现加载中的样式 
     */
    export const fetch = ({dispatch,action,loading = false}) =>{
        loading && openLoading(dispatch); // 加载中
        return new Promise((resolve,reject)=>{
            action.resolve = resolve;
            action.reject = reject;
            dispatch(action);    
        }).finally(()=>{ // 异步请求完成后关闭loading
            closeLoading(dispatch);
        })
    }
    
    //修改全局reducers/global.ts定义的loading状态 
    const openLoading = (dispatch)=>{
       dispatch({
           type:"UPDATE_GLOBAL_STATE",
           value:{
               type:"loading",
               value:true
           }
       })
    }
    
    const closeLoading = (dispatch)=>{
        dispatch({
            type:"UPDATE_GLOBAL_STATE",
            value:{
                type:"loading",
                value:false
            }
        })
    }
    
    

    中间件函数

    在哪个地方去监听type值为PUSH_MESSAGEaction呢?这部分代码封装在redux中间件函数里非常合适.

    中间件函数不仅能解析action的具体参数,它还能将全局要使用的WebSocketredux绑定在一起,最终实现通过派发action达到运用WebSocket向后端发起请求的目的.

    观察下面中间件函数,即wsMiddleware()的返回值.redux的中间件函数会在应用每一次派发action后,都会执行一遍.

    登录action派发后,线程会进入下面的中间件函数运行.中间件函数里编写了一个switch,分别监听type值为CONNECT_READYPUSH_MESSAGE以及DIS_CONNECT,分别对应建立WebSocket连接、向后端推送数据以及断开连接三种操作.

    登录操作派发的action.type正是等于PUSH_MESSAGE,因此会进入第二个case结构,代表向后端发起请求.

    后面我们会设置应用启动时派发type值为CONNECT_READYaction,即创建WebSocket连接.假设进行登录操作时,WebSocket连接已经创建好了,变量名为socket.

    case 'PUSH_MESSAGE'包裹的代码里,代码首先解析action的参数commanddata,并使用uuid随机生成一个不重复的字符串request_id.

    commanddatarequest_id组装成参数对象message.接下来关键的一步代码,如果发现action携带resolve参数,说明本次请求是由调用上面fetch函数发起的,因此需要将该action缓存到callback_list.最后调用socket.send将请求发送给后端.

    前后端商议好,凡是带有request_id的请求,后端处理完后也要将request_id联合响应返回给前端.这样前端就能知道返回的响应对应着哪一次发起的请求.

    现在前端请求已经结束了,后端一旦处理完就会向前端发起推送通知,这时候就会触发onMessage函数.

    onMessage函数拿到后端推送的消息,取出其中的request_id,并检查callback_list是否缓存过该action,如果缓存了说明该请求是通过fetch发起,那么此时调用action.resolve(response.data),就能触发fetch返回的回调函数执行并将响应结果一同传递过去.

    这样整个过程串联起来就会发现,页面组件先调用action,而action调用fetch,进而fetch又触发中间件函数使用Websocket向后端发送数据,并将请求的action缓存在callback_list.后端返回响应后,中间件的监听函数从callback_list里取出缓存的action,并调用action.resolve从而顺利触发了fetch的回调函数执行.因此整个环节便实现了发起请求 - 接受响应.

    const WS = window.require('ws');// 安装基于node构建的websocket库ws,并使用window.require引入
    import { messageResolve } from './common';
    import { v1 as uuid } from 'uuid';
    
    //请求缓存列表
    const callback_list = {};
    
    const wsMiddleware = () => {
      let socket = {}; // 存储websocket连接
    
      /**
       * 连接成功了
       */
      const onOpen = (store) => {
        store.dispatch({
          type: 'UPDATE_GLOBAL_STATE',
          value: {
            type: 'connected',
            payload: true,
          },
        });
      };
    
      /**
       * 收到发送过来的消息
       */
      const onMessage = (store, response) => {
        if(typeof response === "string"){
          response = JSON.parse(response);
        }
        let action;
        if (response.request_id && (action = callback_list[response.request_id])) {
          delete callback_list[response.request_id];
          // 该请求缓存过了
          action.resolve(response.data);
        }
        messageResolve(store, response);
      };
    
      /**
       * 连接断开了
       */
      const onClose = (store) => {
        store.dispatch({
          type: 'UPDATE_GLOBAL_STATE',
          value: {
            type: 'connected',
            payload: false,
          },
        });
      };
      
      //定时器
      let timer = null;
    
      //返回中间件函数
      return (store) => (next) => (action) => {
        switch (action.type) {
          // 建立连接
          case 'CONNECT_READY':
            timer = setInterval(() => {
              if (socket != null && (socket.readyState == 1 || socket.readyState == 2)) {
                //已经连接成功了
                timer && clearInterval(timer);
                timer = null;
                return;
              } 
              socket = new WS('ws://localhost:8080');     
              socket.on('open', () => {
                onOpen(store);
              });
              socket.on('message', (data: any) => {
                onMessage(store, data);
              });
              socket.on('close', () => {
                onClose(store);
              });
            }, 1000);
    
            break;
          // 向后台推送消息
          case 'PUSH_MESSAGE':
            const { command, data } = action.value;
            const message = {
              command,
              data,
              request_id: uuid(),
            };
            if (action.resolve) {
              callback_list[message.request_id] = action;
            }
            socket.send(JSON.stringify(message)); // 想后端推送消息
            break;
          // 应用主动发起断开连接的操作
          case 'DIS_CONNECT':
            socket.close();
            onClose(store);
            break;
          default:
            next(action);
        }
      };
    };
    
    export default wsMiddleware();
    
    

    建立连接

    上面中间件函数还监听了两种操作,分别是'CONNECT_READY'对应的建立连接和'DIS_CONNECT'对应的断开连接.

    在看上述操作之前,先在reducers下面创建一个存储全局通用的状态文件global.js(代码如下).文件分别定义了四个状态,分别是connectedtokenis_login以及loading.

    connected用来标记当前Websocket有没有处于连接上,比如突然断网connected的值会变成false,那么界面上就可以根据该状态值做相应的视图展现.

    tokenis_login是登录成功后赋予的值,下一次客户端再发起请求时就可以将token值塞到data中一起发送给后端做校验.

    const defaultState = {
      connected: false, // 是否连接上
      token: '', // 登录成功返回的token
      is_login:false, // 已经登录了吗
      loading:false //页面是否显示加载中的样式
    };
    
    export default (state = defaultState, action: actionType) => {
      switch (action.type) {
        case 'UPDATE_GLOBAL_STATE': // 修改全局状态
          const { type, payload } = action.value;
          return { ...state, [type]: payload };
        default:
          return state;
      }
    };
    

    全局状态定义了四个,而与中间件函数密切相关的属性是connected.

    case 'CONNECT_READY' 负责监听建立Websocket连接的操作(代码如下),代码块里首先定义了一个定时器,每过一秒连接一次,直到与后端连接成功为止.

    连接建立后,socket分别监听了三个函数openmessageclose.open函数会在连接建立成功后触发,成功后将全局状态connected置为true.

    close断开连接时触发,断开时将全局状态connected置为false.

    message监听后端推送的过来的消息.这里的消息分为两种类型.一种是前端发起请求,后端返回响应,另一种是后端主动推送消息.

    那何时何地派发type值为'CONNECT_READY'action来建立Websocket连接呢?

    
     /**
       * 连接成功了
       */
      const onOpen = (store) => {
        store.dispatch({
          type: 'UPDATE_GLOBAL_STATE',
          value: {
            type: 'connected',
            payload: true,
          },
        });
      };
    
      /**
       * 收到发送过来的消息
       */
      const onMessage = (store, response) => {
        if(typeof response === "string"){
          response = JSON.parse(response);
        }
        let action;
        if (response.request_id && (action = callback_list[response.request_id])) {
          delete callback_list[response.request_id];
          // 该请求缓存过了
          action.resolve(response.data);
        }
        messageResolve(store, response);
      };
    
      /**
       * 连接断开了
       */
      const onClose = (store) => {
        store.dispatch({
          type: 'UPDATE_GLOBAL_STATE',
          value: {
            type: 'connected',
            payload: false,
          },
        });
      };
    
    //省略
    ...
    
    case 'CONNECT_READY':
            timer = setInterval(() => {
              if (socket != null && (socket.readyState == 1 || socket.readyState == 2)) {
                //已经连接成功了
                timer && clearInterval(timer);
                timer = null;
                return;
              }
              
              socket = new WS('ws://localhost:8080');   
             
              socket.on('open', () => {
                onOpen(store);
              });
              socket.on('message', (data: any) => {
                onMessage(store, data);
              });
              socket.on('close', () => {
                onClose(store);
              });
            }, 1000);
    

    文章其实上面已经提及,建立连接应该发生在应用启动之时,因为只有当Websocket连接成功了,后面所有的操作才有意义.

    新建一个组件WsConnect执行连接操作(代码如下).组件先判断全局状态connected值,如果发现没有连接上,随即派发CONNECT_READY,触发中间件的函数执行创建Websocket连接的操作.

    const WsConnect = (props) => {
        const dispatch = useDispatch();
        const { connected } = useSelector((state)=>state.global);
        //建立websocket连接
        if(!connected){
            dispatch({
                type:"CONNECT_READY"
            });
        }
        return (
            <div>
                {props.children}
            </div>
        );
    }
    export default WsConnect;
    

    最后将WsConnect塞入到react的根组件App中,这样就能确保应用在启动之时就会派发action建立Websocket连接.

    export default function App() {
      return (
        <Provider store={store}>
          <WsConnect>
            <Router />
          </WsConnect>
        </Provider>
      );
    }
    
    

    登录完成

    我们再回到最初讲解的LoginAction(代码如下),中间件函数内监听到后端响应回来时会执行action.resolve(response.data).

    这句代码一执行就会触发fetch返回的then回调函数执行.

    回调函数将后端返回的值赋予了全局状态token,并将全局状态is_login设置为true,代表登录成功了.

    const updateGlobalType = (type,value)=>{
        return {
            type:"UPDATE_GLOBAL_STATE",
            value:{
                type,
                value
            }
        }
    }
    
    export const LoginAction = ()=>(dispatch,getState)=>{
        const { username,password } = getState().login;
        return fetch({
            dispatch,
            action:loginType(username,password)
        }).then((response)=>{
            dispatch(updateGlobalType("token",response.token)); // 存储token值
            dispatch(updateGlobalType("is_login",true)); //将全局状态is_login置为true
        })
    }
    

    由于上面fetch函数前面加了一个return返回自己的执行结果,因此界面上调用dispatch(LoginAction())也能返回一个then回调函数(代码如下).

    在回调函数里引用react-router-dom提供的api,登录成功后页面立马跳转到首页,至此整个登录流程完结.

    import { useHistory } from "react-router-dom";
    import { LoginAction } from "../../redux/actions/login"; 
    
    export default function Login() {
    
      const dispatch = useDispatch();
      const history = useHistory();
      
      //省略
      ...
      
      //登录
      const login = ()=>{
        dispatch(LoginAction()).then(()=>{
          console.log("登录成功!");
          history.push("/home");
        })
      }
    
      return (
              <div>
                <ul>
                  <li>登录账号:</li>
                  <li>
                      <div><input onChange={updateUser} type="text" placeholder="请输入账号" /></div>
                  </li>
                  <li>登录密码:</li>
                  <li>
                      <div><input onChange={updatePwd} type="text" placeholder="请输入密码" /></div>
                  </li>
                </ul>
                <button onClick={login}>立即登录</button>
              </div>  
      )
    }
    

    接受通知

    登录功能实践了发起请求 - 接受响应的整体环节,接下来实现服务器主动推送消息的机制.

    Demo最终实现效果图如下.登录成功后,页面跳转到首页.在客户端没发起请求的条件下,应用会连续收到后端发送过来的推送通知,并将推送的数据渲染到首页视图上.

    通过上面对中间件函数讲解可知,onMessage专门负责处理后端推送过来的消息(代码如下).如果是后端主动推送的消息通知,代码会进入messageResolve函数执行.

      import { messageResolve } from './common';
    
      /**
       * 收到发送过来的消息
       */
      const onMessage = (store, response) => {
        if(typeof response === "string"){
          response = JSON.parse(response);
        }
        let action;
        if (response.request_id && (action = callback_list[response.request_id])) {
          delete callback_list[response.request_id];
          // 该请求缓存过了
          action.resolve(response.data);
        }
        messageResolve(store, response);
      };
    

    messageResolve函数(代码如下)一方面会派发typeMESSAGE_INCOMMINGaction,触发某些页面上定义的监听逻辑.

    另一方面它会解析出响应的command字段,用来判端是否触发一些公共功能.比如全局的消息通知以及版本升级的操作.

    /**
     *  消息处理
     */
    export const messageResolve = (store, response) => {
      //将推送的消息广播全局,因为可能某些页面需要监听消息
      store.dispatch({
        type: 'MESSAGE_INCOMMING',
        value: response,
      });
      //公共功能的开发
      switch (response.command) {
        case 'message_inform': //消息通知,可以用弹框提醒
          console.log(`后端推送一条通知:${JSON.stringify(response.data)}`);
          break;
        case 'software_upgrading'://版本升级
          console.log("触发版本升级的窗口");
          break;
      }
    };
    

    首页reducer一旦监听到messageResolve派发的action(代码如下),会解析出command字段的值,如果发现command值与"home/add_item"相等,说明后端想在首页上实时动态添加数据.

    最终首页视图会获取reducer定义的list状态渲染列表,当后端主动推送一条数据时,页面就会触发重新渲染.

    至此后端主动推送的机制便已实现.

    const defaultState = {
      list: []
    };
    
    export default (state = defaultState, action: actionType) => {
      switch (action.type) {
        case 'MESSAGE_INCOMMING': //监听后端推送过来的消息
          if(action.value.command === "home/add_item"){ // 添加一条数据
            return {...state,list:[...state.list,action.value.data]};
          }
          return state;
          break;
        default:
          return state;
      }
    };
    
    

    源码

    源码地址

    展开全文
  • vue 中,运用WebSocket通信,解决前端轮询问题和多用户数据共享问题需求描述:最初实现方式:问题描述问题解决方式WebSocket 简介WebSocket 规范WebSocket 握手协议WebSocket 实战( **Vue中进行使用** )参考文档: ...
  • Socket,WebSocket,Http,Tcp等这些我们已经听的耳朵有茧了,但是用得时候还是复习一下吧。  大学学习网络基础的时候老师讲过,网络由下往上分为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。...
  • wechat-im 微信小程序即时通讯 开发这个项目付出了我很多心血,如果对你有帮助和启发的话,希望在GitHub上给个star!也是对我工作的支持和肯定! 也非常感谢对项目中文本超长溢出...有能力的同学可以更新啦,大部分是语
  •  Socket,WebSocket,Http,Tcp等这些我们已经听的耳朵有茧了,但是用得时候还是复习一下吧。  大学学习网络基础的时候老师讲过,网络由下往上分为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。...
  • 最近接到一个业务需求,需要做一个聊天信息的实时展示的界面,这就需要和服务器端建立webSocket连接,从而实现数据的实时获取和视图的实时刷新.在此将我的实现记录下来,希望可以给有同样需求的人一些帮助.废话少说,下面...
  • websocket简单通信

    2018-10-08 21:55:52
    使用websocket可以实现客户端到服务器的全双工通信,底层还是tcp协议。原理概念请百度。、。 首先我们使用idea搭建一个简单的web项目,使用maven管理项目的依赖。为项目添加spring springmvn支持能力,添加pom依赖...
  • -资源配额可分为四类: 资源均衡型 CDN 资源消耗型 云函数资源消耗型 数据库资源消耗型 webSocket 通信 微信小程序中的webSocket 方法和事件: connectSocket({url}) 连接后端webSocket 服务 onSocketOpen() 与后端...
  • 我的地址 :blog.csdn...Socket,WebSocket,Http,Tcp等这些我们已经听的耳朵有茧了,但是用得时候还是复习一下吧。大学学习网络基础的时候老师讲过,网络由下往上分为物理层、数据链路层、网络层、传输层、会话层...
  • MQTT-WebSocket连接通信

    千次阅读 2020-10-26 18:52:40
    MQTT-WebSocket连接通信 更新时间:2020-10-21 15:13:47 编辑我的收藏 本页目录 背景信息 操作步骤 物联网平台支持基于WebSocket的MQTT协议。您可以首先使用WebSocket建立连接,然后在WebSocket通道上,使用...
  • 全双工通信WebSocket

    2019-09-12 11:09:14
    WebSocket 是一种网络通信协议。在 2009 年诞生,于 2011 年被 IETF 定为标准 RFC 6455 通信标准。并由 RFC7936 补充规范。WebSocket API 也被 W3C 定为标准。 WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上...
  • WebSocket

    千次阅读 2015-08-03 17:25:24
    WebSocket的主要作用是,允许服务器端与客户端进行全双工(full-duplex)的通信。举例来说,HTTP协议有点像发电子邮件,发出后必须等待对方回信;WebSocket则是像打电话,服务器端和客户端可以同时向对方发送数据,...
  • 前言 WebSocket 是HTML5开始提供的一种在浏览器和服务器...这样势必会较大程度浪费服务器和带宽资源,而我们现在要讲的WebSocket正是来解决该问题而出现,使得B/S架构的应用拥有C/S架构一样的实时通信能力。 H...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,004
精华内容 4,401
关键字:

websocket通信能力