一、WebSocket理论部分

1、websocket是什么

Websocket是html5提出的一个协议规范,参考rfc6455。

websocket约定了一个通信的规范,通过一个握手的机制,客户端(浏览器)和服务器(webserver)之间能建立一个类似tcp的连接,从而方便c-s之间的通信。在websocket出现之前,web交互一般是基于http协议的短连接或者长连接。

WebSocket是为解决客户端与服务端实时通信而产生的技术。websocket协议本质上是一个基于tcp的协议,是先通过HTTP/HTTPS协议发起一条特殊的http请求进行握手后创建一个用于交换数据的TCP连接,此后服务端与客户端通过此TCP连接进行实时通信。

注意:此时不再需要原HTTP协议的参与了。

2、websocket的优点

以前web server实现推送技术或者即时通讯,用的都是轮询(polling),在特点的时间间隔(比如1秒钟)由浏览器自动发出请求,将服务器的消息主动的拉回来,在这种情况下,我们需要不断的向服务器发送请求,然而HTTP request 的header是非常长的,里面包含的数据可能只是一个很小的值,这样会占用很多的带宽和服务器资源。

而最比较新的技术去做轮询的效果是Comet – 用了AJAX。但这种技术虽然可达到全双工通信,但依然需要发出请求(reuqest)。

WebSocket API最伟大之处在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息。 浏览器和服务器只需要要做一个握手的动作,在建立连接之后,服务器可以主动传送数据给客户端,客户端也可以随时向服务器发送数据。 此外,服务器与客户端之间交换的标头信息很小。

WebSocket并不限于以Ajax(或XHR)方式通信,因为Ajax技术需要客户端发起请求,而WebSocket服务器和客户端可以彼此相互推送信息;

因此从服务器角度来说,websocket有以下好处:

节省每次请求的header

http的header一般有几十字节

Server Push

服务器可以主动传送数据给客户端

3、websocket的协议规范

1.基于flash的握手协议

使用场景是IE的多数版本,因为IE的多数版本不都不支持WebSocket协议,以及FF、CHROME等浏览器的低版本,还没有原生的支持WebSocket。此处,server唯一要做的,就是准备一个WebSocket-Location域给client,没有加密,可靠性很差。

2.基于md5加密方式的握手协议

其中 Sec-WebSocket-Key1,Sec-WebSocket-Key2 和 [8-byte security key] 这几个头信息是web server用来生成应答信息的来源,依据 draft-hixie-thewebsocketprotocol-76 草案的定义。

web server基于以下的算法来产生正确的应答信息:

1. 逐个字符读取 Sec-WebSocket-Key1 头信息中的值,将数值型字符连接到一起放到一个临时字符串里,同时统计所有空格的数量;

2. 将在第(1)步里生成的数字字符串转换成一个整型数字,然后除以第(1)步里统计出来的空格数量,将得到的浮点数转换成整数型;

3. 将第(2)步里生成的整型值转换为符合网络传输的网络字节数组;

4. 对 Sec-WebSocket-Key2 头信息同样进行第(1)到第(3)步的操作,得到另外一个网络字节数组;

5. 将 [8-byte security key] 和在第(3)、(4)步里生成的网络字节数组合并成一个16字节的数组;

6. 对第(5)步生成的字节数组使用MD5算法生成一个哈希值,这个哈希值就作为安全密钥返回给客户端,以表明服务器端获取了客户端的请求,同意创建websocket连接

3.基于sha加密方式的握手协议

也是目前见的最多的一种方式,这里的版本号目前是需要13以上的版本。

客户端请求:

GET /ls HTTP/1.1

Upgrade: websocket

Connection: Upgrade

Host: www.qixing318.com

Sec-WebSocket-Origin: http://www.qixing318.com

Sec-WebSocket-Key: 2SCVXUeP9cTjV+0mWB8J6A==

Sec-WebSocket-Version: 13

服务器返回:

HTTP/1.1 101 Switching Protocols

Upgrade: websocket Connection:

Upgrade Sec-WebSocket-Accept: mLDKNeBNWz6T9SxU+o0Fy/HgeSw=

其中 server就是把客户端上报的key拼上一段GUID( “258EAFA5-E914-47DA-95CA-C5AB0DC85B11″),拿这个字符串做SHA-1 hash计算,然后再把得到的结果通过base64加密,最后再返回给客户端。

-格式:\r\n

-创建链接之后默认不断开

4、基于sha加密的Opening Handshake(握手环节)

客户端发起连接Handshake请求

GET /chat HTTP/1.1

Host: server.example.com

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

Origin: http://example.com

Sec-WebSocket-Protocol: chat, superchat

Sec-WebSocket-Version: 13

服务器端响应:

HTTP/1.1 101 Switching Protocols

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Sec-WebSocket-Protocol: chat

Upgrade:WebSocket

表示这是一个特殊的 HTTP 请求,请求的目的就是要将客户端和服务器端的通讯协议从 HTTP 协议升级到 WebSocket 协议。

Sec-WebSocket-Key

是一段浏览器base64加密的密钥,server端收到后需要提取Sec-WebSocket-Key 信息,然后加密。

Sec-WebSocket-Accept

服务器端在接收到的Sec-WebSocket-Key密钥后追加一段神奇字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,并将结果进行sha-1哈希,然后再进行base64加密返回给客户端(就是Sec-WebSocket-Key)。 比如:

function encry($req)

{

$key = $this->getKey($req);

$mask = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

# 将 SHA-1 加密后的字符串再进行一次 base64 加密

return base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));

}

如果加密算法错误,客户端在进行校检的时候会直接报错。如果握手成功,则客户端侧会出发onopen事件。

Sec-WebSocket-Protocol

表示客户端请求提供的可供选择的子协议,及服务器端选中的支持的子协议,“Origin”服务器端用于区分未授权的websocket浏览器

Sec-WebSocket-Version: 13

客户端在握手时的请求中携带,这样的版本标识,表示这个是一个升级版本,现在的浏览器都是使用的这个版本。

HTTP/1.1 101 Switching Protocols

101为服务器返回的状态码,所有非101的状态码都表示handshake并未完成。

Data Framing

Websocket协议通过序列化的数据帧传输数据。数据封包协议中定义了opcode、payload length、Payload data等字段。其中要求:

客户端向服务器传输的数据帧必须进行掩码处理:服务器若接收到未经过掩码处理的数据帧,则必须主动关闭连接。

服务器向客户端传输的数据帧一定不能进行掩码处理。客户端若接收到经过掩码处理的数据帧,则必须主动关闭连接。

针对上情况,发现错误的一方可向对方发送close帧(状态码是1002,表示协议错误),以关闭连接。

具体数据帧格式如下图所示:

FIN

标识是否为此消息的最后一个数据包,占 1 bit

RSV1, RSV2, RSV3: 用于扩展协议,一般为0,各占1bit

Opcode

数据包类型(frame type),占4bits

0x0:标识一个中间数据包

0x1:标识一个text类型数据包

0x2:标识一个binary类型数据包

0x3-7:保留

0x8:标识一个断开连接类型数据包

0x9:标识一个ping类型数据包

0xA:表示一个pong类型数据包

0xB-F:保留

MASK:占1bits

用于标识PayloadData是否经过掩码处理。如果是1,Masking-key域的数据即是掩码密钥,用于解码PayloadData。客户端发出的数据帧需要进行掩码处理,所以此位是1。

Payload length

Payload data的长度,占7bits,7+16bits,7+64bits:

如果其值在0-125,则是payload的真实长度。

如果值是126,则后面2个字节形成的16bits无符号整型数的值是payload的真实长度。注意,网络字节序,需要转换。

如果值是127,则后面8个字节形成的64bits无符号整型数的值是payload的真实长度。注意,网络字节序,需要转换。

这里的长度表示遵循一个原则,用最少的字节表示长度(尽量减少不必要的传输)。举例说,payload真实长度是124,在0-125之间,必须用前7位表示;不允许长度1是126或127,然后长度2是124,这样违反原则。

Payload data

应用层数据

server解析client端的数据

接收到客户端数据后的解析规则如下:

1byte

1bit: frame-fin,x0表示该message后续还有frame;x1表示是message的最后一个frame

3bit: 分别是frame-rsv1、frame-rsv2和frame-rsv3,通常都是x0

4bit: frame-opcode,x0表示是延续frame;x1表示文本frame;x2表示二进制frame;x3-7保留给非控制frame;x8表示关 闭连接;x9表示ping;xA表示pong;xB-F保留给控制frame

2byte

1bit: Mask,1表示该frame包含掩码;0表示无掩码

7bit、7bit+2byte、7bit+8byte: 7bit取整数值,若在0-125之间,则是负载数据长度;若是126表示,后两个byte取无符号16位整数值,是负载长度;127表示后8个 byte,取64位无符号整数值,是负载长度

3-6byte: 这里假定负载长度在0-125之间,并且Mask为1,则这4个byte是掩码

7-end byte: 长度是上面取出的负载长度,包括扩展数据和应用数据两部分,通常没有扩展数据;若Mask为1,则此数据需要解码,解码规则为- 1-4byte掩码循环和数据byte做异或操作。

示例代码:

while True:

# 对数据进行解密

# send_msg(conn, bytes('alex', encoding='utf-8'))

# send_msg(conn, bytes('SB', encoding='utf-8'))

# info = conn.recv(8096)

# print(info)

info = conn.recv(8096)

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)

msg = str(bytes_list, encoding='utf-8')

rep = msg + 'sb'

send_msg(conn,bytes(rep,encoding='utf-8'))

5、原理代码:

import socket

import hashlib

import base64

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 send_msg(conn, msg_bytes):

"""

WebSocket服务端向客户端发送消息

:param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept()

:param msg_bytes: 向客户端发送的字节

:return:

"""

import struct

token = b"\x81"

length = len(msg_bytes)

if length<126:token += struct.pack("B",length)

elif length <= 0xFFFF:token += struct.pack("!BH",126, length)

else:

token += struct.pack("!BQ",127, length)

msg= token+ msg_bytes

conn.send(msg)

return True

sock= socket.socket(socket.AF_INET,socket.SOCK_STREAM)

sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

sock.bind(('127.0.0.1', 8002))

sock.listen(5)

# 等待用户连接

conn, address= sock.accept()# WebSocket发来的连接

# 1. 获取握手数据

data= conn.recv(1024)headers= get_headers(data)# 2. 对握手信息进行加密:

magic_string= '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'value= headers['Sec-WebSocket-Key']+ magic_string

ac= base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())# 3. 返回握手信息

response_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:8002\r\n\r\n"

response_str= response_tpl% (ac.decode('utf-8'),)

conn.sendall(bytes(response_str, encoding='utf-8'))# 之后,才能进行首发数据。

while True:

# 对数据进行解密

# send_msg(conn, bytes('alex', encoding='utf-8'))# send_msg(conn, bytes('SB', encoding='utf-8'))# info= conn.recv(8096)# print(info)

info= conn.recv(8096)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= Nonemask= 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)

msg= str(bytes_list,encoding='utf-8')rep= msg+ 'sb'

send_msg(conn,bytes(rep,encoding='utf-8'))

后端

Title

WebSocket协议学习

//向 127.0.0.1:8002 发送一个WebSocket请求

varsocket= newWebSocket("ws://127.0.0.1:8002");

socket.onmessage= function(event) {/*服务器端向客户端发送数据时,自动执行*/

varresponse=event.data;

console.log(response);

};

python requests发送websocket_python之WebSocket协议相关推荐

  1. websocket python爬虫_python实现基于websocket协议的网络爬虫

    WebSocket是一种在单个TCP连接上进行全双工通信的协议,简单来说就是建立一个TCP长连接之后,你可以服务器随时可以给客户端发送消息,客户端随时可以给服务器发送消息,而以前只能是客户端给服务器发 ...

  2. python requests发送websocket_Pywss - 用python实现WebSocket服务端

    一种类似Flask开发的WebSocket-Server服务端框架,适用python3.X 1.安装模块Pywss pip install pywss 2.搭建简易服务器 2.1 服务端代码 代码简介 ...

  3. python requests 发送 上传 多个文件

    目录 1.上传文件 代码实例 2.files参数 字典类型 3.files参数 元组列表类型 1.上传文件 代码实例 在requests中发送文件的接口只有一种,那就是使用requests.post的 ...

  4. python post请求实例_Python使用requests发送POST请求实例代码

    本文研究的主要是Python使用requests发送POST请求的相关内容,具体介绍如下. 一个http请求包括三个部分,为别为请求行,请求报头,消息主体,类似以下这样: 请求行 请求报头 消息主体 ...

  5. python绘制蚊香形_Python requests发送post请求的一些疑点

    前言 在Python爬虫中,使用requests发送请求,访问指定网站,是常见的做法.一般是发送GET请求或者POST请求,对于GET请求没有什么好说的,而发送POST请求,有很多朋友不是很清楚,主要 ...

  6. Python之WebSocket协议

    一.WebSocket理论部分 1.websocket是什么 Websocket是html5提出的一个协议规范,参考rfc6455. websocket约定了一个通信的规范,通过一个握手的机制,客户端 ...

  7. Python 技术篇-requests发送post请求传文件给flask服务端报405、400问题解决方法

    这是我 flask 服务端的一块代码,可以看到有个 methods=['GET', 'POST'],如果没有这个参数,发送请求就会报 405,因为默认能只接收到 get 请求,接收不到 post 请求 ...

  8. 【译】 WebSocket 协议第六章——发送与接收消息(Sending and Receiving Data)

    概述 本文为 WebSocket 协议的第六章,本文翻译的主要内容为 WebSocket 消息发送与接收相关内容. 发送与接收消息(协议正文) 6.1 发送数据 为了通过 WebSocket 连接发送 ...

  9. python requests是什么_如何基于Python + requests实现发送HTTP请求

    这篇文章主要介绍了如何基于Python + requests实现发送HTTP请求,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.在接口自动化测试 ...

最新文章

  1. 决策树-特征属性选择划分
  2. 两个服务之间的调用请求
  3. java xssf的背景色,poi xssf背景颜色对照表 java生成excel文件
  4. 用Matlab实现字符串分割(split)
  5. 从“char []”转换为“LPCWSTR” 指向的类型无关
  6. snmp有android代理端吗,Android实现snmp协议(1)
  7. .NET_.NET 部署问题
  8. Item 22. 模板方法与曲线救国(Template Method)
  9. 量子计算机解ns方程,量子计算机可解方程组
  10. 写给非网工的CCNA教程(3)聊聊ping命令后的原理
  11. 载波为半波三角波的单相三阶SPWM逆变器——谐波分析
  12. 白嫖UltraEdit、UltraCompare等等类似工具(2021.2.16更新)
  13. mac如何共享网络?mac怎么建立wifi热点?
  14. 鸿蒙 3.0 来了!新版本就是强啊!!
  15. 生活污水处理厂工程脱水车间设计、果汁饮料厂工艺流程及车间平面布置CAD设计、水处理车间工艺图、氯乙烯分离车间平面布置图、乳品车间设备布置图、核桃乳饮料厂工艺流程及车间平面布置CAD设计……
  16. ps cc下载地址及破解教程
  17. 19蓝桥国赛B组C/C++ I第八大奇迹
  18. 【Linux】Linux目录
  19. 窥探现代浏览器架构(四)
  20. bzoj4004[JLOI2015]装备购买

热门文章

  1. python中eps是什么意思_如何在中使用参数epsabsscipy.integrate.quad在Python中?
  2. 冒泡和快速排序的时间复杂度_十大经典排序算法——快速排序
  3. 【无标题】如何解决多芯光纤的扇入扇出
  4. 命令行查看Kafka版本,快速docker安装Kafka版本命令
  5. 全国领先,这个云顾问要火
  6. linux IO模式及select、epoll、select详解(附示例)
  7. css3获取最后一个元素,一个CSSselect器来获取最后一个可见的div
  8. 多边形游戏 动态规划C语言实现
  9. python统计每个单词出现的次数
  10. php 获取来源域名方法,PHP实现获取域名的方法小结_PHP