python 实现 websocket
一、websocket概要:
websocket是基于TCP传输层协议实现的一种标准协议(关于网络协议,可以看看文末的图片),用于在客户端和服务端双向传输数据
传统的客户端想要知道服务端处理进度有两个途径:
1)通过ajax不断轮询,由于http的无状态性,每次轮询服务器都需要去解析http协议,对服务器压力也很大
2)采用long poll的方式,服务端不给客户端反馈,客户端就一直等待,服务就一直被挂起,此阶段一直是阻塞状态
而当服务器完成升级(http-->websocket)后,上面两个问题就得到解决了:
1)被动性,升级后,服务端可以主动推送消息给客户端,解决了轮询造成的同步延迟问题
2)升级后,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")
完整客户端代码如下:
<script type="text/javascript">
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", "<p>"+message.data+"</p>");
}
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()
}
</script>
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)一般也不会使用
2)创建websocket客户端
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
完成上述代码,分别打开两个终端,运行WebSocket_Server和WebSocket_Client代码,控制台交互结果如下,可以看到已经成功实现了客户端和服务端的通信:
3)如何监听浏览发送过来的socket连接请求?修改上面的WebSocket_Client如下:
class WebSocket_Client():
def __init__(self, conn, connId):
self.connection = conn
self.connId= connId
super(WebSocket_Client, self).__init__()
def run():
print("new socket joined")
# 前端传递过来的请求连接数据
data = self.connection.recv(1024)
try:
headers = self.parse_header(data)
token = self.generate_token(headers["Sec-WebSocket-Key"]).decode("utf8")
response ="HTTP/1.1 101 Switching Protocols\r\n" \
"Connection:Upgrade\r\n" \
"Upgrade:websocket\r\n" \
"Sec-WebSocket-Accept:{0}\r\n" \
"WebSocket-Protocol:chat\r\n\r\n".format(token)
# 通知前端已经成功建立连接
self.connection.send(response.encode("utf8"))
except socket.error as e:
print("unexpect error:", e)
clients.pop(self.connId)
while True:
# 启动 Socket 并监听连接,使用socket中的同名方法,创建TCP服务对象
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # socket.AF_INET是指使用哪种IP地址,socket.AF_INET即使用IPv4,如果是socket.AF_INET6的话,则使用IPv6;socket.SOCKET_STREAM是指使用的套接字类型,在这里我们使用流式套接字。
try:
sock.bind(("127.0.0.1",8000))
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.listen(5)
except Exception as e:
logging.error(e)
return
else:
logging.info(Server running...) # 等待访问
while True:
conn, addr = sock.accept() #此时会进入waiting状态
data = str(conn.recv(1024))
logging.debug(data) header_dict = {}
header, _ = data.split(r"\r\n\r\n", 1)
for line in header.split(r"\r\n")[1:]:
key, val = line.split(":",1)
header_dict[key] = val if "Sec-WebSocket-Key" not in header_dict:
logging.error(this socket is not websocket, connection closed...)
conn.close()
return magic_key = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
sec_key = header_dic["Sec-WebSocket-Key"]+magic_key
key = base64.b64encode(hashlib.sha1(bytes(sec_key, encoding="utf-8")).digest())
key_str = str(key)[2:30]
logging.debug(key_str) response = "HTTP/1.1 101 Switching Protocols\r\n"\
"Connection:Upgrade\r\n"\
"Upgrade:websocket\r\n"\
"Sec-WebSocket-Accept:{0}\r\n"\
"WebSocket-Protocol:chat\r\n\r\n".format(key_str)
conn.send(bytes(response),encoding="utf-8")
logging.debug("send the handshake data")
WebsocketThread(conn).start()
3)进行通信:
Sever端接收到client发来的报文进行解析
下面是软件通信的七层结构:
下三层结构偏向于数据通信,上三层更偏向于数据处理,中间的传输层是上三层与下三层之间的连接桥梁,每一层做不同的工作,上层协议依赖于下层协议,上层协议需要调用下层协议的接口,下层协议使用上层协议的参数,分工合作。
socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。属于传输控制层协议,websocket是一个典型的应用层协议。
参考资料:https://www.cnblogs.com/JetpropelledSnake/p/9033064.html
https://www.cnblogs.com/lichmama/p/3931212.html
python 实现 websocket的更多相关文章
- python实现websocket服务器,可以在web实时显示远程服务器日志
一.开始的话 使用python简单的实现websocket服务器,可以在浏览器上实时显示远程服务器的日志信息. 之前做了一个web版的发布系统,但没实现在线看日志,每次发布版本后,都需要登录到服务器上 ...
- python 实现websocket
python中websocket需要我们自己实现握手代码,流程是这样:服务端启动websocket服务,并监听.当客户端连接过来时,(需要我们自己实现)服务端就接收客户端的请求数据,拿到请求头,根据请 ...
- HTTPS请求HTTP接口被浏览器阻塞,python实现websocket客户端,websocket服务器,跨域问题,dwebsocket,https,拦截,服务端
HTTPS请求HTTP接口被浏览器阻塞,python实现websocket客户端,websocket服务器,跨域问题,dwebsocket,https,拦截,服务端 发表时间:2020-03-05 1 ...
- 利用python对websocket进行并发压测
简述 产品经理鉴于运营反馈并对程序的websocket长连接保持怀疑的态度,让我对websocket服务器进行压力测试,我内心是拒绝的. 开发思路 查阅websocket的相关资料,查到python的 ...
- Python基于websocket实时通信的实现—GoEasy
Python websocket实时消息推送 在这里我记录一下之前如何实现服务器端与客户端实时通信: 实现步骤如下: 1. 获取GoEasy appkey. 在goeasy官网上注册一个 ...
- python tornado websocket 多聊天室(返回消息给部分连接者)
python tornado 构建多个聊天室, 多个聊天室之间相互独立, 实现服务器端将消息返回给相应的部分客户端! chatHome.py // 服务器端, 渲染主页 --> 聊天室建立web ...
- 基于python的websocket开发,tomcat日志web页面实时打印监控案例
web socket 接收器:webSocket.py 相关依赖 # pip install bottle gevent gevent-websocket argparse from bottle i ...
- 用python实现websocket请求遇到的问题及解决方法。
想要实现python的ws库功能,实时获取对方服务器ws协议返回的数据,查了下百度,用如下流程: ws = create_connection("wss://ws.xxxxxxx.info/ ...
- python 版websocket实现
ubuntu下python2.76 windows python 2.79, chrome37 firefox35通过 代码是在别人(cddn有人提问)基础上改的, 主要改动了parsedata和se ...
- python模拟websocket握手过程中计算sec-websocket-accept
背景 以前,很多网站使用轮询实现推送技术.轮询是在特定的的时间间隔(比如1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给浏览器.轮询的缺点很明显,浏览器需要不断的向服 ...
随机推荐
- [Java.File]如果写 File filesFolder = new File("/") ,在windows系统中,filesFolder 会去找哪个盘符? 答案:程序运行路径的根盘符.
首先这段代码在Unix/Linux系统上会去找根路径,但在Windows系统上会去找C:盘还是D:盘还是其它盘呢? 其实它会去找user.dir所在盘符的根目录,user.dir即用户的当前工作目录, ...
- ActiveMQ相关API
一.Producer 1,发送消息 MessageProducer send(Message message)发送消息到默认目的地,就是创建Producer时指定的目的地. send(Destinat ...
- Diffie-Hellman算法简介
一.DH算法是一种密钥交换协议,它可以让双方在不泄漏密钥的情况下协商出一个密钥来. DH算法基于数学原理,比如小明和小红想要协商一个密钥,可以这么做: . 小明先选一个素数和一个底数,例如,素数p=, ...
- Docs-.NET-C#-指南-语言参考-关键字-值类型:bool
ylbtech-Docs-.NET-C#-指南-语言参考-关键字-值类型:bool 1.返回顶部 1. bool(C# 参考) 2015/07/20 bool 关键字是 System.Boolean ...
- [转]地理投影,常用坐标系详解、WGS84、WGS84 Web墨卡托、WGS84 UTM、北京54坐标系、西安80坐标系、CGCS2000坐标系
转自:http://www.rivermap.cn/docs/show-1829.html 常用坐标系详解 (一)WGS84坐标系 WGS-84坐标系(World Geodetic System一19 ...
- 使用kindeditor获取不到富文本框中的值
获取不到富文本框中的值,网上一搜一堆,但最终没有几个能解决问题的,折腾一番最终解决.注意就是红色代码,加上之后就可以解决问题了. KindEditor.ready(function (K) { var ...
- 【440】Tweet 元素意义
参考:Tweet Object 参考:Geo Objects 参考:User Object Ref: Place data dictionary Tweet Object Attribut ...
- div定位relative和absolute测试1
div里的position定位也是比较常见的,relative是相对定位,absolute是绝对定位.如本文测试:body自带8px的margin,这里不对其进行清空.蓝色的div和红色的div分别设 ...
- 异步I/O、事件驱动、单线程
异步I/O.事件驱动.单线程 nodejs的特点总共有以下几点 异步I/O(非阻塞I/O) 事件驱动 单线程 擅长I/O密集型,不擅长CPU密集型 高并发 下面是一道很经典的面试题,描述了node的整 ...
- Appium移动自动化测试-----(十三)appium API 之其他操作
其它操作针对移动设备上特有的一些操作. 1.熄屏 方法: * lockDevice() 点击电源键熄灭屏幕. 在iOS设备可以设置熄屏一段时间.Android上面不带参数,所以熄屏之后就不会再点亮屏幕 ...