Simple Life

和这个世界交手这许多年   你是否光彩依旧,兴致盎然...

您现在的位置是:首页 爱折腾 爱折腾详情

使用autobahn python搭建基于websocket的网页聊天室

发布时间:2016-4-03 作者:Felix 浏览(5425)

    websocket是HTML5中的一种新协议,实现了浏览器和服务器间的全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:

  • WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样;

  • WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。


    为什么使用websocket呢?

    websocket能够在浏览器和服务器之间实现全双工的通信,通过建立的连接双方可以push数据。而HTTP协议是无状态的,它意味着服务端无法通过一次连接经常性的向浏览器push数据。之前很多时候,web应用通过ajax长轮询请求服务器去实现类似的需求,我也曾近这么做过,如下图:

    

lp.png

    服务器push数据比ajax长轮询更有效率,而且可扩展,因为浏览器不需要通过ajax经常性的去请求服务器。问题很明显,当客户端以固定频率向服务器端发送请求时,服务器端的数据可能并没有更新,带来很多无谓请求,浪费带宽,效率低下。

    

ws.png

    上面的示意图展示了服务器push数据到浏览器,websock是全双工的连接,所以浏览器也可以向服务器推送数据,见下图:

    

websock全双工

    服务器和浏览器接受来自对方的数据,就像聊天一样,所以你也可以依次做一个聊天室的应用。

    

    如果你想用python写一个基于websocket的应用,会有很多选择。如果你是使用django的,你可以使用channels,而且在今年django1.10版本计划合并channel,如果使用的是flask,可以使用flask-SocketIO。以上两个都是在web框架的基础上进行扩展,让你使用websocket。Tronado是一个完整的框架可以使用websock创建实时的异步应用。

    这里介绍下一个比较成熟的解决方案 —— Autobahn-python,它是基于Twisted 和 Asyncio来实现websocket的,Twisted是一个比较久远而且稳定的python异步解决方案,基于事件驱动的网络引擎框架。

    autobahn在特定的事件会触发回掉,autobahn的核心接口autobahn.websocket.interfaces.IWebSocketChannel提供了如下回调:    

    • autobahn.websocket.interfaces.IWebSocketChannel.onConnect()

    • autobahn.websocket.interfaces.IWebSocketChannel.onOpen()

    • autobahn.websocket.interfaces.IWebSocketChannel.onMessage()

    • autobahn.websocket.interfaces.IWebSocketChannel.onClose()

    1.onConnect() 客户端请求握手的时候触发 

服务端:
class MyServerProtocol(WebSocketServerProtocol):
   def onConnect(self, request):
      print("Client connecting: {}".format(request.peer))
客户端
class MyClientProtocol(WebSocketClientProtocol):
   def onConnect(self, response):
      print("Connected to Server: {}".format(response.peer))

        这个事件中,你可以:

            a.检查或者设置cookie,或者是其他HTTP HEADER

            b.校验IP地址

            c.校验websocket原始请求

            d.判断websocket协议 

       2.onOpen() 成功建立socket连接后触发

class MyProtocol(WebSocketProtocol):
   def onOpen(self):
      print("WebSocket connection open.")

        3.onMessage() 建立连接后发送消息

from autobahn.twisted.websocket import WebSocketServerProtocol
class MyServerProtocol(WebSocketServerProtocol):
   def onMessage(self, payload, isBinary):
      ## echo back message verbatim
      self.sendMessage(payload, isBinary)

        4.onClose() 关闭连接时触发

class MyProtocol(WebSocketProtocol):
   def onClose(self, wasClean, code, reason):
      print("WebSocket connection closed: {}".format(reason))

        

    聊天室的实现:

        前端代码 index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <script type="text/javascript">
        window.addEventListener("load", function() {
            // 创建websocket实例
            var mySocket = new WebSocket("ws://localhost:8080/ws");
            // 监听消息
            mySocket.onmessage = function (event) {
                var output = document.getElementById("output");
                output.textContent = event.data + "\n";
            };
            var form = document.getElementsByClassName("foo");
            var input = document.getElementById("input");
            form[0].addEventListener("submit", function (e) {
                input_text = input.value;
                mySocket.send(input_text);
                e.preventDefault()
            })
        });
    </script>
</head>
<body>
    <div style="width: 800px;margin:100px auto;">
        <form class="foo">
            <label>聊天对话框:</label>
            <input id="input">
            <input type="submit"/>
        </form>
    <div id="output" style="margin-top: 200px"></div>
    </div>
</body>
</html>

        后端代码 server.py:

import sys
import random
from twisted.web.static import File
from twisted.python import log
from twisted.web.server import Site
from twisted.internet import reactor
from autobahn.twisted.websocket import WebSocketServerFactory, \
    WebSocketServerProtocol
from autobahn.twisted.resource import WebSocketResource


class ServerProtocol(WebSocketServerProtocol):   
    def onOpen(self):
        """连接成功 注册 寻找好友"""
        self.factory.register(self)
        self.factory.find_friend(self)
    def connectionLost(self, reason):
        """断开连接 注销"""
        self.factory.unregister(self)
    def onMessage(self, payload, isBinary):
        """发送消息"""
        self.factory.communicate(self, payload, isBinary)

class RouterFactory(WebSocketServerFactory):
    def __init__(self, *args, **kwargs):
        super(RouterFactory, self).__init__(*args, **kwargs)
        self.clients = {}
    def register(self, client):
        """用户注册"""        
        self.clients[client.peer] = {"object": client, "friend": None}
    def unregister(self, client):
        """用户注销"""
        del self.clients[client.peer]
    def find_friend(self, client):
        """寻找好友"""
        friend = [c for c in self.clients if c != client.peer and not self.clients[c]["friend"]]
        if not friend:
            client.sendMessage(u"{0} 您暂时还没有好友在线".format(client.peer).encode("utf-8"))
        else:
            friend_choice = random.choice(friend)
            self.clients[friend_choice]["friend"] = client
            self.clients[client.peer]["friend"] = self.clients[friend_choice]["object"]
    def communicate(self, client, payload, isBinary):
        """传输信息"""
        f = self.clients[client.peer]
        if not f["friend"]:
            f["object"].sendMessage(u"请先连接好友。")
        else:
            f["friend"].sendMessage(payload, isBinary)

if __name__ == "__main__":
    log.startLogging(sys.stdout)
    # 默认为index.html
    root = File(".")
    factory = RouterFactory(u"ws://127.0.0.1:8080")
    factory.protocol = ServerProtocol
    resource = WebSocketResource(factory)
    root.putChild(u"ws", resource)
    site = Site(root)
    reactor.listenTCP(8080, site)
    reactor.run()

        执行 python server.py, 在浏览器打开多个http://127.0.0.1:8080, 就可以和自己聊天了。

        代码下载

基于 Django 搭建

服务器采用的 阿里云

域名来自 万网

苏ICP备16015443号

©2015-2016 felixglow.com.

GitHub

Design by Felix