介紹
一般互聯網公司都會有一套自己的代碼發布系統,並且大部分的代碼發布都是用運維工具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