websocket

一、服务端向客户端推送消息

  • 轮询
  • 长轮询
  • websocket

轮询(效率极低,基本不用)

"""
让浏览器定时(例如每隔5秒发一次)通过ajax朝服务端发送请求获取数据

缺点:
	消息延迟严重
	请求次数多 消耗资源过大
"""

长轮询(兼容性好)

"""
服务端给每个浏览器创建一个队列,让浏览器通过ajax向后端偷偷的发送请求,去各自对应的队列中获取数据,如果没有数据则会有阻塞,但是不会一直阻塞,比如最多阻塞30秒(pending)后给一个响应,无论响应是否是真正的数据,都会再次通过回调函数调用请求数据的代码

有点:
	消息基本没有延迟
	请求次数降低 消耗资源减少
"""
# 大公司需要考虑兼容性问题 追求兼容 目前网页版本的微信和qq用的就是长轮询


# ps:给标签绑定事件的方式大致有两种
# 1标签查找绑定
$('p').click()
# 2直接写函数  注意括号不能少
<p onclick="sendMsg()"></p>

前端递归

# python中有最大递归限制 997 998 官网给出的是1000
"""
在python中是没有尾递归优化的!!!
"""
def func():
  func()
func()  # 不行

# 在js中 是没有递归的概念的 函数可以自己调用自己 属于正常的事件机制
function func1(){
  $.ajax({
    url:'',
    type:'',
    data:'',
    dataType:'JSON',
    success:function({
      func1()  # 可以
    })
  })
}
func1()

后端

import queue


q_dict = {}  # {唯一标示:对应的队列,唯一标示:对应的队列}


def home(request):
    # 获取客户端浏览器的唯一标识
    name = request.GET.get('name')
    # 生成一一对应关系
    q_dict[name] = queue.Queue()
    return render(request,'home.html',locals())


def send_msg(request):
    if request.method == 'POST':
        # 获取用户发送的消息
        message = request.POST.get('content')
        print(message)
        # 将消息给所有的队列发送一份
        for q in q_dict.values():
            q.put(message)
        return HttpResponse('OK')


def get_msg(request):
    # 获取用户唯一标示
    name = request.GET.get('name')
    # 回去对应的队列
    q = q_dict.get(name)
    back_dic = {'status':True,'msg':''}
    try:
        data = q.get(timeout=10)
        back_dic['msg'] = data
    except queue.Empty as e:
        back_dic['status'] = False
    return JsonResponse(back_dic)

前端

<h1>聊天室:{{ name }}</h1>
<input type="text" id="txt">
<button onclick="sendMsg()">提交</button>

<h1>聊天记录</h1>
<div class="record">

</div>

<script>
   function sendMsg() {
        // 朝后端发送消息
       $.ajax({
           url:'/send_msg/',
           type:'post',
           dataType:'JSON',
           data:{'content':$('#txt').val()},
           success:function (args) {

           }
       })
   }

   function getMsg() {
        // 偷偷的朝服务端要数据
        $.ajax({
            url:'/get_msg/',
            type:'get',
            data:{'name':'{{ name }}'},
            success:function (args) {
                if (args.status){
                    // 获取消息 动态渲染到页面上
                    // 1 创建一个p标签
                    var pEle = $('<p>');
                    // 2 给p标签设置文本内容
                    pEle.text(args.msg);
                    // 3 将p标签添加到div内部
                    $('.record').append(pEle)
                }
                getMsg()
            }
        })
   }
   // 页面加载完毕立刻执行
   $(function () {
        getMsg()
   })
</script>

二、websocket(主流浏览器都支持)

"""
网络协议 
	HTTP  不加密传输
	HTTPS  加密传输
		上面两个都是短链接/无链接
	WebSocket  加密传输
		浏览器和服务端创建链接之后默认不断开(联想网络编程TCP recv和send方法)
		它的诞生能够真正的实现服务端给客户端推送消息
"""

内部原理

"""
websocket实现原理可以分为两部分
	1.握手环节(handshake):并不是所有的服务端都支持websocket 所以用握手环节来验证服务端是否支持websocket
	2.收发数据环节:数据解密
"""

"""
1.握手环节
	浏览器访问服务端之后浏览器会立刻生成一个随机字符串
	浏览器会将生成好的随机字符串发送给服务端(基于HTTP协议 放在请求头中),并且自己也保留一份
	
	服务端和客户端都会对该随机字符串做以下处理
	1.1 先拿随机字符串跟magic string(固定的字符串)做字符串的拼接
	1.2 将拼接之后的结果做加密处理(sha1+base64)
	
	服务端将生成好的处理结果发送给浏览器(基于HTTP协议 放在响应头中)
	浏览器接受服务端发送过来的随机字符串跟本地处理好的随机字符串做比对,如果一致说明服务端支持websocket,如果不一致说明不支持

2.收发数据环节
	前提知识点:
		1.基于网络传输数据都是二进制格式 在python中可以用bytes类型对应
		2.进制换算
	
	先读取第二个字节的后七位数据(payload) 根据payload做不同的处理
	=127:继续往后读取8个字节数据(数据报10个字节)
	=126:继续往后读取2个字节数据(数据报4个字节)
	<=125:不再往后读取(数据2个字节)
	
	上述操作完成后,会继续往后读取固定长度4个字节的数据(masking-key)
	依据masking-key解析出真实数据
"""
# 关键字:magic string、sha1/base64、payload(127,126,125)、masking-key

使用

"""
后端框架
django
	默认不支持websocket
	第三方模块:channels

flask
	默认不支持websocket
	第三方模块:geventwebsocket
	
tornado
	默认支持websocket
"""

Django下载

# 下载channels模块需要注意的点
# 1.版本不要用最新版 推荐使用2.3版本即可 如果你安装最新版可能会出现自动将你本地的django版本升级为最新版
# 2.python解释器建议使用3.6版本(3.5可能会有问题,3.7可能会有问题 具体说明问题没有给解释)
pip install channels==2.3.0
"""channels模块内部帮你封装了握手/加密/解密等所有操作"""

Django使用

  • 注册app

    INSTALLED_APPS = [
        'channels'
    ]
    

    注册完成后,django会无法启动,会直接报错

    CommandError: You have not set ASGI_APPLICATION, which is needed to run the server.

  • 配置

    # 2 配置变量
    ASGI_APPLICATION = 'ceshi.routing.application'
    ASGI_APPLICATION = '项目名同名的文件名.文件夹下py文件名默认就叫routing.该py文件内部的变量名默认就叫application'
    
  • 去项目名同名的文件夹下面新建一个py文件,定义application变量

    from channels.routing import ProtocolTypeRouter,URLRouter
    
    
    application = ProtocolTypeRouter({
        'websocket':URLRouter([
            # 书写websocket路由与视图函数对应关系
        ])
    })
    

上述操作配置完成后,启动django会由原来的wsgiref启动变成asgi启动(内部:达芙妮)

并且启动之后django即支持websocket也支持http协议

基于http的操作还是在urls.py和views.py中完成

基于websocket的操作则在routing.py和consumer.py(对应的应用中创建)中完成

posted @ 2020-04-13 18:12  Jeff的技术栈  阅读(354)  评论(0编辑  收藏  举报
回顶部