WebSocket介绍

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

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

1. 启动服务端

import socket
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()
...
...
...

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

2. 客户端连接

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

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

3. 建立连接【握手】

import socketsock = 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)
# 获取客户端socket对象
conn, address = sock.accept()
# 获取客户端的【握手】信息
data = conn.recv(1024)
...
...
...
conn.send('响应【握手】信息')

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

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

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

请求【握手】信息为:

GET /chatsocket HTTP/1.1
Host: 127.0.0.1:8002
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://localhost:63342
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
...
...

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

import socket
import base64
import hashlibdef get_headers(data):"""将请求头格式化成字典:param data::return:"""header_dict = {}data = str(data, encoding='utf-8')for i in data.split('\r\n'):print(i)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_dictsock = 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()
data = conn.recv(1024)
headers = get_headers(data) # 提取请求头信息
# 对请求头中的sec-websocket-key进行加密
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://%s%s\r\n\r\n"
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
value = headers['Sec-WebSocket-Key'] + magic_string
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])
# 响应【握手】信息
conn.send(bytes(response_str, encoding='utf-8'))
...
...
...

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

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

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

   info = conn.recv(8096)payload_len = info[1] & 127if 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)body = str(bytes_list, encoding='utf-8')print(body)
基于Python实现解包过程(未实现长内容)

解包详细过程: 

      0                   1                   2                   30 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 ...                |+---------------------------------------------------------------+

The MASK bit simply tells whether the message is encoded. Messages from the client must be masked, so your server should expect this to be 1. (In fact, section 5.1 of the spec says that your server must disconnect from a client if that client sends an unmasked message.) When sending a frame back to the client, do not mask it and do not set the mask bit. We'll explain masking later. Note: You have to mask messages even when using a secure socket.RSV1-3 can be ignored, they are for extensions.

The opcode field defines how to interpret the payload data: 0x0 for continuation, 0x1 for text (which is always encoded in UTF-8), 0x2 for binary, and other so-called "control codes" that will be discussed later. In this version of WebSockets, 0x3 to 0x7 and 0xB to 0xF have no meaning.

The FIN bit tells whether this is the last message in a series. If it's 0, then the server will keep listening for more parts of the message; otherwise, the server should consider the message delivered. More on this later.

Decoding Payload Length

To read the payload data, you must know when to stop reading. That's why the payload length is important to know. Unfortunately, this is somewhat complicated. To read it, follow these steps:

  1. Read bits 9-15 (inclusive) and interpret that as an unsigned integer. If it's 125 or less, then that's the length; you're done. If it's 126, go to step 2. If it's 127, go to step 3.
  2. Read the next 16 bits and interpret those as an unsigned integer. You're done.
  3. Read the next 64 bits and interpret those as an unsigned integer (The most significant bit MUST be 0). You're done.

Reading and Unmasking the Data

If the MASK bit was set (and it should be, for client-to-server messages), read the next 4 octets (32 bits); this is the masking key. Once the payload length and masking key is decoded, you can go ahead and read that number of bytes from the socket. Let's call the data ENCODED, and the key MASK. To get DECODED, loop through the octets (bytes a.k.a. characters for text data) of ENCODED and XOR the octet with the (i modulo 4)th octet of MASK. In pseudo-code (that happens to be valid JavaScript):

 

var DECODED = "";
for (var i = 0; i < ENCODED.length; i++) {
    DECODED[i] = ENCODED[i] ^ MASK[i % 4];
}

 

Now you can figure out what DECODED means depending on your application.

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

def send_msg(conn, msg_bytes):"""WebSocket服务端向客户端发送消息:param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept():param msg_bytes: 向客户端发送的字节:return: """import structtoken = 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_bytesconn.send(msg)return True
View Code

5. 基于Python实现简单示例

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

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import base64
import hashlibdef 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_dictdef send_msg(conn, msg_bytes):"""WebSocket服务端向客户端发送消息:param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept():param msg_bytes: 向客户端发送的字节:return: """import structtoken = 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_bytesconn.send(msg)return Truedef run():sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)sock.bind(('127.0.0.1', 8003))sock.listen(5)conn, address = sock.accept()data = conn.recv(1024)headers = get_headers(data)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://%s%s\r\n\r\n"value = headers['Sec-WebSocket-Key'] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])conn.send(bytes(response_str, encoding='utf-8'))while True:try:info = conn.recv(8096)except Exception as e:info = Noneif not info:breakpayload_len = info[1] & 127if 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)body = str(bytes_list, encoding='utf-8')send_msg(conn,body.encode('utf-8'))sock.close()if __name__ == '__main__':run()

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

<!DOCTYPE html>
<html>
<head lang="en"><meta charset="UTF-8"><title></title>
</head>
<body><div><input type="text" id="txt"/><input type="button" id="btn" value="提交" οnclick="sendMsg();"/><input type="button" id="close" value="关闭连接" οnclick="closeConn();"/></div><div id="content"></div><script type="text/javascript">var socket = new WebSocket("ws://127.0.0.1:8003/chatsocket");socket.onopen = function () {/* 与服务器端连接成功后,自动执行 */var newTag = document.createElement('div');newTag.innerHTML = "【连接成功】";document.getElementById('content').appendChild(newTag);};socket.onmessage = function (event) {/* 服务器端向客户端发送数据时,自动执行 */var response = event.data;var newTag = document.createElement('div');newTag.innerHTML = response;document.getElementById('content').appendChild(newTag);};socket.onclose = function (event) {/* 服务器端主动断开连接时,自动执行 */var newTag = document.createElement('div');newTag.innerHTML = "【关闭连接】";document.getElementById('content').appendChild(newTag);};function sendMsg() {var txt = document.getElementById('txt');socket.send(txt.value);txt.value = "";}function closeConn() {socket.close();var newTag = document.createElement('div');newTag.innerHTML = "【关闭连接】";document.getElementById('content').appendChild(newTag);}</script>
</body>
</html>

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

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

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

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import uuid
import json
import tornado.ioloop
import tornado.web
import tornado.websocketclass IndexHandler(tornado.web.RequestHandler):def get(self):self.render('index.html')class ChatHandler(tornado.websocket.WebSocketHandler):# 用户存储当前聊天室用户waiters = set()# 用于存储历时消息messages = []def open(self):"""客户端连接成功时,自动执行:return: """ChatHandler.waiters.add(self)uid = str(uuid.uuid4())self.write_message(uid)for msg in ChatHandler.messages:content = self.render_string('message.html', **msg)self.write_message(content)def on_message(self, message):"""客户端连发送消息时,自动执行:param message: :return: """msg = json.loads(message)ChatHandler.messages.append(message)for client in ChatHandler.waiters:content = client.render_string('message.html', **msg)client.write_message(content)def on_close(self):"""客户端关闭连接时,,自动执行:return: """ChatHandler.waiters.remove(self)def run():settings = {'template_path': 'templates','static_path': 'static',}application = tornado.web.Application([(r"/", IndexHandler),(r"/chat", ChatHandler),], **settings)application.listen(8888)tornado.ioloop.IOLoop.instance().start()if __name__ == "__main__":run()
app.py
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Python聊天室</title>
</head>
<body><lt;div><input type="text" id="txt"/><input type="button" id="btn" value="提交" onclick="sendMsg();"/><input type="button" id="close" value="关闭连接" onclick="closeConn();"/></div><div id="container" style="border: 1px solid #dddddd;margin: 20px;min-height: 500px;"></div><script src="/static/jquery-2.1.4.min.js"></script><script type="text/javascript">$(function () {wsUpdater.start();});var wsUpdater = {socket: null,uid: null,start: function() {var url = "ws://127.0.0.1:8888/chat";wsUpdater.socket = new WebSocket(url);wsUpdater.socket.onmessage = function(event) {console.log(event);if(wsUpdater.uid){wsUpdater.showMessage(event.data);}else{wsUpdater.uid = event.data;}}},showMessage: function(content) {$('#container').append(content);}};function sendMsg() {var msg = {uid: wsUpdater.uid,message: $("#txt").val()};wsUpdater.socket.send(JSON.stringify(msg));}</script></body>
</html>
index.html

 

 

转载于:https://www.cnblogs.com/hedeyong/p/8128129.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/467009.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

干,认识Audio框架还因此发现一个雷

我们最近出了一个问题&#xff0c;我们点击播放音乐&#xff0c;然后再点击停止播放的时候&#xff0c;喇叭还会输出一段杂音后喇叭才会停止输出。经过排查发现&#xff0c;在代码里面就做了这个功能代码在AudioFlinger.h 里frameworks/av/services/audioflinger/AudioFlinger.…

画图板-- 中点算法画圆

为了能以任意点为圆心画圆&#xff0c;我们可以把圆心先设为视点&#xff08;相当于于将其平移到坐标原点&#xff09;&#xff0c;然后通过中点法扫描转换后&#xff0c;再恢复原来的视点&#xff08;相当于将圆心平移回原来的位置&#xff09;。圆心位于原点的圆有四条对称轴…

Linux 内核如何描述一个进程?

哈喽&#xff0c;我是吴同学&#xff0c;继续记录我的学习心得。一、关于写文章许多知识&#xff0c;书上或者网络上都有&#xff0c;就算这两个地方都没有&#xff0c;代码里也会有答案。但有时恰恰是 资料太多&#xff0c;反而让人难以检索出有用的信息。面对同样的资料&…

供应商关系管理SRM为企业的节流增贡献

在供应链下游的需求链上&#xff0c;企业为了增加市场份额、提高销售收入&#xff0c;更为重视面向客户的管理和信息化管理方面的投入&#xff0c;纷纷引入客户关系管理CRM的管理理念和信息化系统&#xff0c;在“客户第一”的经营策略下借助IT技术的手段来提高对客户的服务水平…

UART/I2C/SPI/1-wire四大通信接口的神解释

1、 裘千丈轻功水上漂之UART射雕英雄传中的裘千丈说&#xff0c;UART就是我的轻功水上漂过河。想从河上过&#xff08;通信&#xff09;&#xff0c;提前布暗桩&#xff0c;行走时步伐按桩距固定&#xff08;波特率提前确定&#xff09;&#xff0c;步幅太大或太小都会落水。为…

last_kmsg和ram console

相关文章Android 7.1使用脚本保存LOGCAT和KMSG日志到文件首先&#xff0c;在kernel里面通过printk吐log的时候会是下面的一个过程&#xff1a;printk会将信息格式化到kernel log buffer里面去。然后将这些格式化信息送到console去&#xff0c;在我们的系统里面有两个console&am…

字符串类

1.String类 常用方法&#xff1a; 参考&#xff1a;http://wenku.baidu.com/link?urltz-3Dpwj-JSJQdG6vSo0J1L1G9oJS4eQJjYgogieIzgjdNNLmj-U9EpWhOnVthz4egAKv0SNmLkqzNz2WsiZ2EmPGMu2UXhB6yy-E4yvMQ3 NB&#xff1a; 这里的”s1s2“是地址相等,而是s1.equals(s2)是内容相等…

github网页

GitHub主页创建仓库想必大家都有自己的Github账号吧&#xff0c;没有的可以到GitHub官网注册账号&#xff0c;注册完后&#xff0c;我们来下一步&#xff0c;在我们的GitHub上面右上角的New repository来创建一个仓库。 仓库名必须遵守相应格式&#xff1a;your_username.githu…

有用的Copy-On-write,写时复制

写时复制和写时拷贝是一个意思写时复制是一种策略&#xff0c;并不是Linux独有的&#xff0c;如果你正在设计某个系统架构&#xff0c;也可以参考这种思想。写时复制的英文解释如下Copy-on-write (sometimes referred to as "COW") is an optimization strategy used…

雷军:如果程序人生的话,这条路太漫长

这篇文章是在雷总个人博客看到的&#xff0c;里面聊到了他作为程序员的一些经历、初衷以及思考。写的不错&#xff0c;便转来给大家看看。-- 如果程序人生的话&#xff0c;这条路太漫长我并非天生喜欢写程序&#xff0c;上高中时也没有想过程序员的生活。我学电脑非常偶然&…

51单片机——硬件基础

单片机——硬件基础知识 宗旨&#xff1a;技术的学习是有限的&#xff0c;分享的精神是无限的。 1、单片机内部资源 STC89C52&#xff1a;8KFLASH、512字节RAM、32个IO口、3个定时器、1个UART、8个中断源 &#xff08;1&#xff09;Flash&#xff08;硬盘&#xff09;——程…

马上工作了,想问下要注意哪些问题?

这是一位读者在知识星球提问的&#xff0c;涉及的内容很多&#xff0c;但是这些问题&#xff0c;我觉得很多人都需要注意&#xff0c;分享出来让大家看&#xff0c;也希望留言说下自己的看法。1一、 offer情况 1. 已签三方 2. 公司是国内靠前的芯片原厂&#xff0c;深圳&#x…

CSS绘图

1. 自适应的椭圆 背景知识&#xff1a;border-radius属性的基本用法 使用border-radius制作一个圆很简单&#xff0c;只要给任何正方形元素设置固定宽高及一半长度以上的border-radius&#xff0c;就可以得到圆形 1234background: #fb3;width: 200px;height: 200px;border-radi…

51单片机——定时器

单片机——定时器 宗旨&#xff1a;技术的学习是有限的&#xff0c;分享的精神是无限的。 1、定时器和计数器 时钟周期&#xff1a;单片机时序中的最小单位&#xff0c;集体计算的放法就是时钟源分之一 机器周期&#xff1a;单片机完成一个操作的最短时间&#xff0c;12个时钟…

当年要是这样搞,我的C++也不至于这样!

C一直被称作永不过时的开发语言&#xff0c;比如游戏、服务器、人工智能等领域都必须用到C&#xff01;虽然网上有很多教程和资料&#xff0c;但仍有很多人说C难学&#xff0c;究其原因是没有找到正确的学习方法&#xff01;GoogleTest框架一直广泛应用于C/C项目测试 &#xff…

ABP之创建实体

ABP框架是一个非常庞大的框架&#xff0c;里面的东西有很多&#xff0c;那么如果我需要使用ABP进行项目的开发&#xff0c;具体的使用流程是怎样的呢&#xff1f;接下来将以一个简单的电影票管理“系统”为例子具体的实现一下。 一. 实体的创建 实体是属于领域层的&#xff0c;…

三亚野生植物园

近来朋友外出旅游颇多,大家分享快乐和美景的同时&#xff0c;文字往来也丰富。我的三亚行也该落幕了&#xff0c;休整得差不多了&#xff0c;重整行装&#xff0c;再踏旅途……诸君逍遥游芙蓉叶残桂满岩&#xff0c;粤女梦起游天姥。白塔青湖鸣沙丘&#xff0c;渡海峨眉牵白鹿。…

10张图22段代码,万字长文带你搞懂虚拟内存模型和malloc内部原理

成功是急不来的。不计较眼前得失&#xff0c;将注意力真正着眼于正在做的事情本身&#xff0c;持续付出努力&#xff0c;才能一步步向前迈进&#xff0c;逐渐达到理想的目标。不着急&#xff0c;才能从容不迫&#xff0c;结果自会水到渠成。大家好&#xff0c;我是程序喵&#…

51单片机——中断

单片机——中断 宗旨&#xff1a;技术的学习是有限的&#xff0c;分享的精神是无限的。 1、中断&#xff1a;放下手中的事情去处理另一种事情。 2、定时器中断 定时器是单片机模块的一个资源&#xff0c;确确实实存在的一个模块&#xff0c;中断是单片机的一种运行机制。不是一…

转!!ftp的主动模式(port)与被动模式(PASV)

转自 http://www.phpweblog.net/killjin/archive/2008/01/06/2653.htmlftp中主动模式(port)与被动模式(PASV)目录 开场白 基础 主动FTP 主动FTP的例子 被动FTP 被动FTP的例子 总结 参考资料 附录 1: 配置常见FTP服务器 开场白 处理防火墙和其他网络连接问题时最常见的…