Python实现websocket

Python实现websocket

一、 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”)

完整客户端代码如下:

<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 ) 一般也不会使用

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

发表评论

邮箱地址不会被公开。

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据