Python通过websocket与js客户端通信示例分析


Posted in Python onJune 25, 2014

具体的 websocket 介绍可见 http://zh.wikipedia.org/wiki/WebSocket 

这里,介绍如何使用 Python 与前端 js 进行通信。

websocket 使用 HTTP 协议完成握手之后,不通过 HTTP 直接进行 websocket 通信。

于是,使用 websocket 大致两个步骤:使用 HTTP 握手,通信。

js 处理 websocket 要使用 ws 模块; Python 处理则使用 socket 模块建立 TCP 连接即可,比一般的 socket ,只多一个握手以及数据处理的步骤。

握手

过程

Python通过websocket与js客户端通信示例分析

包格式

js 客户端先向服务器端 python 发送握手包,格式如下:

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

其中, Sec-WebSocket-Key 是随机的,服务器用这些数据构造一个 SHA-1 信息摘要。

方法为: key+migic , SHA-1  加密, base-64 加密,如下:

Python通过websocket与js客户端通信示例分析

Python 中的处理代码

MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
res_key = base64.b64encode(hashlib.sha1(sec_key + MAGIC_STRING).digest())

握手完整代码

js 端

js 中有处理 websocket 的类,初始化后自动发送握手包,如下:

var socket = new WebSocket('ws://localhost:3368');

Python 端

Python 用 socket 接受得到握手字符串,处理后发送

HOST = 'localhost'
PORT = 3368
MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
HANDSHAKE_STRING = "HTTP/1.1 101 Switching Protocols\r\n" \
      "Upgrade:websocket\r\n" \
      "Connection: Upgrade\r\n" \
      "Sec-WebSocket-Accept: {1}\r\n" \
      "WebSocket-Location: ws://{2}/chat\r\n" \
      "WebSocket-Protocol:chat\r\n\r\n"
 
def handshake(con):
#con为用socket,accept()得到的socket
#这里省略监听,accept的代码,具体可见blog:http://blog.csdn.net/ice110956/article/details/29830627
 headers = {}
 shake = con.recv(1024)
 
 if not len(shake):
  return False
 
 header, data = shake.split('\r\n\r\n', 1)
 for line in header.split('\r\n')[1:]:
  key, val = line.split(': ', 1)
  headers[key] = val
 
 if 'Sec-WebSocket-Key' not in headers:
  print ('This socket is not websocket, client close.')
  con.close()
  return False
 
 sec_key = headers['Sec-WebSocket-Key']
 res_key = base64.b64encode(hashlib.sha1(sec_key + MAGIC_STRING).digest())
 
 str_handshake = HANDSHAKE_STRING.replace('{1}', res_key).replace('{2}', HOST + ':' + str(PORT))
 print str_handshake
 con.send(str_handshake)
return True

通信

不同版本的浏览器定义的数据帧格式不同, Python 发送和接收时都要处理得到符合格式的数据包,才能通信。

Python 接收

Python 接收到浏览器发来的数据,要解析后才能得到其中的有用数据。

浏览器包格式

Python通过websocket与js客户端通信示例分析

固定字节:

( 1000 0001 或是 1000 0002 )这里没用,忽略

包长度字节:

第一位肯定是 1 ,忽略。剩下 7 个位可以得到一个整数 (0 ~ 127) ,其中

( 1-125 )表此字节为长度字节,大小即为长度;

(126)表接下来的两个字节才是长度;

(127)表接下来的八个字节才是长度;

用这种变长的方式表示数据长度,节省数据位。

mark 掩码:

mark 掩码为包长之后的 4 个字节,之后的兄弟数据要与 mark 掩码做运算才能得到真实的数据。

兄弟数据:

得到真实数据的方法:将兄弟数据的每一位 x ,和掩码的第 i%4 位做 xor 运算,其中 i 是 x 在兄弟数据中的索引。

完整代码

def recv_data(self, num):
 try:
  all_data = self.con.recv(num)
  if not len(all_data):
   return False
 except:
  return False
 else:
  code_len = ord(all_data[1]) & 127
  if code_len == 126:
   masks = all_data[4:8]
   data = all_data[8:]
  elif code_len == 127:
   masks = all_data[10:14]
   data = all_data[14:]
  else:
   masks = all_data[2:6]
   data = all_data[6:]
  raw_str = ""
  i = 0
  for d in data:
   raw_str += chr(ord(d) ^ ord(masks[i % 4]))
   i += 1
  return raw_str

js 端的 ws 对象,通过 ws.send(str) 即可发送

ws.send(str)

Python 发送

Python 要包数据发送,也需要处理,发送包格式如下

Python通过websocket与js客户端通信示例分析

固定字节:固定的 1000 0001( ‘ \x81 ′ )

包长:根据发送数据长度是否超过 125 , 0xFFFF(65535) 来生成 1 个或 3 个或 9 个字节,来代表数据长度。

def send_data(self, data):
 if data:
  data = str(data)
 else:
  return False
 token = "\x81"
 length = len(data)
 if length < 126:
  token += struct.pack("B", length)
 elif length <= 0xFFFF:
  token += struct.pack("!BH", 126, length)
 else:
  token += struct.pack("!BQ", 127, length)
 #struct为Python中处理二进制数的模块,二进制流为C,或网络流的形式。
 data = '%s%s' % (token, data)
 self.con.send(data)
 return True

js 端通过回调函数 ws.onmessage() 接受数据

ws.onmessage = function(result,nTime){
alert("从服务端收到的数据:");
alert("最近一次发送数据到现在接收一共使用时间:" + nTime);
console.log(result);
}

最终代码

Python服务端

# _*_ coding:utf-8 _*_
__author__ = 'Patrick'

import socket
import threading
import sys
import os
import MySQLdb
import base64
import hashlib
import struct
 
# ====== config ======
HOST = 'localhost'
PORT = 3368
MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
HANDSHAKE_STRING = "HTTP/1.1 101 Switching Protocols\r\n" \
      "Upgrade:websocket\r\n" \
      "Connection: Upgrade\r\n" \
      "Sec-WebSocket-Accept: {1}\r\n" \
      "WebSocket-Location: ws://{2}/chat\r\n" \
      "WebSocket-Protocol:chat\r\n\r\n"
 
class Th(threading.Thread):
 def __init__(self, connection,):
  threading.Thread.__init__(self)
  self.con = connection
 
 def run(self):
  while True:
   try:
     pass
  self.con.close()
 
 def recv_data(self, num):
  try:
   all_data = self.con.recv(num)
   if not len(all_data):
    return False
  except:
   return False
  else:
   code_len = ord(all_data[1]) & 127
   if code_len == 126:
    masks = all_data[4:8]
    data = all_data[8:]
   elif code_len == 127:
    masks = all_data[10:14]
    data = all_data[14:]
   else:
    masks = all_data[2:6]
    data = all_data[6:]
   raw_str = ""
   i = 0
   for d in data:
    raw_str += chr(ord(d) ^ ord(masks[i % 4]))
    i += 1
   return raw_str
 
 # send data
 def send_data(self, data):
  if data:
   data = str(data)
  else:
   return False
  token = "\x81"
  length = len(data)
  if length < 126:
   token += struct.pack("B", length)
  elif length <= 0xFFFF:
   token += struct.pack("!BH", 126, length)
  else:
   token += struct.pack("!BQ", 127, length)
  #struct为Python中处理二进制数的模块,二进制流为C,或网络流的形式。
  data = '%s%s' % (token, data)
  self.con.send(data)
  return True
 
 
 # handshake
 def handshake(con):
  headers = {}
  shake = con.recv(1024)
 
  if not len(shake):
   return False
 
  header, data = shake.split('\r\n\r\n', 1)
  for line in header.split('\r\n')[1:]:
   key, val = line.split(': ', 1)
   headers[key] = val
 
  if 'Sec-WebSocket-Key' not in headers:
   print ('This socket is not websocket, client close.')
   con.close()
   return False
 
  sec_key = headers['Sec-WebSocket-Key']
  res_key = base64.b64encode(hashlib.sha1(sec_key + MAGIC_STRING).digest())
 
  str_handshake = HANDSHAKE_STRING.replace('{1}', res_key).replace('{2}', HOST + ':' + str(PORT))
  print str_handshake
  con.send(str_handshake)
  return True
 
def new_service():
 """start a service socket and listen
 when coms a connection, start a new thread to handle it"""
 
 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 try:
  sock.bind(('localhost', 3368))
  sock.listen(1000)
  #链接队列大小
  print "bind 3368,ready to use"
 except:
  print("Server is already running,quit")
  sys.exit()
 
 while True:
  connection, address = sock.accept()
  #返回元组(socket,add),accept调用时会进入waite状态
  print "Got connection from ", address
  if handshake(connection):
   print "handshake success"
   try:
    t = Th(connection, layout)
    t.start()
    print 'new thread for client ...'
   except:
    print 'start new thread error'
    connection.close()
 
 
if __name__ == '__main__':
 new_service()

js客户 端

<script>
var socket = new WebSocket('ws://localhost:3368');
ws.onmessage = function(result,nTime){
alert("从服务端收到的数据:");
alert("最近一次发送数据到现在接收一共使用时间:" + nTime);
console.log(result);
}
</script>
Python 相关文章推荐
利用打码兔和超人打码自封装的打码类分享
Mar 16 Python
编写Python脚本来获取mp3文件tag信息的教程
May 04 Python
在Lighttpd服务器中运行Django应用的方法
Jul 22 Python
python模块之re正则表达式详解
Feb 03 Python
速记Python布尔值
Nov 09 Python
python 画三维图像 曲面图和散点图的示例
Dec 29 Python
python 计算一个字符串中所有数字的和实例
Jun 11 Python
Python通过Tesseract库实现文字识别
Mar 05 Python
520使用Python实现“我爱你”表白
May 20 Python
安装pyecharts1.8.0版本后导入pyecharts模块绘图时报错: “所有图表类型将在 v1.9.0 版本开始强制使用 ChartItem 进行数据项配置 ”的解决方法
Aug 18 Python
[原创]赚疯了!转手立赚800+?大佬的python「抢茅台脚本」使用教程
Jan 12 Python
Python实现老照片修复之上色小技巧
Oct 16 Python
Flask框架学习笔记(一)安装篇(windows安装与centos安装)
Jun 25 #Python
Python中文编码那些事
Jun 25 #Python
教你如何在Django 1.6中正确使用 Signal
Jun 22 #Python
python抓取网页时字符集转换问题处理方案分享
Jun 19 #Python
python在linux中输出带颜色的文字的方法
Jun 19 #Python
解决windows下Sublime Text 2 运行 PyQt 不显示的方法分享
Jun 18 #Python
win7 下搭建sublime的python开发环境的配置方法
Jun 18 #Python
You might like
PHP错误抑制符(@)导致引用传参失败Bug的分析
2011/05/02 PHP
PHP array操作10个小技巧分享
2011/06/23 PHP
PHP根据IP判断地区名信息的示例代码
2014/03/03 PHP
PHP实现图的邻接矩阵表示及几种简单遍历算法分析
2017/11/24 PHP
PHP实现微信退款的方法示例
2019/03/26 PHP
window.open()弹出居中的窗口
2007/02/01 Javascript
JavaScript 在网页上单击鼠标的地方显示层及关闭层
2012/12/30 Javascript
JavaScrip实现PHP print_r的数功能(三种方法)
2013/11/12 Javascript
jquery获取当前日期的方法
2015/01/14 Javascript
JavaScript的内存释放问题详解
2015/01/21 Javascript
JavaScript Window浏览器对象模型方法与属性汇总
2015/04/20 Javascript
jQuery获取URL请求参数的方法
2015/07/18 Javascript
SpringMVC restful 注解之@RequestBody进行json与object转换
2015/12/10 Javascript
Bootstrap 下拉多选框插件Bootstrap Multiselect
2017/01/22 Javascript
Bootstrap3多级下拉菜单
2017/02/24 Javascript
Vuex之理解Mutations的用法实例
2017/04/19 Javascript
使用Xcache缓存器加速PHP网站的配置方法
2017/04/22 Javascript
JavaScript实现单击网页任意位置打开新窗口与关闭窗口的方法
2017/09/21 Javascript
javascript实现拼图游戏
2021/01/29 Javascript
[02:23]2016国际邀请赛中国区预选赛wings晋级之路
2016/06/29 DOTA
Python程序中用csv模块来操作csv文件的基本使用教程
2016/03/03 Python
使用python和pygame绘制繁花曲线的方法
2018/02/24 Python
python批量替换多文件字符串问题详解
2018/04/22 Python
深入理解Django自定义信号(signals)
2018/10/15 Python
Python Numpy:找到list中的np.nan值方法
2018/10/30 Python
Python2和Python3中urllib库中urlencode的使用注意事项
2018/11/26 Python
使用Html5多媒体实现微信语音功能
2019/07/26 HTML / CSS
JAVA和C++区别都有哪些
2015/03/30 面试题
户外活动策划方案
2014/03/12 职场文书
个人自我鉴定总结
2014/03/25 职场文书
商场促销活动策划方案
2014/08/18 职场文书
干部竞争上岗演讲稿
2014/09/11 职场文书
公司法定代表人授权委托书
2014/09/29 职场文书
房地产工程部经理岗位职责
2015/04/09 职场文书
Ubuntu安装Mysql+启用远程连接的完整过程
2022/06/21 Servers
类和原型的设计模式之复制与委托差异
2022/07/07 Javascript