手动实现的websocket

你所见过的websocket

你一定见过在网站中,有一个游客聊天的聊天框,比如人人影视。这个聊天框是如何实现即时通讯的呢,就是用到了websocket

你可以打开浏览器的network,会看到有个ws://xxxxx,这就代表了是websocket做的

那么什么是websocket?

websocket就是一套协议。

看名字,虽然有个websocket,但他和http协议一样,也要走socket。

不同的是:http是短连接,处理完一个请求就断开;

​ websocket是连上就不断开,一直不断开,属于双工通道,服务端可以主动给客户端推送消息,客户端也可以主动给服务端推送消息

当某一个客户端发送一条消息,服务端接收以后,再推送给所有的客户端,所以才会呈现出所有人都在即时通讯的效果

服务端当然就是我们写的程序了,那客户端是浏览器,所以还需要浏览器支持才行。不要以为浏览器是都支持的,如果所有人都用chrome,前端开发工程师估计就没什么工作了。还有,如果所有的浏览器都支持,腾讯的webQQ,web微信,也不会使用长轮询来做这个事了。

来看一下具体的代码实现

import socket
import base64
import hashlib def 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_dict 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()
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')) 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)): #上面解密的最终结果,就是拿到这个decode,就是浏览器发来的真实的数据(加密的)
chunk = decoded[i] ^ mask[i % 4] #按位异或
bytes_list.append(chunk) body = str(bytes_list, encoding='utf-8')
print(body)

客户端向服务端发送的请求里,有Sec-WebSocket-Key这样一个key,服务端回消息的时候,就要拿到这个key,加密后再发给浏览器,浏览器会判断自己加密后的值,与浏览器处理的是否一致,一致才能连接。加密的方式,用到一个magic_string,其实就是一段固定的字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11,加密后打包发给浏览器,浏览器验证通过后就可以通讯了,再来看看客户端:

客户端就直接用浏览器运行这个html文件就行

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="dist/css/bootstrap.css">
</head>
<body> <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="content"></div> <script type="text/javascript">
var socket = new WebSocket("ws://127.0.0.1:8002");
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> <script></script>
</body>
</html>

这里面有三个方法:

  1. 连接上后,onopen会自动执行
  2. 发消息时,onmessage自动执行
  3. 断开连接,onclose自动执行

客户端发送给服务端的数据,还有一层加密,必须通过解密才能拿到正确的消息

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)): #上面解密的最终结果,就是拿到这个decode,就是浏览器发来的真实的数据(加密的)
chunk = decoded[i] ^ mask[i % 4] #按位异或
bytes_list.append(chunk) body = str(bytes_list, encoding='utf-8')

这段就是解密的过程,用到位运算

Django默认是不支持websocket的,虽然有个第三方的channels插件

但是tornado默认就支持

tornado实现websocket

如果用tornado,客户端不能直接用浏览器运行了,而应该是运行tornado的一个模板文件

服务端代码:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import uuid
import json
import tornado.ioloop
import tornado.web
import tornado.websocket class 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:
# {'uid':'xxx','message':asdfasd}
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(msg) 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(8009)
tornado.ioloop.IOLoop.instance().start() if __name__ == "__main__":
run()

模板文件(客户端代码):

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Python聊天室</title>
</head>
<body>
<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-3.2.1.js"></script>
<script type="text/javascript">
$(function () {
wsUpdater.start();
});
var wsUpdater = {
socket: null,
uid: null,
start: function() {
var url = "ws://192.168.16.200:8009/chat";
wsUpdater.socket = new WebSocket(url);
wsUpdater.socket.onmessage = function(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>

原理都一样,但是用tornado实现起来,就清爽多了。

ps:再说一下腾讯的长轮询,如果你登录webQQ,或者web微信,你可以在network里面找到 pending的字样,这就是表示是使用的长轮询(long polling)。

长轮询与轮询(polling)的区别就是:

​ 轮询是过来以后看到没消息就立马去走了,但是长轮询不会立马走,而是在这等30秒(约定的时间)之后,如果一直没有消息,才返回,下一次来在等30秒,直到有消息了,这样有个缺点就是,拿到的消息并不是即时的。那腾讯这么大的公司,为什么不用性能更好的websocket呢?原因就是他是个大公司,必须要考虑兼容性,必须要保证所有的浏览器都能使用才行。

你可以从这里拿到完整 的示例代码

https://github.com/zEllis/websocket_demo

websocket介绍的更多相关文章

  1. WebSocket介绍,与Socket的区别

    WebSocket介绍与原理 WebSocket protocol 是HTML5一种新的协议.它实现了浏览器与服务器全双工通信(full-duplex).一开始的握手需要借助HTTP请求完成. ——百 ...

  2. 谈下WebSocket介绍,与Socket的区别

    这个话题应该是面试中出现频率比较高的吧....不管咋样还是有必要深入了解下两者之间的关联.废话不多说,直接入题吧: WebSocket介绍与原理 目的:即时通讯,替代轮询 网站上的即时通讯是很常见的, ...

  3. WebSocket(1)---WebSocket介绍

    WebSocket介绍   一.为什么需要 WebSocket? 初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处? 答案很 ...

  4. WebSocket 介绍(二)-WebSocket API

    这一章介绍如何用WebSocket API来控制协议和创建应用,运用http://websocket.org 提供的现有WebSocket服务器,我们可以收发消息.创建一些简单的WebSocket应用 ...

  5. WebSocket介绍和一个简单的聊天室

    WebSocket是什么呢? WebSocket一种在单个 TCP 连接上进行全双工通讯的协议.WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范, ...

  6. WebSocket 介绍(一)

    WebSocket 发起单个请求,服务端不需要等待客服端,客户端在任何时候也能发消息到服务端,减少了轮询时候的延迟.经历一次连接后,服务器能给客户端发多次.下图是轮询与WebSocket的区别. 基于 ...

  7. 【WebSocket】WebSocket介绍

    1.背景 在WebSocket出现之前客户端向服务器发出请求是通过http协议实现的,而http协议有个特点是通行请求只能由客户端发起,然后服务端响应查询结果,HTTP 协议没法让服务器主动向客户端推 ...

  8. websocket介绍 以及 vue websocket使用案例

    1 介绍: https://www.zhihu.com/question/20215561 2 案例: https://www.jianshu.com/p/0d20a032d0ec (坑说明). ht ...

  9. Jetty开发指导:WebSocket介绍

    WebSocket是一个新的基于HTTP的双向通讯的协议. 它是基于低级别的框架协议.使用UTF-8 TEXT或者BINARY格式传递信息. 在WebSocket中的单个信息能够是不论什么长度(然而底 ...

随机推荐

  1. HTTP 简要

    HTTP协议就是客户端和服务器交互的一种通迅的格式. 当在浏览器中点击这个链接的时候,浏览器会向服务器发送一段文本,告诉服务器请求打开的是哪一个网页.服务器收到请求后,就返回一段文本给浏览器,浏览器会 ...

  2. 实现一个单隐层神经网络python

    看过首席科学家NG的深度学习公开课很久了,一直没有时间做课后编程题,做完想把思路总结下来,仅仅记录编程主线. 一 引用工具包 import numpy as np import matplotlib. ...

  3. vue.js快速搭建图书管理平台

      前  言 上一期简单讲解了vue的基本语法,这一次我们做一个小项目,搭建一个简单的图书管理平台,能够让我们更深刻的理解这门语言的妙用.   1.DEMO样式 首先我们需要搭建一个简单的demo样式 ...

  4. 对foreach循环的思考

    阿里java开发手册已经发表,很多都值得认真研究思考,看到零度的思考题,没忍住研究了一下. 零度的思考:https://mp.weixin.qq.com/s/dDR21k30s6ZVfDvl8BVQm ...

  5. MySql数据库的基本原理及指令

    1.什么是数据库 数据库就是存储数据的仓库,其本质是一个文件系统,数据按照特定的格式将数据存储起来,用户可以通过SQL对数据库中的数据进行增加,修改,删除及查询操作. 2.简介 MySQL是一个开放源 ...

  6. 用js判断是否为手机浏览,如果是手机浏览就跳转到手机站

    <script type="text/javascript"> function browserRedirect() { var sUserAgent= navigat ...

  7. grunt concat针对有依赖文件的js脚本的合并

    grunt concat针对有依赖文件的js脚本的合并: 在一个入口文件index.js里,有很多依赖文件,主要分两类,一类是和主文件同目录,另一类是其他目录下的js(cmd.非cmd的js文件,一般 ...

  8. Python 列表浅拷贝与深拷贝

    浅拷贝 shallow copy 和深拷贝 deep copy list.copy() 浅拷贝:复制此列表(只复制一层,不会复制深层对象) 等同于 L[:] 举例: 浅拷贝: a = [1.1, 2. ...

  9. CentOS6.8系统下,ecipse下进行编辑操作,意外退出

    错误情况:centos下打开eclipse软件,点击*.java或者*.pom软件卡死,命令行终端报错误信息,稍后eclipse自动退出. 错误信息: Java: cairo-misc.c:380: ...

  10. [转载] Gossip算法学习

    转载自http://blog.csdn.net/yfkiss/article/details/6943682/ 1. 概述gossip,顾名思义,类似于流言传播的概念,是一种可以按照自己的期望,自行选 ...