新篇章---http协议---http协议与websocket区别:
一 websocket协议
1.0 前置概念
WebSocket:
目的: 解决网络传输中的双向通信的问题.
诞生 为了创建一种双向通信的协议.
1.1 为什么用WebSocket替换HTTP
HTTP1.1默认使用持久连接(persistent connection),在一个TCP连接上也可以传输多个Request/Response消息对,但HTTP的基本模型是一个Request对应一个Response.
解决方案(双向通信---客户端要向服务器传送数据,同时服务器也需要实时向客户端传输信息,一个聊天系统就是典型的双向通信):
- 轮询(polling),轮询造成对网络和通信双方资源的浪费,且非实时.
- 长轮询,客户端发送一个超时时间很长的Request,服务器夯住这个连接,在有新数据到达时返回Response,相对第一个,占据网络带宽变少,其他类似.
- 长连接,指HTTP的长连接.(如果用Socket建立TCP的长连接,那么这个长连接和websocket是一样的,实际上TCP长连接就是websocket的基础)
如果是HTTP的长连接,本质上还是Request/Response消息对,仍然会造成资源的浪费,实时性不强等问题.
1.2 协议基础
WebSocket的目的是取代HTTP在双向通信场景下的使用,而且它的实现方式有些也是基于HTTP的(WS的默认端口是80和443)。现有的网络环境(客户端、服务器、网络中间人、代理等)对HTTP都有很好的支持,所以这样做可以充分利用现有的HTTP的基础设施,有点向下兼容的意味。简单来讲,WS协议有两部分组成:握手和数据传输。
- 握手(handshake)
出于兼容性的考虑,WS的握手使用HTTP来实现(此文档中提到未来有可能会使用专用的端口和方法来实现握手),客户端的握手消息就是一个「普通的,带有Upgrade头的,HTTP Request消息」。所以这一个小节到内容大部分都来自于RFC2616,这里只是它的一种应用形式,下面是RFC6455文档中给出的一个客户端握手消息示例:
GET /chat HTTP/1.1 //1
Host: server.example.com //2
Upgrade: websocket //3
Connection: Upgrade //4
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== //5
Origin: http://example.com //6
Sec-WebSocket-Protocol: chat, superchat //7
Sec-WebSocket-Version: 13 //8
详细解释
- Upgrade:upgrade是HTTP1.1中用于定义转换协议的header域。它表示,如果服务器支持的话,客户端希望使用现有的「网络层」已经建立好的这个「连接(此处是TCP连接)」,切换到另外一个「应用层」(此处是WebSocket)协议。
- Connection:HTTP1.1中规定Upgrade只能应用在「直接连接」中,所以带有Upgrade头的HTTP1.1消息必须含有Connection头,因为Connection头的意义就是,任何接收到此消息的人(往往是代理服务器)都要在转发此消息之前处理掉Connection中指定的域(不转发Upgrade域)。
如果客户端和服务器之间是通过代理连接的,那么在发送这个握手消息之前首先要发送CONNECT消息来建立直接连接。 - Sec-WebSocket-*:第7行标识了客户端支持的子协议的列表(关于子协议会在下面介绍),第8行标识了客户端支持的WS协议的版本列表,第5行用来发送给服务器使用(服务器会使用此字段组装成另一个key值放在握手返回信息里发送客户端)。
- Origin:作安全使用,防止跨站攻击,浏览器一般会使用这个来标识原始域。
如果服务器接受了这个请求,可能会发送如下这样的返回信息,这是一个标准的HTTP的Response消息。101表示服务器收到了客户端切换协议的请求,并且同意切换到此协议。
HTTP/1.1 101 Switching Protocols //1
Upgrade: websocket. //2
Connection: Upgrade. //3
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= //4
Sec-WebSocket-Protocol: chat. //5
1.3 其他
1.3.1 WebSocket协议Uri
ws协议默认使用80端口,wss协议默认使用443端口。
1.3.2 在客户端发送握手之前要做的一些小事
一个客户端对一个ip地址同一时刻只能由一个处于connecting状态的连接.
1.3.3 重点是什么
重点是生成的聊天消息对象,任何操作无非就是找到对的人,然后给他发送消息.
二 简单介绍http协议
2.1 HTTP的地址格式:
http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]] 协议和host不分大小写
2.2 HTTP消息
一个HTTP消息可能是request或者是response消息.
一个合格的HTTP客户端不应该在消息头或者尾添加多余的CRLF,服务端也会忽略这些字符。(CRLF是Carriage-Return Line-Feed的缩写,意思是回车换行)

2.2.1 Request消息
RFC2616定义HTTP Request消息:
Request = Request-Line
*(( general-header
| request-header(跟本次请求相关的一些header)
| entity-header ) CRLF)(跟本次请求相关的一些header)
CRLF
[ message-body ]
一个HTTP的request消息以一个请求行开始,从第二行开始是header,接下来是一个空行,表示header结束,最后是消息体。
请求行的定义:
//请求行的定义
Request-Line = Method SP Request-URL SP HTTP-Version CRLF
//方法的定义
Method = "OPTIONS" | "GET" | "HEAD" |"POST" |"PUT" |"DELETE" |"TRACE" |"CONNECT" | extension-method
//资源地址的定义
Request-URI ="*" | absoluteURI | abs_path | authotity(CONNECT)
2.2.2 Response消息
响应消息:
Response = Status-Line
*(( general-header
| response-header
| entity-header ) CRLF)
CRLF
[ message-body ]
可以看到,除了header不使用request-header之外,只有第一行不同,响应消息的第一行是状态行,其中就包含大名鼎鼎的返回码。
2.3 返回码
返回码是一个3位数,第一位定义的返回码的类别,总共有5个类别,它们是:
- 1xx: Informational - Request received, continuing process
- 2xx: Success - The action was successfully received,
understood, and accepted
- 3xx: Redirection - Further action must be taken in order to
complete the request
- 4xx: Client Error - The request contains bad syntax or cannot
be fulfilled
- 5xx: Server Error - The server failed to fulfill an apparently
valid request
三 http协议与websocket协议异同
不同
按照OSI网络分层模型,
IP是网络层协议,
TCP是传输层协议,
HTTP是应用层的协议。
在这三者之间,SPDY和WebSocket都是与HTTP相关的协议,而TCP是HTTP底层的协议。 WebSocket则提供使用一个TCP连接进行双向通讯的机制,包括网络协议和API.
相同点
- 都是基于TCP的应用层协议。
- 都使用Request/Response模型进行连接的建立。
- 在连接的建立过程中对错误的处理方式相同,在这个阶段WS可能返回和HTTP相同的返回码。
- 都可以在网络中传输数据。
不同点
- WS使用HTTP来建立连接,但是定义了一系列新的header域,这些域在HTTP中并不会使用。
- WS的连接不能通过中间人来转发,它必须是一个直接连接。
- WS连接建立之后,通信双方都可以在任何时刻向另一方发送数据。
- WS连接建立之后,数据的传输使用帧来传递,不再需要Request消息。
- WS的数据帧有序。
四 websocket实现代码
4.0 准备
安装pip install gevent-websocket
4.1 简单实现从自己的机器访问
注意,启动的话要从
from flask import Flask, request,render_template
from geventwebsocket.handler import WebSocketHandler # 提供WS协议处理
# 反套路
from geventwebsocket.server import WSGIServer # 承载服务UWsgi Werkzeug
# 上下两个是一个WSGIServer的
# from gevent.pywsgi import WSGIServer
from geventwebsocket.websocket import WebSocket # 语法提示,关于如何100%把方法点出来的,编译器只找3层,强制用WSGIServer找到最后一层
app = Flask(__name__)
@app.route('/my_socket')
def my_socket():
# print(request.headers)
# print(request.environ) # 请求的原始信息
# 获取当前客户端与服务器的socket链接
user_socket = request.environ.get("wsgi.websocket") # type:WebSocket
# 注意,type:WebSocket是必须的!!!
print('-------------------')
print(user_socket, '200 ok,可以发消息了')
while 1:
msg = user_socket.receive()
print(msg)
user_socket.send(msg)
# return render_template('h1.html')
return '200 ok!'
if __name__ == '__main__':
# app.run('0.0.0.0', 9527)
http_server = WSGIServer(('0.0.0.0', 9527), app, handler_class=WebSocketHandler)
http_server.serve_forever()
# websocket如何处理wsgi的请求?
# 从http请求转变为websocket协议.
HTML文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
<script type="text/javascript">
var ws = new WebSocket('ws:192.168.16.18:12580/my_socket');
// 监听电话(下面的不带的话是没办法在前端的控制台上面显示的)
ws.onmessage = function (MessageEvent) {
console.log(MessageEvent.data)
}
</script>
</html>
注意,这个不在于你输入的域名(实际上你输入域名就根本不可能能使用,那是HTTP协议),那应该是直接打开上面那个HTML文件就可使用.(这个是websocket协议) .重点在于script那里,完全可以把这个html文件作为是一个客户端(只不过是从浏览器启动的).
4.2 匿名群聊(广播)
4.2.1 HTML文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>群聊</title>
</head>
<body>
<p><input type="text" id="content"><button onclick="send_msg()">发送</button></p>
<div id="chat_list">
</div>
</body>
<script type="text/javascript">
var ws = new WebSocket('ws:192.168.16.18:12580/my_socket');
// 监听电话
ws.onmessage = function (MessageEvent) {
console.log(MessageEvent.data);
var p = document.createElement('p');
p.innerText = MessageEvent.data;
document.getElementById('chat_list').appendChild(p);
};
function send_msg() {
var content = document.getElementById('content').value;
ws.send(content);
}
</script>
</html>
4.2.2 python文件(当需要广播的时候,不用写那么多乱七八糟的代码,只需要写一个装饰器,返回一个html就行了)
# 简单实现websocket
from flask import Flask,request,render_template
from geventwebsocket.handler import WebSocketHandler # 提供websocket协议
from geventwebsocket.server import WSGIServer # 承载服务UWsgi Werkzeug
from geventwebsocket.websocket import WebSocket # 语法提示
app = Flask(__name__)
user_socket_list = []
@app.route('/my_socket')
def my_socket():
user_socket = request.environ.get('wsgi.websocket') # type:WebSocket
if user_socket:
user_socket_list.append(user_socket)
print(len(user_socket_list),user_socket_list)
# print(user_socket,'200 ok')
while 1:
msg = user_socket.receive()
print(msg)
for usocket in user_socket_list:
try:
usocket.send(msg)
except:
continue
return '200 ok!'
@app.route('/gc')
def gc():
return render_template('h1.html')
if __name__ == '__main__':
http_server = WSGIServer(('0.0.0.0',12580),app,handler_class=WebSocketHandler)
http_server.serve_forever()
4.3 带昵称的群聊(有bug,数字计算不正确)
4.3.1 python文件
from flask import Flask, request, render_template
from geventwebsocket.handler import WebSocketHandler # 转为websocket协议
from geventwebsocket.server import WSGIServer # 承载服务UWsgi Workzeug
from geventwebsocket.websocket import WebSocket # 语法提示
import json
app = Flask(__name__)
user_socket_dict = {}
dic = {}
@app.route('/my_socket/<username>') # 动态参数
def my_socket(username):
user_socket = request.environ.get('wsgi.websocket') # type:WebSocket
# print(user_socket)
if user_socket:
dic.setdefault('number', 0)
dic['number'] += 1
print(dic)
user_socket_dict[dic['number']] = {'from_user': username, 'chat': user_socket}
print(len(user_socket_dict), user_socket_dict)
while 1:
msg_dict = json.loads(user_socket.receive())
# print(msg_dict,type(msg_dict))
# print(user_socket_dict)
for number, usocket in user_socket_dict.items():
try:
msg = '游客%s_'%number + msg_dict['from_user'] + ':' + (msg_dict['chat'])
# print(msg)
# print(usocket['chat'])
usocket['chat'].send(msg)
except:
continue
@app.route('/gc')
def gc():
return render_template('h1.html')
if __name__ == '__main__':
http_server = WSGIServer(('0.0.0.0', 9000), app, handler_class=WebSocketHandler)
http_server.serve_forever()
4.3.2 html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>群聊</title>
</head>
<body>
<p>我的昵称: <input type="text" id="username"><button onclick="loginGc()">登录</button></p>
<p><input type="text" id="content"><button onclick="send_msg()">发送</button></p>
<div id="chat_list"></div>
</body>
<script type="text/javascript">
var ws = null;
function loginGc() {
var username = document.getElementById('username').value;
ws = new WebSocket('ws://192.168.16.18:9000/my_socket/'+username);
// 监听电话
ws.onmessage = function (MessageEvent) {
console.log(MessageEvent);
var p = document.createElement('p');
p.innerText = MessageEvent.data;
document.getElementById('chat_list').appendChild(p);
};
}
function send_msg() {
var username = document.getElementById('username').value;
var content = document.getElementById('content').value;
var sendStr = {
from_user:username,
chat:content,
};
ws.send(JSON.stringify(sendStr));
}
</script>
</html>
4.4 一对一私聊
4.4.1 python文件
from flask import Flask, request, render_template
from geventwebsocket.handler import WebSocketHandler # 转为websocket协议
from geventwebsocket.server import WSGIServer # 承载服务UWsgi Workzeug
from geventwebsocket.websocket import WebSocket # 语法提示
import json
app = Flask(__name__)
user_socket_dict = {}
dic = {}
@app.route('/my_socket/<username>') # 动态参数
def my_socket(username):
user_socket = request.environ.get('wsgi.websocket') # type:WebSocket
if user_socket:
dic.setdefault('number', 0)
dic['number'] += 1
user_socket_dict[dic['number']] = {'from_user': username, 'chat': user_socket}
print(len(user_socket_dict), user_socket_dict)
while 1:
msg = user_socket.receive()
msg_dict = json.loads(msg)
print(msg_dict)
to_user_nick = msg_dict.get('to_user')
for number, usocket in user_socket_dict.items():
try:
if usocket['from_user'] == to_user_nick:
to_user_socket = usocket['chat']
msg = '游客%s_'%number + msg_dict['from_user'] + ':' + (msg_dict['chat'])
to_user_socket.send(msg)
except:
continue
@app.route('/gc')
def gc():
return render_template('h1.html')
if __name__ == '__main__':
http_server = WSGIServer(('0.0.0.0', 9000), app, handler_class=WebSocketHandler)
http_server.serve_forever()
4.4.2 HTML文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>群聊</title>
</head>
<body>
<p>我的昵称: <input type="text" id="username"><button onclick="loginGc()">登录</button></p>
<p>给 <input type="text" id="to_user">发送</p>
<p><input type="text" id="content"><button onclick="send_msg()">发送</button></p>
<div id="chat_list"></div>
</body>
<script type="text/javascript">
var ws = null;
function loginGc() {
var username = document.getElementById('username').value;
ws = new WebSocket('ws://192.168.16.18:9000/my_socket/'+username);
// 监听电话
ws.onmessage = function (MessageEvent) {
console.log(MessageEvent);
var p = document.createElement('p');
p.innerText = MessageEvent.data;
document.getElementById('chat_list').appendChild(p);
};
}
function send_msg() {
var username = document.getElementById('username').value;
var to_user = document.getElementById('to_user').value;
var content = document.getElementById('content').value;
var sendStr = {
from_user:username,
to_user:to_user,
chat:content,
};
ws.send(JSON.stringify(sendStr));
}
</script>
</html>
五 补充
5.1 Werkzeug
5.1.1 概念
Werkzeug是Python的WSGI规范的实用函数库。使用广泛,基于BSD协议.。
Werkzeug就是Flask使用的底层WSGI库
5.1.2 功能
- HTTP头解析与封装
- 易于使用的request和response对象
- 基于浏览器的交互式JavaScript调试器
- 与 WSGI 1.0 规范100%兼容
- 支持Python 2.6,Python 2.7和Python3.3
- 支持Unicode
- 支持基本的会话管理及签名Cookie
- 支持URI和IRI的Unicode使用工具
- 内置支持兼容各种浏览器和WSGI服务器的实用工具
- 集成URL请求路由系统
5.1.3 基于Werkzeug的代码
from werkzeug.wrappers import Request, Response
@Request.application
def application(request):
return Response('Hello World!')
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, application)
5.2 BSD协议
5.2.1 概念
BSD是"Berkeley Software Distribution"的缩写,意思是"伯克利软件发行版"。BSD是一整套软件发行版的统称。
身份
什么是许可协议呢,要介绍什么是许可,当你为你的产品签发许可,你是在让出自己的权利,不过,你仍然拥有版权和专利(如果申请了的话),许可的目的是,向使用你产品的人提供 一定的权限。
不管产品是免费向公众分发,还是出售,制定一份许可协议非常有用,否则,对于前者,你相当于放弃了自己所有的权利,任何人都没有义务表明你的原始作 者身份,对于后者,你将不得不花费比开发更多的精力用来逐个处理用户的授权问题。
而开源许可协议使这些事情变得简单,开发者很容易向一个项目贡献自己的代码,它还可以保护你原始作者的身份,使你 至少获得认可,开源许可协议还可以阻止其它人将某个产品据为己有。以下是开源界的 5 大许可协议:五大开源许可协议分别是GPL,LGPL,BSD,MIT,Apache。
BSD就是这五种开源协议之一。
5.3 uwsgi,uWSGI,WSGI
uWSGI是一个Web服务器,是实现了uwsgi和WSGI两种协议的Web服务器。它实现了WSGI协议、uwsgi、http等协议。Nginx中HttpUwsgiModule的作用是与uWSGI服务器进行交换。
WSGI是一种通信协议。WSGI是一种Web服务器网关接口。它是一个Web服务器(如nginx,uWSGI等服务器)与web应用(如用Flask框架写的程序)通信的一种规范。
uwsgi是一种线路协议而不是通信协议,在此常用于在uWSGI服务器与其他网络服务器的数据通信。 uwsgi协议是一个uWSGI服务器自有的协议,它用于定义传输信息的类型(type of information),每一个uwsgi packet前4byte为传输信息类型描述,它与WSGI相比是两样东西。
浙公网安备 33010602011771号