Title

代码发布系统一

代码发布系统一

介绍

一般互联网公司都会有一套自己的代码发布系统,并且大部分的代码发布都是用运维工具jenkins(shell脚本)、其实也有公司自己定制自己的代码发布系统(saltstack、Java开发脚本、PHP脚本)

hTTP协议的四大特性

1.基于请求响应
一次请求对应一次响应

2.基于TCP/IP作用于应用层之上的协议
回顾:osi七层协议(由下往上):物理层->数据链路层->网络层->传输层->会话层->表示层->应用层

3.无状态
不保留客户端状态,即每一次登陆访问都是按照第一次访问
因此产生了cookie, session, token这些方法来保存状态数据

4.无连接
发送请求和获得回应后链接就消失,不会长久存在,下一次请求和回应时又会出现
长链接:链接会长时间存在(websocket,类似于http协议的大补丁,比如创建一个聊天室的项目就可以使用这个)
               

其中的无连接就算请求响应之后链接就会消失,而不会长久存在,这样就导致服务端无法主动给客户端发送消息

为什么要做到服务端主动给客户端推送消息,该技术点有哪些应用场景

  • 大屏幕投票实时展示

  • 任务的执行流程

  • 群聊功能

  •  

gojs插件

前端插件,可以动态的生成图表、流程图、组织架构图等等

https://gojs.net/latest/index.html

paramiko模块

类似于Xshell远程连接服务器并操作,底层用的是SSH连接

还会封装一个类,让操作更加方便

gitpython模块

通过python代码操作git

还会封装一个类,让操作更加方便

如何实现服务端主动给客户端推送消息的效果?

伪实现

可不可以让客户端浏览器每隔一段时间偷偷的去服务器请求数据

这样能实现效果,但是内部本质还是客户端朝服务端发送消息

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

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

缺点:
消息延迟严重
请求次数多 消耗资源过大
"""
  • 长轮询(兼容性好)

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

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

真实现

  • Websocket

它的诞生真正的实现了服务端主动给客户端推送消息

知识点回顾

ajax操作

异步提交,局部刷新

用它就可以偷偷的朝服务端发送请求

# views.py
from django.shortcuts import render,HttpResponse
import json

def index(request):
   if request.method == 'POST':
       back_dic = {'msg':'hahaha'}
       return HttpResponse(json.dumps(back_dic))
   return render(request,'index.html')

#index.html
<button id="d1">点我</button>
<script>
   $('#d1').click(function () {
       $.ajax({
           url:'',
           type:'POST',
           success:function(args){
               alert(typeof args) # 此时返回的是string
               alert(args) # {"msg": "hahaha"}
          }
      })
  })
</script>

补充知识点:dataType参数和JsonResponse

#index.html
<button id="d1">点我</button>
<script>
   $('#d1').click(function () {
       $.ajax({
           url:'',
           type:'POST',
           dataType:'JSON' # 若添加这个参数
           success:function(args){
               alert(typeof args) # 此时返回的是Object对象
               alert(args) # [object Object]
          }
      })
  })
</script>

若是直接以JsonResponse返回,则要不要dataType参数都一样

# views.py
from django.shortcuts import render
from django.http import JsonResponse

def index(request):
   if request.method == 'POST':
       back_dic = {'msg':'hahaha'}
       return JsonResponse(back_dic)
   return render(request,'index.html')

#index.html
<button id="d1">点我</button>
<script>
   $('#d1').click(function () {
       $.ajax({
           url:'',
           type:'POST',
           success:function(args){
               alert(typeof args) #此时返回的是Object对象
               alert(args) # [object Object]
          }
      })
  })
</script>

后续在写ajax请求的时候建议你加上dataType参数

递归

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

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

校验性组件

forms组件

modelform组件(它是forms组件的加强版本,功能和代码差不多,但是更加的方便)

队列

队列:先进先出

堆栈:先进后出

python内部在内存中帮我们维护了一个队列

import queue


# 创建一个队列
q = queue.Queue()


# 往队列中添加数据
q.put(111)
q.put(222)


# 从队列中取数据
v1 = q.get()
v2 = q.get()
# v3 = q.get() # 没有数据原地阻塞直到有数据
# v4 = q.get_nowait() # 没有数据直接报错
try:
   v5 = q.get(timeout=3)  # 没有数据等待10s再没有就报错 报queue.Empty
except queue.Empty as e:
   pass
print(v1,v2)

实际生产中不会使用上述的消息队列 会使用功能更加的强大的 消息队列 redis kafka rebittMQ

基于ajax与队列其实就可以实现服务端给客户端推送消息的效果

服务端给每一个客户端维护一个队列,然后再浏览器上面通过ajax请求朝对应队列获取数据,没有数据就原地阻塞(pending状态),有就直接拿走渲染即可

群聊:获取群聊中某个人发送的消息,将该消息给每一个队列

简易版群聊功能

后端

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>

给标签绑定事件的方式大致有两种

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

jquery创建标签

<div class="record">
// 1 创建一个p标签
var pEle = $('<p>');
// 2 给p标签设置文本内容
pEle.text(args.msg);
// 3 将p标签添加到div内部
$('.record').append(pEle)

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

代码验证(无需掌握)

# 请求头中的随机字符串
Sec-WebSocket-Key: NlNG/FK/FrQS/RH5Bcy9Gw==
# 响应头
tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
     "Upgrade:websocket\r\n" \
     "Connection: Upgrade\r\n" \
     "Sec-WebSocket-Accept: %s\r\n" \
     "WebSocket-Location: ws://127.0.0.1:8080\r\n\r\n"
response_str = tpl %ac.decode('utf-8')  # 处理到响应头中
import socket
import hashlib
import base64

# 正常的socket代码
sock = socket.socket()  # 默认就是TCP
# 避免mac本重启服务经常报地址被占用的错误
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8080))
sock.listen(5)


conn, address = sock.accept()
data = conn.recv(1024)  # 获取客户端发送的消息
# print(data.decode('utf-8'))

def get_headers(data):
   """
  将请求头格式化成字典
  :param data:
  :return:
  """
   header_dict = {}
   data = str(data, encoding='utf-8')

   header, body = data.split('\r\n\r\n', 1)
   header_list = header.split('\r\n')
   for i in range(0, len(header_list)):
       if i == 0:
           if len(header_list[i].split(' ')) == 3:
               header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
       else:
           k, v = header_list[i].split(':', 1)
           header_dict[k] = v.strip()
   return header_dict

def get_data(info):
   """
  按照websocket解密规则针对不同的数字进行不同的解密处理
  :param info:
  :return:
  """
   payload_len = info[1] & 127
   if payload_len == 126:
       extend_payload_len = info[2:4]
       mask = info[4:8]
       decoded = info[8:]
   elif payload_len == 127:
       extend_payload_len = info[2:10]
       mask = info[10:14]
       decoded = info[14:]
   else:
       extend_payload_len = None
       mask = info[2:6]
       decoded = info[6:]

   bytes_list = bytearray()
   for i in range(len(decoded)):
       chunk = decoded[i] ^ mask[i % 4]
       bytes_list.append(chunk)
   body = str(bytes_list, encoding='utf-8')

   return body


header_dict = get_headers(data)  # 将一大堆请求头转换成字典数据 类似于wsgiref模块
client_random_string = header_dict['Sec-WebSocket-Key']  # 获取浏览器发送过来的随机字符串
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'  # 全球共用的随机字符串 一个都不能写错
value = client_random_string + magic_string  # 拼接
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())  # 加密处理


tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
     "Upgrade:websocket\r\n" \
     "Connection: Upgrade\r\n" \
     "Sec-WebSocket-Accept: %s\r\n" \
     "WebSocket-Location: ws://127.0.0.1:8080\r\n\r\n"
response_str = tpl %ac.decode('utf-8')  # 处理到响应头中


# 基于websocket收发消息
conn.send(bytes(response_str,encoding='utf-8'))

while True:
   data = conn.recv(1024)
   # print(data) # 加密数据 b'\x81\x89\n\x94\xac#\xee)\x0c\xc6\xaf)I\xb6\x80'
   value = get_data(data)
   print(value)
<script>
   var ws = new WebSocket('ws://127.0.0.1:8080/')
   // 这一句话帮你完成了握手环节所有的操作
   // 1 生成随机字符串
   // 2 对字符串做拼接和加码操作
   // 3 接受服务端返回的字符串做比对
</script>

总结:上述代码知识为了诠释websocket内部本质,实际应用直接使用别人封装好的模块即可

实际应用中,并不是所有的后端框架默认都支持websocket协议,如果你想使用的话,可能需要借助于不同的第三方模块

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

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

tornado
默认支持websocket
"""

django如何支持websocket

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

基本使用

  • 注册app

    INSTALLED_APPS = [
       'channels'
    ]

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

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

  • 配置

    # 2 配置变量
    ASGI_APPLICATION = 's13_day01.routing.application'
    ASGI_APPLICATION = '项目名同名的文件名.文件夹下py文件名默认就叫routing.该py文件内部的变量名默认就叫application'
  • 去项目名同名的文件夹下面新建一个名为routing的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(对应的应用中创建)中完成

# routing.py
from channels.routing import ProtocolTypeRouter,URLRouter
from django.conf.urls import url
from app01 import consumer
application = ProtocolTypeRouter({
   'websocket':URLRouter([
       url(r'^index/',consumer.xxx)
  ])
})

 

 

 
posted @ 2020-04-13 22:39  Mr江  阅读(863)  评论(0编辑  收藏  举报