一、websocket

WebSocket协议是基于TCP的一种新的协议。WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符。它实现了浏览器与服务器全双工(full-duplex)通信。其本质是保持TCP连接,在浏览器和服务端通过Socket进行通信。

本文将使用Python编写Socket服务端,一步一步分析请求过程!!!

1. 启动服务端

  1. import socket
  2. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  3. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  4. sock.bind(('127.0.0.1', 8002))
  5. sock.listen(5)
  6. # 等待用户连接
  7. conn, address = sock.accept()
  8. ...
  9. ...
  10. ...

启动Socket服务器后,等待用户【连接】,然后进行收发数据。

2. 客户端连接

  1. <script type="text/javascript">
  2.  
  3. var socket = new WebSocket("ws://127.0.0.1:8002/xxoo");
  4. ...
  5. </script>

当客户端向服务端发送连接请求时,不仅连接还会发送【握手】信息,并等待服务端响应,至此连接才创建成功!

3. 建立连接【握手】

  1. import socket
  2.  
  3. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  4. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  5. sock.bind(('127.0.0.1', 8002))
  6. sock.listen(5)
  7. # 获取客户端socket对象
  8. conn, address = sock.accept()
  9. # 获取客户端的【握手】信息
  10. data = conn.recv(1024)
  11. ...
  12. ...
  13. ...
  14. conn.send('响应【握手】信息')

请求和响应的【握手】信息需要遵循规则:

  • 从请求【握手】信息中提取 Sec-WebSocket-Key
  • 利用magic_string 和 Sec-WebSocket-Key 进行hmac1加密,再进行base64加密
  • 将加密结果响应给客户端

注:magic string为:258EAFA5-E914-47DA-95CA-C5AB0DC85B11

请求【握手】信息为:

  1. GET /chatsocket HTTP/1.1
  2. Host: 127.0.0.1:8002
  3. Connection: Upgrade
  4. Pragma: no-cache
  5. Cache-Control: no-cache
  6. Upgrade: websocket
  7. Origin: http://localhost:63342
  8. Sec-WebSocket-Version: 13
  9. Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg==
  10. Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
  11. ...
  12. ...

提取Sec-WebSocket-Key值并加密:

  1. import socket
  2. import base64
  3. import hashlib
  4.  
  5. def get_headers(data):
  6. """
  7. 将请求头格式化成字典
  8. :param data:
  9. :return:
  10. """
  11. header_dict = {}
  12. data = str(data, encoding='utf-8')
  13.  
  14. for i in data.split('\r\n'):
  15. print(i)
  16. header, body = data.split('\r\n\r\n', 1)
  17. header_list = header.split('\r\n')
  18. for i in range(0, len(header_list)):
  19. if i == 0:
  20. if len(header_list[i].split(' ')) == 3:
  21. header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
  22. else:
  23. k, v = header_list[i].split(':', 1)
  24. header_dict[k] = v.strip()
  25. return header_dict
  26.  
  27. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  28. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  29. sock.bind(('127.0.0.1', 8002))
  30. sock.listen(5)
  31.  
  32. conn, address = sock.accept()
  33. data = conn.recv(1024)
  34. headers = get_headers(data) # 提取请求头信息
  35. # 对请求头中的sec-websocket-key进行加密
  36. response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
  37. "Upgrade:websocket\r\n" \
  38. "Connection: Upgrade\r\n" \
  39. "Sec-WebSocket-Accept: %s\r\n" \
  40. "WebSocket-Location: ws://%s%s\r\n\r\n"
  41. magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
  42. value = headers['Sec-WebSocket-Key'] + magic_string
  43. ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
  44. response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])
  45. # 响应【握手】信息
  46. conn.send(bytes(response_str, encoding='utf-8'))
  47. ...
  48. ...
  49. ...

4.客户端和服务端收发数据

客户端和服务端传输数据时,需要对数据进行【封包】和【解包】。客户端的JavaScript类库已经封装【封包】和【解包】过程,但Socket服务端需要手动实现。

第一步:获取客户端发送的数据【解包】

  1. info = conn.recv(8096)
  2.  
  3. payload_len = info[1] & 127
  4. if payload_len == 126:
  5. extend_payload_len = info[2:4]
  6. mask = info[4:8]
  7. decoded = info[8:]
  8. elif payload_len == 127:
  9. extend_payload_len = info[2:10]
  10. mask = info[10:14]
  11. decoded = info[14:]
  12. else:
  13. extend_payload_len = None
  14. mask = info[2:6]
  15. decoded = info[6:]
  16.  
  17. bytes_list = bytearray()
  18. for i in range(len(decoded)):
  19. chunk = decoded[i] ^ mask[i % 4]
  20. bytes_list.append(chunk)
  21. body = str(bytes_list, encoding='utf-8')
  22. print(body)

基于Python实现解包过程(未实现长内容)

解包详细过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

第二步:向客户端发送数据【封包】

  1. def send_msg(conn, msg_bytes):
  2. """
  3. WebSocket服务端向客户端发送消息
  4. :param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept()
  5. :param msg_bytes: 向客户端发送的字节
  6. :return:
  7. """
  8. import struct
  9.  
  10. token = b"\x81"
  11. length = len(msg_bytes)
  12. if length < 126:
  13. token += struct.pack("B", length)
  14. elif length <= 0xFFFF:
  15. token += struct.pack("!BH", 126, length)
  16. else:
  17. token += struct.pack("!BQ", 127, length)
  18.  
  19. msg = token + msg_bytes
  20. conn.send(msg)
  21. return True

5. 基于Python实现简单示例

a. 基于Python socket实现的WebSocket服务端:

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. import socket
  4. import base64
  5. import hashlib
  6.  
  7. def get_headers(data):
  8. """
  9. 将请求头格式化成字典
  10. :param data:
  11. :return:
  12. """
  13. header_dict = {}
  14. data = str(data, encoding='utf-8')
  15.  
  16. header, body = data.split('\r\n\r\n', 1)
  17. header_list = header.split('\r\n')
  18. for i in range(0, len(header_list)):
  19. if i == 0:
  20. if len(header_list[i].split(' ')) == 3:
  21. header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
  22. else:
  23. k, v = header_list[i].split(':', 1)
  24. header_dict[k] = v.strip()
  25. return header_dict
  26.  
  27. def send_msg(conn, msg_bytes):
  28. """
  29. WebSocket服务端向客户端发送消息
  30. :param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept()
  31. :param msg_bytes: 向客户端发送的字节
  32. :return:
  33. """
  34. import struct
  35.  
  36. token = b"\x81"
  37. length = len(msg_bytes)
  38. if length < 126:
  39. token += struct.pack("B", length)
  40. elif length <= 0xFFFF:
  41. token += struct.pack("!BH", 126, length)
  42. else:
  43. token += struct.pack("!BQ", 127, length)
  44.  
  45. msg = token + msg_bytes
  46. conn.send(msg)
  47. return True
  48.  
  49. def run():
  50. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  51. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  52. sock.bind(('127.0.0.1', 8003))
  53. sock.listen(5)
  54.  
  55. conn, address = sock.accept()
  56. data = conn.recv(1024)
  57. headers = get_headers(data)
  58. response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
  59. "Upgrade:websocket\r\n" \
  60. "Connection:Upgrade\r\n" \
  61. "Sec-WebSocket-Accept:%s\r\n" \
  62. "WebSocket-Location:ws://%s%s\r\n\r\n"
  63.  
  64. value = headers['Sec-WebSocket-Key'] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
  65. ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
  66. response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])
  67. conn.send(bytes(response_str, encoding='utf-8'))
  68.  
  69. while True:
  70. try:
  71. info = conn.recv(8096)
  72. except Exception as e:
  73. info = None
  74. if not info:
  75. break
  76. payload_len = info[1] & 127
  77. if payload_len == 126:
  78. extend_payload_len = info[2:4]
  79. mask = info[4:8]
  80. decoded = info[8:]
  81. elif payload_len == 127:
  82. extend_payload_len = info[2:10]
  83. mask = info[10:14]
  84. decoded = info[14:]
  85. else:
  86. extend_payload_len = None
  87. mask = info[2:6]
  88. decoded = info[6:]
  89.  
  90. bytes_list = bytearray()
  91. for i in range(len(decoded)):
  92. chunk = decoded[i] ^ mask[i % 4]
  93. bytes_list.append(chunk)
  94. body = str(bytes_list, encoding='utf-8')
  95. send_msg(conn,body.encode('utf-8'))
  96.  
  97. sock.close()
  98.  
  99. if __name__ == '__main__':
  100. run()

b. 利用JavaScript类库实现客户端

  1. <!DOCTYPE html>
  2. <html>
  3. <head lang="en">
  4. <meta charset="UTF-8">
  5. <title></title>
  6. </head>
  7. <body>
  8. <div>
  9. <input type="text" id="txt"/>
  10. <input type="button" id="btn" value="提交" onclick="sendMsg();"/>
  11. <input type="button" id="close" value="关闭连接" onclick="closeConn();"/>
  12. </div>
  13. <div id="content"></div>
  14.  
  15. <script type="text/javascript">
  16. var socket = new WebSocket("ws://127.0.0.1:8003/chatsocket");
  17.  
  18. socket.onopen = function () {
  19. /* 与服务器端连接成功后,自动执行 */
  20.  
  21. var newTag = document.createElement('div');
  22. newTag.innerHTML = "【连接成功】";
  23. document.getElementById('content').appendChild(newTag);
  24. };
  25.  
  26. socket.onmessage = function (event) {
  27. /* 服务器端向客户端发送数据时,自动执行 */
  28. var response = event.data;
  29. var newTag = document.createElement('div');
  30. newTag.innerHTML = response;
  31. document.getElementById('content').appendChild(newTag);
  32. };
  33.  
  34. socket.onclose = function (event) {
  35. /* 服务器端主动断开连接时,自动执行 */
  36. var newTag = document.createElement('div');
  37. newTag.innerHTML = "【关闭连接】";
  38. document.getElementById('content').appendChild(newTag);
  39. };
  40.  
  41. function sendMsg() {
  42. var txt = document.getElementById('txt');
  43. socket.send(txt.value);
  44. txt.value = "";
  45. }
  46. function closeConn() {
  47. socket.close();
  48. var newTag = document.createElement('div');
  49. newTag.innerHTML = "【关闭连接】";
  50. document.getElementById('content').appendChild(newTag);
  51. }
  52.  
  53. </script>
  54. </body>
  55. </html>

6. 基于Tornado框架实现Web聊天室

Tornado是一个支持WebSocket的优秀框架,其内部原理正如1~5步骤描述,当然Tornado内部封装功能更加完整。

以下是基于Tornado实现的聊天室示例:

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. import uuid
  4. import json
  5. import tornado.ioloop
  6. import tornado.web
  7. import tornado.websocket
  8.  
  9. class IndexHandler(tornado.web.RequestHandler):
  10. def get(self):
  11. self.render('index.html')
  12.  
  13. class ChatHandler(tornado.websocket.WebSocketHandler):
  14. # 用户存储当前聊天室用户
  15. waiters = set()
  16. # 用于存储历时消息
  17. messages = []
  18.  
  19. def open(self):
  20. """
  21. 客户端连接成功时,自动执行
  22. :return:
  23. """
  24. ChatHandler.waiters.add(self)
  25. uid = str(uuid.uuid4())
  26. self.write_message(uid)
  27.  
  28. for msg in ChatHandler.messages:
  29. content = self.render_string('message.html', **msg)
  30. self.write_message(content)
  31.  
  32. def on_message(self, message):
  33. """
  34. 客户端连发送消息时,自动执行
  35. :param message:
  36. :return:
  37. """
  38. msg = json.loads(message)
  39. ChatHandler.messages.append(message)
  40.  
  41. for client in ChatHandler.waiters:
  42. content = client.render_string('message.html', **msg)
  43. client.write_message(content)
  44.  
  45. def on_close(self):
  46. """
  47. 客户端关闭连接时,,自动执行
  48. :return:
  49. """
  50. ChatHandler.waiters.remove(self)
  51.  
  52. def run():
  53. settings = {
  54. 'template_path': 'templates',
  55. 'static_path': 'static',
  56. }
  57. application = tornado.web.Application([
  58. (r"/", IndexHandler),
  59. (r"/chat", ChatHandler),
  60. ], **settings)
  61. application.listen(8888)
  62. tornado.ioloop.IOLoop.instance().start()
  63.  
  64. if __name__ == "__main__":
  65. run()

app.py

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Python聊天室</title>
  6. </head>
  7. <body>
  8. <div>
  9. <input type="text" id="txt"/>
  10. <input type="button" id="btn" value="提交" onclick="sendMsg();"/>
  11. <input type="button" id="close" value="关闭连接" onclick="closeConn();"/>
  12. </div>
  13. <div id="container" style="border: 1px solid #dddddd;margin: 20px;min-height: 500px;">
  14.  
  15. </div>
  16.  
  17. <script src="/static/jquery-2.1.4.min.js"></script>
  18. <script type="text/javascript">
  19. $(function () {
  20. wsUpdater.start();
  21. });
  22.  
  23. var wsUpdater = {
  24. socket: null,
  25. uid: null,
  26. start: function() {
  27. var url = "ws://127.0.0.1:8888/chat";
  28. wsUpdater.socket = new WebSocket(url);
  29. wsUpdater.socket.onmessage = function(event) {
  30. console.log(event);
  31. if(wsUpdater.uid){
  32. wsUpdater.showMessage(event.data);
  33. }else{
  34. wsUpdater.uid = event.data;
  35. }
  36. }
  37. },
  38. showMessage: function(content) {
  39. $('#container').append(content);
  40. }
  41. };
  42.  
  43. function sendMsg() {
  44. var msg = {
  45. uid: wsUpdater.uid,
  46. message: $("#txt").val()
  47. };
  48. wsUpdater.socket.send(JSON.stringify(msg));
  49. }
  50.  
  51. </script>
  52.  
  53. </body>
  54. </html>

index.html

示例源码下载

python之websocket的更多相关文章

  1. python实现websocket服务器,可以在web实时显示远程服务器日志

    一.开始的话 使用python简单的实现websocket服务器,可以在浏览器上实时显示远程服务器的日志信息. 之前做了一个web版的发布系统,但没实现在线看日志,每次发布版本后,都需要登录到服务器上 ...

  2. python 实现websocket

    python中websocket需要我们自己实现握手代码,流程是这样:服务端启动websocket服务,并监听.当客户端连接过来时,(需要我们自己实现)服务端就接收客户端的请求数据,拿到请求头,根据请 ...

  3. HTTPS请求HTTP接口被浏览器阻塞,python实现websocket客户端,websocket服务器,跨域问题,dwebsocket,https,拦截,服务端

    HTTPS请求HTTP接口被浏览器阻塞,python实现websocket客户端,websocket服务器,跨域问题,dwebsocket,https,拦截,服务端 发表时间:2020-03-05 1 ...

  4. 利用python对websocket进行并发压测

    简述 产品经理鉴于运营反馈并对程序的websocket长连接保持怀疑的态度,让我对websocket服务器进行压力测试,我内心是拒绝的. 开发思路 查阅websocket的相关资料,查到python的 ...

  5. Python基于websocket实时通信的实现—GoEasy

    Python websocket实时消息推送 在这里我记录一下之前如何实现服务器端与客户端实时通信: 实现步骤如下: 1.        获取GoEasy appkey. 在goeasy官网上注册一个 ...

  6. python tornado websocket 多聊天室(返回消息给部分连接者)

    python tornado 构建多个聊天室, 多个聊天室之间相互独立, 实现服务器端将消息返回给相应的部分客户端! chatHome.py // 服务器端, 渲染主页 --> 聊天室建立web ...

  7. 基于python的websocket开发,tomcat日志web页面实时打印监控案例

    web socket 接收器:webSocket.py 相关依赖 # pip install bottle gevent gevent-websocket argparse from bottle i ...

  8. 用python实现websocket请求遇到的问题及解决方法。

    想要实现python的ws库功能,实时获取对方服务器ws协议返回的数据,查了下百度,用如下流程: ws = create_connection("wss://ws.xxxxxxx.info/ ...

  9. python 版websocket实现

    ubuntu下python2.76 windows python 2.79, chrome37 firefox35通过 代码是在别人(cddn有人提问)基础上改的, 主要改动了parsedata和se ...

  10. python模拟websocket握手过程中计算sec-websocket-accept

    背景 以前,很多网站使用轮询实现推送技术.轮询是在特定的的时间间隔(比如1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给浏览器.轮询的缺点很明显,浏览器需要不断的向服 ...

随机推荐

  1. float元素一定要闭合

    float:left; float:right; 一定要两个元素一起放float

  2. less-!important关键字

    //!important关键字 使用!important关键字混入调用之后,以标记它继承的所有属性!important,example: .test{ background:red; font-siz ...

  3. mysql delete 多表

    DELETE u.*,acu.*,alu.* FROM user u, accountuser acu ,algouser alu WHERE u.userId=11 and acu.userID=1 ...

  4. Learning PHP Design Patterns

    Learning PHP Design Patterns CHAPTER 1 Algorithms handle speed of operations, and design patterns ha ...

  5. Storm-源码分析-Topology Submit-Executor

    在worker中通过executor/mk-executor worker e, 创建每个executor (defn mk-executor [worker executor-id] (let [e ...

  6. django 表单系统 之 forms.Form

    继承forms.Form实现django表单系统 参考: https://www.cnblogs.com/zongfa/p/7709639.html https://www.cnblogs.com/c ...

  7. Hibernate缓存原理

    对于Hibernate这类ORM而言,缓存显的尤为重要,它是持久层性能提升的关键. 简单来讲Hibernate就是对JDBC进行封装,以实现内部状态的管理,OR关系的映射等, 但随之带来的就是数据访问 ...

  8. Django 之基础学习

     阅读目录 配置 视图层之路由系统配置 模版层 模版过滤器 request & response Ajax Cookie Session 分页 文件传输 Django MTV模型 Django ...

  9. Django中间件(含Django运行周期流程图)

    Django中的中间件(含Django完整生命周期图) Django中间件简介 django 中的中间件(middleware),在django中,中间件其实就是一个类,在请求到来和结束后,djang ...

  10. Delphi 正则表达式语法(6): 贪婪匹配与非贪婪匹配

    Delphi 正则表达式语法(6): 贪婪匹配与非贪婪匹配 //贪婪匹配 var   reg: TPerlRegEx; begin   reg := TPerlRegEx.Create(nil);   ...