新篇章---http协议---http协议与websocket区别:

一 websocket协议

1.0 前置概念

WebSocket:
目的: 解决网络传输中的双向通信的问题.
诞生 为了创建一种双向通信的协议.

1.1 为什么用WebSocket替换HTTP

HTTP1.1默认使用持久连接(persistent connection),在一个TCP连接上也可以传输多个Request/Response消息对,但HTTP的基本模型是一个Request对应一个Response.
解决方案(双向通信---客户端要向服务器传送数据,同时服务器也需要实时向客户端传输信息,一个聊天系统就是典型的双向通信):

  1. 轮询(polling),轮询造成对网络和通信双方资源的浪费,且非实时.
  2. 长轮询,客户端发送一个超时时间很长的Request,服务器夯住这个连接,在有新数据到达时返回Response,相对第一个,占据网络带宽变少,其他类似.
  3. 长连接,指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

详细解释

  1. Upgrade:upgrade是HTTP1.1中用于定义转换协议的header域。它表示,如果服务器支持的话,客户端希望使用现有的「网络层」已经建立好的这个「连接(此处是TCP连接)」,切换到另外一个「应用层」(此处是WebSocket)协议。
  2. Connection:HTTP1.1中规定Upgrade只能应用在「直接连接」中,所以带有Upgrade头的HTTP1.1消息必须含有Connection头,因为Connection头的意义就是,任何接收到此消息的人(往往是代理服务器)都要在转发此消息之前处理掉Connection中指定的域(不转发Upgrade域)。
    如果客户端和服务器之间是通过代理连接的,那么在发送这个握手消息之前首先要发送CONNECT消息来建立直接连接。
  3. Sec-WebSocket-*:第7行标识了客户端支持的子协议的列表(关于子协议会在下面介绍),第8行标识了客户端支持的WS协议的版本列表,第5行用来发送给服务器使用(服务器会使用此字段组装成另一个key值放在握手返回信息里发送客户端)。
  4. 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 重点是什么

重点是生成的聊天消息对象,任何操作无非就是找到对的人,然后给他发送消息.

参考链接1

二 简单介绍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.

相同点

  1. 都是基于TCP的应用层协议。
  2. 都使用Request/Response模型进行连接的建立。
  3. 在连接的建立过程中对错误的处理方式相同,在这个阶段WS可能返回和HTTP相同的返回码。
  4. 都可以在网络中传输数据。

不同点

  1. WS使用HTTP来建立连接,但是定义了一系列新的header域,这些域在HTTP中并不会使用。
  2. WS的连接不能通过中间人来转发,它必须是一个直接连接。
  3. WS连接建立之后,通信双方都可以在任何时刻向另一方发送数据。
  4. WS连接建立之后,数据的传输使用帧来传递,不再需要Request消息。
  5. 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 功能

  1. HTTP头解析与封装
  2. 易于使用的request和response对象
  3. 基于浏览器的交互式JavaScript调试器
  4. 与 WSGI 1.0 规范100%兼容
  5. 支持Python 2.6,Python 2.7和Python3.3
  6. 支持Unicode
  7. 支持基本的会话管理及签名Cookie
  8. 支持URI和IRI的Unicode使用工具
  9. 内置支持兼容各种浏览器和WSGI服务器的实用工具
  10. 集成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相比是两样东西。

参考来源:百度百科

posted on 2019-07-15 22:02  流云封心  阅读(352)  评论(0)    收藏  举报