添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

长连接(long pull)

  • 以即时通信为代表的web应用程序对数据的Low Latency要求,传统的基于轮询的方式已经无法满足,而且也会带来不好的用户体验。于是一种基于http长连接的“服务器推”技术便被hack出来。这种技术被命名为Comet。
  • 客户端发起请求,服务端把这个连接攥在手里不回复,等有消息了再回,如果超时了客户端就再请求一次——其实大家也懂,这只是个减少了请求次数、实时性更好的轮询,本质没变。
  • 同ajax轮询一样,也是每次都要建立HTTP连接,同样也都是被动的。而且这种方法对服务器的并行要求比较大,因为在没有消息的时候,服务器拽着连接不放,而这时需要其它信息时又要建立新的连接。
  • Websocket

    Websocket是HTML5中提出的新的协议,注意,这里是协议,可以实现客户端与服务器端的全双工通信,实现服务器的推送功能。

    Websocket

  • 本质上就是为了解决HTTP协议本身的单向性问题。先通过HTTP/HTTPS协议进行握手后创建一个用于交换数据的TCP连接,服务端与客户端通过此TCP连接进行实时通信。
  • 它借用了Web的端口和消息头来创建连接,后续的数据传输又和基于TCP的Socket几乎完全一样,但封装了好多原本在Socket开发时需要我们手动去做的功能。
  • WebSocket协议的URL使用WS://开头,另外安全的WebSocket协议使用WSS://开头
  • WebSocket和http,tcp的联系

    基础API

  • onopen:WebSocket创建成功时执行
  • onmessage:当客户端收到信息时,会触发onmessage事件
  • onclose:当客户端收到服务器发来的关闭连接请求时,浏览器会触发onclose事件
  • onerror:当出现连接、处理、接收、发送数据失败的时候就会触发onerror事件
  • 我们可以看出所以的操作都是采用事件的方式触发,如此就不会阻塞UI,使得UI更快的响应时间,得到更好的用户体验

    websocket与TCP,HTTP的关系

    WebSocket与http协议一样都是基于TCP的,所以他们都是可靠的协议,Web开发者调用的WebSocket的send函数在browser 的实现中最终都是通过TCP的系统接口进行传输的。WebSocket和Http协议一样都属于应用层的协议,那么他们之间有没有什么关系呢?答案是肯定 的,WebSocket在建立握手连接时,数据是通过http协议传输的,正如我们上一节所看到的“GET/chat HTTP/1.1”,这里面用到的只是http协议一些简单的字段。但是在建立连接之后,真正的数据传输阶段是不需要http协议参与的。

    websocket server

  • PyWebSocket:PyWebSocket采用Python语言编写,可以很好的跨平台,扩展起来也比较简单,目前WebKit采用它搭建WebSocket服务器来做LayoutTest。
  • WebSocket-Node:WebSocket-Node采用JavaScript语言编写,这个库是建立在nodejs之上的,对于熟悉JavaScript的朋友可参考一下,另外Html5和Web应用程序受欢迎的程度越来越高,nodejs也正受到广泛的关注。
  • LibWebSockets:LibWebSockets采用C/C++语言编写,可定制化的力度更大,从TCP监听开始到封包的完成我们都可以参与编程。
  • Socket.IO

    整合多种双向推送消息方式的库,当初最大的卖点就是兼容所有浏览器版本,自动识别旧版浏览器并采取不同的连接方式,现在也渐渐失去了优势,所有新版浏览器都兼容WebSocket,直接用原生的就行了。

    node.js提供了高效的服务端运行环境,但是由于浏览器端对HTML5的支持不一,为了兼容所有浏览器,提供卓越的实时的用户体验,并且为程序员提供客户端与服务端一致的编程体验,于是socket.io诞生。Socket.io将Websocket和轮询 (Polling)机制以及其它的实时通信方式封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。

  • 很大一部分AJAX的使用场景仍然是传统的请求-响应形式,比如获取json数据、post表单之类。这些功能虽然靠WebSocket也能实现,但就像在原本传输数据流的TCP之上定义了基于请求的HTTP协议一样,我们也要在WebSocket之上重新定义一种新的协议,最少也要加个request id用来区分每次响应数据对应的请求.但是何苦重复造轮子呢。
  • 传输大文件、图片、媒体流的时候,最好还是老老实实用HTTP来传。如果一定要用WebSocket的话,至少也专门为这些数据专门开辟个新通道,而别去占用那条用于推送消息、对实时性要求很强的连接。否则会把串行的WebSocket彻底堵死.
  • 所以说WebSocket在用于双向传输、推送消息方面能够做到灵活、简便、高效,但在普通的Request-Response过程中并没有太大用武之地,比起普通的HTTP请求来反倒麻烦了许多,甚至更为低效。
  • 每项技术都有自身的优缺点,在适合它的地方能发挥出最大长处,而看到它的几个优点就不分场合地全方位推广的话,可能会适得其反。

    服务端API

    备注:使用Node作为服务端。

    var io = require('socket.io')(http);
    
  • connection事件
    io.on('connection', function(socket){
    console.log('a user connected');
    
  • disconnect事件,每个socket离线时会发射特殊的disconnect事件
  • message事件,客户端通过socket.send来传送消息时触发此事件,message为传输的消息,callback是收到消息后要执行的回调
    socket.on(‘message’, function(message, callback) {})
    
  • anything,收到任何事件时触发
  • 事件注册和监听

    数据的交流通过事件的方式,因此事件的发射,监听,和广播才是关键

  • 监听,例子:客户端发送消息,服务端接受消息
    //client
    socket.emit('chat message', $('#m').val());
    // server
    socket.on('chat message', function(msg){
    console.log('message: ' + msg);
    
  • 为了能够向每个人都发射事件,Socket.IO为我们提供了io.emit()方法。
    io.emit('some event', { for: 'everyone' });
    
  • 如果指定客户端发送给其他客户端,自己不能收到。
    socket.broadcast.emit('some event',hi');
    

    设置命名空间

    有的时候要一个程序支持多个应用,如果使用默认的 “/” 命名空间可能会比较混乱。如果想让一个连接可以支持多个连接,可以使用如下的命名空间的方法:

    io.of('/chat').on('connection', function (socket) {});
    

    发送获取数据

    很多时候,我们可以在发送的时候直接获取数据,在emit的时候,我们可以传递第三个参数作为回调函数,服务端接受回调函数。

    // client
    socket.emit('evtnanme', 'clientdata', function (data) {
     console.log(data); // data will be 'serverdata'
    // server
    socket.on('evtnanme', function (name, fn) {
      // name will be 'clientdata'
      fn('serverdata');
    

    客户端API

    暴露了io作为全局对象,然后进行连接,调用io()得到socket对象,没有声明任何URL,默认是尝试连接提供网页的主机。

    emit & on

  • 非常重要:特别注意socket.io使用on绑定事件,可以理解成一个多播事件,如果代码运行多次,会对事件绑定多次,这样的话一旦事件发生了,事件处理函数会执行多次,这里一定要注意,看代码运行多次对自己的项目会不会有影响,一般同样的代码执行多次肯定是有问题,融云连接的问题就在于此,因为必须注意这个问题。
  • once:这个方法不是说不会重复绑定,而是事件触发一次之后就自动解除绑定了,之后不会在响应。
  • connect
  • connecting
  • disconnect
  • connect_failed
  • error:错误发生,并且无法被其他事件类型所处理
  • anything:服务器端anything事件
  • reconnect_failed
  • reconnect
  • reconnecting
  • 这里只简单列举功能点,具体查看官方文档

  • Server 配置path,serverClient,adapter,origins,parser,pingTimeout,pingInterval,cookie等参数。
  • Namespace
    • 命名名称:namespace.name
    • 连接在此空间的socket集合:namespace.connected
    • 适配器:eg:redis
    • 指定房间:namespace.to(room),指定多个可以使用多次to,to也可以使用in代替
    • 得到连接到这个空间下的客户端:namespace.clients(callback)
    • 注册中间件:namespace.use(fn)
    • Socket
      • socket的标识符:socket.id
      • 加入的房间:socket.rooms
      • 得到客户端:socket.client
      • 请求对象:socket.request
      • socket.handshake
      • 注册中间件:socket.use(fn)
      • 发送message事件:socket.send([...args][, ack])
      • socket.on(eventName, listener)
      • socket.once(eventName, listener)
      • socket.removeListener(eventName, listener)
      • socket.removeAllListeners([eventName])
      • socket.eventNames()
      • 加入指定房间:socket.join(room[, callback])
      • 加入多个房间:socket.join(rooms[, callback])
      • 从指定房间移除:socket.leave(room[, callback])
      • 达到指定房间:socket.to(room)或者使用in
      • 是否压缩:socket.compress(value)
      • 断开连接:socket.disconnect(close)
      • Flag:broadcast,volatile
      • Event:disconnect,error,disconnecting
      • Client
      • io.protocol
      • io([url][, options])
        • io({autoConnect: false});
        • Manager
          • new Manager(url[, options])
          • Socket
            • socket.id
            • socket.open() = socket.connect()
            • 触发message事件,socket.send([...args][, ack])
            • socket.on(eventName, callback)
            • socket.compress(value)
            • socket.close() = socket.disconnect()
            • event:connect,connect_error,connect_timeout,error,disconnect,reconnect,reconnect_attempt,reconnecting,reconnect_error,reconnect_failed,ping,pong
            • Namespaces and Rooms

            • Namespaces
              • 创建命名空间io.of('/my-namespace');
              • Rooms
                • 在每个名称空间中,您还可以定义任意频道,套接字可以加入和离开。
                • socket.join('some room');
                • io.to('some room').emit('some event');
                • 默认房间:套接字在被创建时,通过一个唯一的Socket#id确定,为了方便使用,每个套接字都会被自动加入通过自己id命名的房间。
                • 附录1-事件发送示例

                  事件emit示例

                  // sending to the client
                    socket.emit('hello', 'can you hear me?', 1, 2, 'abc');
                    // sending to all clients except sender
                    socket.broadcast.emit('broadcast', 'hello friends!');
                    // sending to all clients in 'game' room except sender
                    socket.to('game').emit('nice game', "let's play a game");
                    // sending to all clients in 'game1' and/or in 'game2' room, except sender
                    socket.to('game1').to('game2').emit('nice game', "let's play a game (too)");
                    // sending to all clients in 'game' room, including sender
                    io.in('game').emit('big-announcement', 'the game will start soon');
                    // sending to all clients in namespace 'myNamespace', including sender
                    io.of('myNamespace').emit('bigger-announcement', 'the tournament will start soon');
                    // sending to individual socketid (private message)
                    socket.to(<socketid>).emit('hey', 'I just met you');
                    // sending with acknowledgement
                    socket.emit('question', 'do you think so?', function (answer) {});
                    // sending without compression
                    socket.compress(false).emit('uncompressed', "that's rough");
                    // sending a message that might be dropped if the client is not ready to receive messages
                    socket.volatile.emit('maybe', 'do you really need it?');
                    // sending to all clients on this node (when using multiple nodes)
                    io.local.emit('hi', 'my lovely babies');
                  

                  保留事件,请勿暂用

                  error
                  connect
                  disconnect
                  disconnecting
                  newListener
                  removeListener
                  

                  维护在线列表

                  一开始想在服务端connection事件中直接将用户加入在线列表中,但是那样无法传递数据,应该另起一个事件,具体步骤如下:

                • 比如add user专门用来新增用户
                • 服务端监听用户disconnect离线事件,用来删除用户
                • 以上两个看上去完成了,但是有个隐藏的大问题,在应用期间socket离线时,如果网络恢复,会自动重连,而这时候服务器已经将它删除,这样就会造成用户不在线的错误,因此还有第三步,重连新增用户。
                  socket.on('reconnect',function(){
                   socket.emit('add user',data);
                  

                  连接不上服务器

                  具体表现为无限Ajax请求,但始终无法连接上服务器,在自己的Demo上是没有问题的,移到公司的Ionic App上就出现问题这种问题,一开始考虑是不是在Angular中使用方法不一样?实在找不到原因,但使用最新的问题的socket.io的CDN,问题就不出现了。

                  由于项目上之前存在一个socket的应用是可以正常运行的,那么为什么现在的会出现问题呢?猜测:应该是服务端和客户端的socket.io的版本有对应关系

                  事件重复响应

                  应用到Ionic App上时,客户端出现重复响应事件的问题,一开始考虑到会不会会服务器多处推的问题,排除掉之后才发现是客户端的问题。谨记以下几点:

                • on绑定事件属于多播绑定,使用多次就会响应多次,这样设计是有道理的,给开发者更高的自由度且这是必须的,因为我们需要在监听同一个事件用来处理不同的业务,因此肯定不能采用单播的设计,但是这样带来一个问题,就是开发中不注意,同一个事件处理绑定了多次就会响应多次。
                • 在Angular中,即使controller被销毁,但是其中绑定的事件并不会自动解绑,需要手动解除绑定,不仅仅是Socket的事件,angular中事件也是同样的道理。socket解除事件绑定方法如下:
                  socket.removeListener(name,handler);
                  socket.removeAllListeners();
                  
                • 事件解除绑定时机,这里设计到Ionic的声明周期如下:
                  • $ionicView.loaded:该观点已经加载。此事件仅一次按次序被创建并添加到DOM发生。如果视图离开,但被缓存,那么这个事件将不会再在触发。类似于Android的activity中的onCreate()方法。
                  • $ionicView.enter:该观点已经全面进入,现在是活动视图。此事件将触发,无论是第一次视图或缓存的视图。类似于Android的activity中的onStart()方法。
                  • $ionicView.leave:该观点已经完成离开,不再是积极的看法。此事件将触发,无论是缓存或销毁。类似于Android的activity中的onStop()方法。
                  • $ionicView.beforeEnter:视图是即将进入并成为活动视图。类似于Android的activity中的onResume()方法。
                  • $ionicView.beforeLeave:视图是即将离开,不再是活动视图。类似于Android的activity中的onPause()方法。
                  • $ionicView.afterEnter:该观点已经全面进入,现在是活动视图。
                  • $ionicView.afterLeave:该观点已经完成离开,不再是积极的看法。
                  • $ionicView.unloaded:该视图的控制器已经被破坏,它的元素已经从DOM中删除。 类似于Android的activity中的onDestroy()方法。
                  • $destroy:常用来进行资源释放,清理监听,取消请求,经过思考,取消socket事件绑定的可以放置在此事件中。
                  • socket.io updated at 20220221

                    socket.io 提供两种层级的抽象

                  • low-level:使用 Engine.IO + Manager 对象
                  • high-level:使用 Socket.IO
                  • transport 两种方式

                  • websocket
                  • polling
                  • 推荐使用默认值,也就是 polling 建立连接、websocket 交换数据,但不利于调试,可选择在开发环境直接启用 websocket
                  • socket 实例有三个特别事件

                  • connect:连接上或者重连上时触发
                  • connect_error
                    • 底层连接建立失败
                    • 服务端中间件阻止了
                    • disconnecting
                    • disconnect
                      • io server disconnect 服务端手动关闭
                      • io client disconnect 客户端手动关闭
                      • ping timeout 心跳超时
                      • transport close 连接关闭,比如断开网络、网络切换等
                      • transport error 连接出现错误
                      • 前两种原因不会自动重连,其他情况会尝试重连
                      • 默认情况下,没有连接时发送的消息,会被存到缓存区,直到重连成功,但这可能导致链接激增,当瞬间重连上时
                      • 你可以通过 socket.connected 确保连接才发送消息
                      • 或者使用 socket.volatile.emit 表示消息允许丢失
                      • 服务端多次 emit,客户端同时收到,以及客户端会莫名自动重连

                      • 主要是服务端的原因,线程被重计算卡住后,导致多次 emit 最后一次性收到
                      • 由于服务端线程被卡住,从而导致心跳超时,客户端自动触发重连
                      • socket.emit 和 socket.send 是不一样的,前者是发送自定义事件,后者是发送内置 message 事件

                        关于配置踩的坑,传输文件时,会导致连接断开,reason 内容为 transport close,但当我降级到 2.3 的版本时,确是正确的,这让人很困惑,原因如下

                      • maxHttpBufferSize 设置过小的原因,而正好我的文件比较大,从而导致连接断开
                      • 至于为什么 2.3 可以工作,是因为默认值的不同,2.x 默认值 10e7,而 4.x 默认值 1e6
                      • ClientOptions 需要注意的配置

                      • forceBase64
                        • 使用 websocket 时,是否强制对二进制内容进行 base64 编码
                        • 在 long-polling 模式时,总是开启的
                        • closeOnBeforeunload
                          • 默认为 true,当浏览器 beforeunload 事件触发时候,自动关闭链接
                          • autoConnect 是否自动重连
                          • auth 认证相关
                          • ServeOptions

                          • connectTimeout
                          • maxHttpBufferSize 设置最大传输大小,对应 websocket 的 maxPayload 选项
                          • httpCompression 是否为 HTTP long-polling 传输开启压缩
                          • cors 相关
                          • cookie 往客户端写 cookie
  •