一、 websocket概要:
websocket是基于TCP传输层协议实现的一种标准协议 (关于网络协议, 可以看看文末的图片), 用于在客户端和服务端双向传输数据
传统的客户端想要知道服务端处理进度有两个途径:
1) 通过ajax不断轮询, 由于http的无状态性, 每次轮询服务器都需要去解析http协议, 对服务器压力也很大
2) 采用long poll的方式, 服务端不给客户端反馈, 客户端就一直等待, 服务就一直被挂起, 此阶段一直是阻塞状态, 而当服务器完成升级( http–>websocket )后, 上面两个问题就得到解决了:
被动性, 升级后, 服务端可以主动推送消息给客户端, 解决了轮询造成的同步延迟问题
升级后, websocket只需要一次http握手, 服务端就能一直与客户端保持通信, 直到关闭连接, 这样就解决了服务器需要反复解析http协议, 减少了资源的开销.
二、 websocket通信过程
websocket目前基本主流浏览器都已经支持, IE10以下不支持 .
1、建立连接
在客户端,new Websocket 实例化一个新的WebSocket客户端对象, 连接类似 ws://yourdomain:port/path 的服务端 WebSocket URL, WebSocket 客户端对象会自动解析并识别为 WebSocket 请求, 从而连接服务端接口, 执行双方握手过程, 客户端发送数据格式类似:
1) 客户端请求报文:
GET / HTTP/1.1
Upgrade:websocket #line1
Connection:Upgrade #line2 :与http请求报文比,多了line1和line2这两行,它告诉服务器此次发起的是websocket协议,而不是http协议了,记得要升级哦
Host:example.com
Origin:http://example.com
Sec-WebSocket-Key:sN9cRrP/n9NdMgdcy2VJFQ== # line3:这个是浏览器随机生成的一个base64加密值,提供基本的防护,告诉服务器,我有提供的密码的,我会做验证的,防止恶意或无意的连接
Sec-WebSocket-Version:13 #line4 :告诉服务器使用的websocket版本,如果服务器不支持该版本,会返回一个Sec-WebSocket-Versionheader,里面包含服务器支持的版本号
客户端创建websocket连接
var ws=new websocket (“ws:127.0.0.1:8000”)
完整客户端代码如下:
var ws;
var box = document.getElementById(“box”);
function startWS(){
ws = new websocket(“ws:127.0.0.1:8000”);
ws.onopen = function(msg){
console.log(“websocket opened!”);
}
ws.onomessage = function(message){
console.log(“receive message:”+message.data);
box.insertAdjacentHTML(“beforeend”, “
”+message.data+”
”);}
ws.onerror = function(err){
console.log(“error:”+err.name+err.number);
}
ws.onclose = function(){
console.log(“websocket closed!”)
}
}
function sendMsg(){
console.log(“sending a message…”);
var text = document.getElementById(“text”);
ws.send(text.value);
}
window.onbeforeunload = function(){
ws.onclose = function(){}
ws.close()
}
2) 服务端响应报文:
HTTP/1.1 101 Switching Protocols # 101表示服务端已经理解了客户端的请求,并将通过Upgrade消息头通知客户端采用不同的协议来完成这个请求
Upgrade:websocket
Connection:Upgrade # 这里两行是告诉浏览器,我已经成功切换协议了,协议是websocket
Sec-WebSocket-Accept:HSmrc0sM1YUkAGmm50PpG2HaGwK= #经过服务器确认并加密后的Sec-WebSocket-Key
Sec-WebSocket-Protocol:chat # 表示最终使用的协议,至此http就已经完成全部的工作,接下来就是完全按照websocket协议进行了。
上文的Sec-WebSocket-Accept加密算法:
a)将Sec-WebSocket-Key和258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接
b)通过SHA1计算出摘要, 并转成Base64字符串
如token=base64.b64encode(hashlib.sha1(key+magic_str).encode(“utf8”).degist())
这里在做加密之前, key一定要记得看前后有没有空白, 有的话要记得去空白,
不然加密的结果会一直报错不匹配, 这个坑被坑了很久
注意: 此处的Sec-WebSocket-Key/Sec-WebSocket-Accept的换算, 只能带来基本保障, 但连接是否安全, 数据是否安全, 客户端 服务端是否是合法的ws客户端 ws服务端, 并没有实际保证
Sec-WebSocket-Protocol: 表示最终使用的协议
完整的服务端代码:
1) 创建websocket服务端:
import socket
import threading
global clients
clients = {}
class Websocket_Server(threading.Thread):
def init(self, port):
self.port = port
super(Websocket_Server, self).init()
def run(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((“127.0.0.1”, self.port))
sock.listen(5)
while(True):
# 等待客户端连接
conn, addr = sock.accept()
print(“客户端{}连接成功:”.format(addr))
conn.send((“welcome…”).encode(“utf8”))
while(True):
try:
info = conn.recv(1024)
connId = “ID:”+str(addr[1])
clients[connId] = conn
print(“{0}:{1}”.format(connId, info.decode(“utf8”)))
except Exception as e:
print(e)
msg = input()
conn.send(msg.encode(“utf8”))
if info == b”bye”:
print(“客户端退出”)
conn.close()
break
上面创建了websocket服务端, 通过socket.socket()创建了TCP服务对象
接收两个参数, family和type
family: 有三种:
AF_INET: 即IPV4(默认)
AF_INET6: 即IPV6
AF_UNIX: 只能够用于单一的Unix系统进程间通信
type: 套接字类型:
流套接字(SOCK_STREAM )( 默认 ): 只读取TCP协议的数据, 用于提供面向连接, 可靠的数据传输服务. 该服务可以保证数据可以实现无差错无重复发送, 并按序接收. 之所以能够实现可靠的数据传输, 原因在于使用了传输控制协议(TCP)
数据报套接字( SOCK_DGRAM ): 只读取UDP协议的数据. 提供了一种无连接服务, 该服务并不能保证数据的可靠性. 有可能在数据传输过程中出现数据丢失, 错乱重复等. 由于数据包套接字不能保证数据的可靠性, 对于有可能出现数据丢失的情况
原始套接字( SOCK_RAW ): 原始套接字和标准套接字 (上面两种)的区别是: 原始套接字可以读取内核没有处理的IP数据包. 而流套接字只能读取TCP协议的数据; 数据包套接字只能读取UDP协议的数据
可靠UDP形式:( SOCK_RDM ), 会对数据进行校验, 一般不会使用
可靠的连续数据包:( SOCK_SEQPACKET ) 一般也不会使用
import socket
import threading
class Websocket_Client(threading.Thread):
def init(self):
self.host =”localhost”
self.port = 8000
self.address = (host, port)
self.buffer = 1024
def run():
#创建TCP客户端程序
tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务端
tcp_client.connect(self.address)
while True:
info = tcp_client.recv(self.buffer)
print(“{}”.format(str(info, encoding=”utf8″)))
msg = input()
tcp_client.send(msg.encode(“utf8”))
if info.lower().decode(“utf8″)==”bye”:
tcp_client.close()
break