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 相关文章推荐
Python字符串拼接的几种方法整理
Aug 02 Python
Python SQLite3简介
Feb 22 Python
详解Python3.6安装psutil模块和功能简介
May 30 Python
Python从单元素字典中获取key和value的实例
Dec 31 Python
Python中按值来获取指定的键
Mar 04 Python
Python+OpenCV+pyQt5录制双目摄像头视频的实例
Jun 28 Python
Python利用scapy实现ARP欺骗的方法
Jul 23 Python
python实现的批量分析xml标签中各个类别个数功能示例
Dec 30 Python
Python中包的用法及安装
Feb 11 Python
Python列表解析操作实例总结
Feb 26 Python
Requests什么的通通爬不了的Python超强反爬虫方案!
May 20 Python
Elasticsearch 数据类型及管理
Apr 19 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
微信公众平台开发-微信服务器IP接口实例(含源码)
2017/03/05 PHP
用js实现多域名不同文件的调用方法
2007/01/12 Javascript
网易JS面试题与Javascript词法作用域说明
2010/11/09 Javascript
jQuery模拟超链接点击效果代码
2013/04/21 Javascript
模拟一个类似百度google的模糊搜索下拉列表
2014/04/15 Javascript
javascript 控制input只允许输入的各种指定内容
2014/06/19 Javascript
在Node.js中使用HTTP上传文件的方法
2015/06/23 Javascript
写给小白的JavaScript引擎指南
2015/12/04 Javascript
jquery获取img的src值的简单实例
2016/05/17 Javascript
JavaScript lodash常见用法系列小结
2016/08/24 Javascript
jQuery Validate设置onkeyup验证的实例代码
2016/12/09 Javascript
JS基于onclick事件实现单个按钮的编辑与保存功能示例
2017/02/13 Javascript
微信小程序新增的拖动组件movable-view使用教程
2017/05/20 Javascript
Angular 数据请求的实现方法
2018/05/07 Javascript
AngularJS上传文件的示例代码
2018/11/10 Javascript
浅谈Node 异步IO和事件循环
2019/05/05 Javascript
php结合js实现多条件组合查询
2019/05/28 Javascript
Node.js中console.log()输出彩色字体的方法示例
2019/12/01 Javascript
vue2.* element tabs tab-pane 动态加载组件操作
2020/07/19 Javascript
python获得文件创建时间和修改时间的方法
2015/06/30 Python
django实现用户登陆功能详解
2017/12/11 Python
Python实现的堆排序算法示例
2018/04/29 Python
python利用smtplib实现QQ邮箱发送邮件
2020/05/20 Python
python如何实现一个刷网页小程序
2018/11/27 Python
python实现微信每日一句自动发送给喜欢的人
2019/04/29 Python
Django组件cookie与session的具体使用
2019/06/05 Python
Python常用库大全及简要说明
2020/01/17 Python
TensorFLow 数学运算的示例代码
2020/04/21 Python
西班牙著名的珠宝首饰品牌:P D PAOLA
2018/09/15 全球购物
Woods官网:加拿大最古老、最受尊敬的户外品牌之一
2020/09/12 全球购物
学生个人的自我评价分享
2013/11/05 职场文书
岗位廉洁从政承诺书
2014/03/27 职场文书
付款证明模板
2015/06/19 职场文书
2016年端午节红领巾广播稿
2015/12/18 职场文书
使用Nginx搭载rtmp直播服务器的方法
2021/10/16 Servers
ipad隐藏软件app图标方法
2022/04/19 数码科技