JavaEE鸿蒙应用开发HTML&JS+前端Python+大数据开发人工智能开发AI+设计软件测试新媒体+短视频直播运营产品经理集成电路应用开发(含嵌入式)Linux云计算+运维开发C/C++拍摄剪辑+短视频制作PMP项目管理认证电商运营Go语言与区块链大数据PHP工程师Android+物联网iOS.NET

WebSocket能干些啥?

来源:黑马程序员

浏览15510人

2020.11.11

1)通知功能:

保持一个长连接,当服务端游新的消息,能够实时的推送到使用方。像知乎的点赞通知、评论等,都可以使用WebSocket通信。

某些使用H5的客户端,为了简化开发,也会使用WebSocket进行消息的通知,由于它是实时推送的,会有更好的用户体验。

2)数据收集:

一些次优级别的数据,比如行为日志、trace、异常执栈收集等,都可以开辟专门的WebSocket通道进行传输。这能够增加信息的集中度,并能及时的针对用户的行为进行合适的配置推送。由于大多数浏览器内核都支持,它将使客户端APM编程模型变得简单。

3)加密 && 认证:

虽然使用Fiddler、Charles等能够抓到很多WebSocket包。但如果同时开启SSL,传输加密后的二进制数据,会大幅增加破解的成本,会安全的多。

4)反向控制钩子:

这个...由于是双工长连接,服务端完全可以推送一些钩子命令,甚至直接是代码,在客户端进行执行。比如截个屏,录个音,种个小马。用户只要通过了授权申请,剩下的就随你发挥了。

所以就一个梗:支付宝偷偷调用你的相机给你拍照

下面我们就来了解websocket协议:

HTTP是单工的还是双工的还是半双工的

先来区分三者的含义
  1. 单工: 数据传输只允许在一个方向上的传输,只能一方来发送数据,另一方来接收数据并发送。例如:对讲机

  2. 半双工:数据传输允许两个方向上的传输,但是同一时间内,只可以有一方发送或接受消息。例如:打电话

  3. 全双工:同时可进行双向传输。例如:websocket

http协议是什么工作模式呢

分版本,版本不同,工作模式不同

  1. http1.0:单工。因为是短连接,客户端发起请求之后,服务端处理完请求并收到客户端的响应后即断开连接。

  2. http1.1:半双工。默认开启长连接keep-alive,开启一个连接可发送多个请求。

  3. http2.0:全双工,允许服务端主动向客户端发送数据。

TCP三次握手四次挥手

1605080783396861.png

WebSocket:WebSocket和HTTP什么关系?

WebSocket和http一样,都是处于OSI模型中的最高层:应用层

1605080819584485.png

WebSocket借助http协议进行握手,三次握手成功后,就会变身为TCP通道,从此与http不再相见。

WebSocket是一种在单个TCP连接上进行全双工通信的协议。在WebSocket API中,浏览器和服务器只需要完成一次握手(不是指建立TCP连接的那个三次握手,是指在建立TCP连接后传输一次握手数据),两者之间就直接可以创建持久性的连接,并进行双向数据传输。

1605080850301946.png

WebSocket:WebSocket协议只能浏览器发起么?

不是。目前此协议的受众的也不仅仅是web开发者。

WebSocket只是一种协议,它和http协议一样,使用类似okhttp的组件,可以在任何地方进行调用,甚至可以借助WebSocket实现RPC框架。

1605080894125141.png

WebSocket:WebSocket和长轮询有什么区别?

  • 长轮询

    WebSocket在握手成功后,就是全双工的TCP通道,数据可以主动从服务端发送到客户端,处于链接两端的应用没有任何区别。

    WebSocket创建的连接和Http的长连接是不一样的。由于Http长连接底层依然是Http协议,所以它还是一问一答,只是Hold住了一条命长点的连接而已。

    长轮询和Http长连接是阻塞的I/O,但WebSocket可以是非阻塞的(具体是多路复用)

    • 就是客户端发送一个请求,服务端将一直在这个连接上等待(当然有一个超长的超时时间),直到有数据才返回,它依然是一个一问一答的模式。比如著名的comted。

WebSocket:如何创建一个WebSocket连接?

WebSocket的连接创建是借助Http协议进行的。这样设计主要是考虑兼容性,在浏览器中就可以很方便的发起请求,看起来比较具有迷惑性。

下图是一个典型的由浏览器发起的ws请求,可以看到和http请求长的是非常相似的。

但是,它只是请求阶段长得像而已:

1605080926750661.png

请求的地址: 一般是:ws://***,或者是使用了SSL/TLS加密的安全协议wss:,用来标识是WebSocket请求。

1)首先,通过Http头里面的Upgrade域,请求进行协议转换。如果服务端支持的话,就可以切换到WebSocket协议。简单点讲:连接已经在那了,通过握手切换成ws协议,就是切换了连接的一个状态而已。

2)Connection域可以认为是与Upgrade域配对的头信息。像nginx等代理服务器,是要先处理Connection,然后再发起协议转换的。

3)Sec-WebSocket-Key 是随机的字符串,服务器端会用这些数据来构造出一个 SHA-1 的信息摘要。如此操作,可以尽量避免普通 HTTP 请求被误认为 WebSocket 协议。

其他的,像Sec-WebSocket*字样的头信息,表明了客户端支持的子协议以及其他信息。像loT中很流行的mqtt,就可以作为WebSocket的子协议。

1605080996545185.png

Python服务器端开发

pip install python-socketio

使用协程的方式运行 (推荐)

  import eventlet  eventlet.monkey_patch()  import socketio  import eventlet.wsgi  sio = socketio.Server(async_mode='eventlet')  # 指明在evenlet模式下  app = socketio.Middleware(sio)  eventlet.wsgi.server(eventlet.listen(('', 8000)), app)

1)事件处理方法

编写事件处理方法,可以接收指定的事件消息数据,并在处理方法中对消息数据进行处理。

@sio.on('connect')def on_connect(sid, environ):    """    与客户端建立好连接后被执行    :param sid: string sid是socketio为当前连接客户端生成的识别id    :param environ: dict 在连接握手时客户端发送的握手数据(HTTP报文解析之后的字典)    """    pass@sio.on('disconnect')def on_disconnect(sid):    """    与客户端断开连接后被执行    :param sid: string sid是断开连接的客户端id    """    pass# 以字符串的形式表示一个自定义事件,事件的定义由前后端约定@sio.on('my custom event')  def my_custom_event(sid, data):    """    自定义事件消息的处理方法    :param sid: string sid是发送此事件消息的客户端id    :param data: data是客户端发送的消息数据    """    pass
注意
  • connect 为特殊事件,当客户端连接后自动执行

  • disconnect 为特殊事件,当客户端断开连接后自动执行

  • connect、disconnect与自定义事件处理方法的函数传入参数不同

2)发送事件消息

  • 群发

    sio.emit('my event', {'data': 'foobar'})
  • 给指定用户发送

    sio.emit('my event', {'data': 'foobar'}, room=user_sid)
  • 给一组用户发送

    SocketIO提供了房间(room)来为客户端分组

    • sio.enter_room(sid, room_name)

      将连接的客户端添加到一个room

        @sio.on('chat')  def begin_chat(sid):      sio.enter_room(sid, 'chat_users')

      注意:当客户端连接后,socketio会自动将客户端添加到以此客户端sid为名的room中

    • sio.leave_room(sid, room_name)

      将客户端从一个room中移除

        @sio.on('exit_chat')  def exit_chat(sid):      sio.leave_room(sid, 'chat_users')
    • sio.rooms(sid)

      查询sid客户端所在的所有房间

      给一组用户发送消息的示例

      @sio.on('my message')def message(sid, data):  sio.emit('my reply', data, room='chat_users')

      也可在群组发消息时跳过指定客户端

      @sio.on('my message')def message(sid, data):  sio.emit('my reply', data, room='chat_users', skip_sid=sid)
  • 使用send发送message事件消息

    对于'message'事件,可以使用send方法

      sio.send({'data': 'foobar'})  sio.send({'data': 'foobar'}, room=user_sid)

3 Python客户端

import socketiosio = socketio.Client()@sio.on('connect')def on_connect():    pass@sio.on('event')def on_event(data):    passsio.connect('http://10.211.55.7:8000')sio.wait()

 

WebSocket:如何使用Nginx做WebSocket的负载均衡?

nginx官网已经给出了例子。主要是Upgrade和Connection头的设置。

Nginx的中的配置如下:

map connection_upgrade {

default upgrade;

''close;

}

 

location /chat/{

proxy_pass http://backend;

proxy_http_version 1.1;

proxy_set_header Upgrade $http_upgrade;

proxy_set_header Connection $connection_upgrade;

}

需要注意的是,nginx做负载均衡,不需要配置ip_hash等参数,nginx天然支持。由于ip_hash仅使用ip地址的前三个数字做hash,还有可能造成服务端的不均衡。

特别注意:

在IM聊天系统场景下,Nginx提供给WebSocket的这种所谓的“负载均衡”,只能解决传统分布系统中的SLB服务器要做的事。

通俗地说,Nginx只能帮助完成引导WebSocket客户连接到哪一个WebSocket服务端实例,在IM集群情况下,如果两个用户处于不同的WebSocket实例下时,它们之间的跨实例通信,Nginx是没有办法实现的,这一块的逻辑还是得IM开发者自已来实现。

总而言之,Nginx提供给WebSocket的所谓“负载均衡”,并不是IM开发者认为的那种全功能集群!

一、什么是长连接

HTTP1.1规定了默认保持长连接(HTTP persistent connection ,也有翻译为持久连接),数据传输完成了保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短连接。

1. HTTP协议与TCP/IP协议的关系

HTTP的长连接和短连接本质上是TCP长连接和短连接。HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议。 IP协议主要解决网络路由和寻址问题,TCP协议主要解决如何在IP层之上可靠地传递数据包,使得网络上接收端收到发送端所发出的所有包,并且顺序与发送顺序一致。TCP协议是可靠的、面向连接的

在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。

而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码:

Connection:keep-alive

在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。

**HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。

**

2.1. TCP连接

当网络通信时采用TCP协议时,在真正的读写操作之前,客户端与服务器端之间必须建立一个连接,当读写操作完成后,双方不再需要这个连接时可以释放这个连接。连接的建立依靠“三次握手”,而释放则需要“四次握手”,所以每个连接的建立都是需要资源消耗和时间消耗的。

2.2. TCP短连接

模拟一下TCP短连接的情况:client向server发起连接请求,server接到请求,然后双方建立连接。client向server发送消息,server回应client,然后一次请求就完成了。这时候双方任意都可以发起close操作,不过一般都是client先发起close操作。上述可知,短连接一般只会在 client/server间传递一次请求操作。

短连接的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段。

2.3. TCP长连接

我们再模拟一下长连接的情况:client向server发起连接,server接受client连接,双方建立连接,client与server完成一次请求后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。

TCP的保活功能主要为服务器应用提供。如果客户端已经消失而连接未断开,则会使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,此时服务器将永远等待客户端的数据。保活功能就是试图在服务端器端检测到这种半开放的连接。

如果一个给定的连接在两小时内没有任何动作,服务器就向客户发送一个探测报文段,根据客户端主机响应探测4个客户端状态:

客户主机依然正常运行,且服务器可达。此时客户的TCP响应正常,服务器将保活定时器复位。

客户主机已经崩溃,并且关闭或者正在重新启动。上述情况下客户端都不能响应TCP。服务端将无法收到客户端对探测的响应。服务器总共发送10个这样的探测,每个间隔75秒。若服务器没有收到任何一个响应,它就认为客户端已经关闭并终止连接。

客户端崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。

客户机正常运行,但是服务器不可达。这种情况与第二种状态类似。

长连接和短连接的优点和缺点

由上可以看出,长连接可以省去较多的TCP建立和关闭的操作,减少浪费,节约时间。对于频繁请求资源的客户端适合使用长连接。在长连接的应用场景下,client端一般不会主动关闭连接,当client与server之间的连接一直不关闭,随着客户端连接越来越多,server会保持过多连接。这时候server端需要采取一些策略,如关闭一些长时间没有请求发生的连接,这样可以避免一些恶意连接导致server端服务受损;如果条件允许则可以限制每个客户端的最大长连接数,这样可以完全避免恶意的客户端拖垮整体后端服务。

短连接对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段。但如果客户请求频繁,将在TCP的建立和关闭操作上浪费较多时间和带宽。

长连接和短连接的产生在于client和server采取的关闭策略。不同的应用场景适合采用不同的策略。

由上可以看出,长连接可以省去较多的TCP建立和关闭的操作,减少浪费,节约时间。对于频繁请求资源的客户来说,较适用长连接。不过这里存在一个问题存活功能的探测周期太长,还有就是它只是探测TCP连接的存活,属于比较斯文的做法,遇到恶意的连接时,保活功能就不够使了。在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可 以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务。

短连接对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段。但如果客户请求频繁,将在TCP的建立和关闭操作上浪费时间和带宽

长连接和短连接的产生在于client和server采取的关闭策略,具体的应用场景采用具体的策略,没有十全十美的选择,只有合适的选择。

 

长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,。每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。

而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好。