Echtzeitanwendungen wie WebSockets werden jeden Tag verwendet, jetzt mehr denn je mit dem Aufkommen von Social-Media-Websites wie Twitter, Google+ und Facebook. Server pushen Updates an Clients, anstatt in regelmäßigen Abständen Änderungen abzufragen – dies wird als „WebSocket“ bezeichnet. Alles in allem war es ein fantastischer Fortschritt, aber es hat seine Nachteile, die sich für Python-Entwickler als problematisch erwiesen haben. Dieser Artikel hilft Ihnen beim Erstellen eines Websocket-Servers mit Python.
Mit den neuesten Entwicklungen der Python-Sprache und mit Hilfe von Frameworks wie Tornado ist die Handhabung von Websocket- oder HTTP/2-Verbindungen viel einfacher geworden. Tornado ist eine Python-Netzwerkbibliothek, die es Entwicklern ermöglicht, HTTP-Server in Python zu erstellen, und die ihnen die Möglichkeit bietet, WebSocket- und andere Netzwerkübertragungen einfacher zu bewältigen. Tornado bietet Webserver-Fähigkeiten in Python, die es erfahrenen Entwicklern ermöglichen, diese Verbindungen besser zu handhaben.
Dieser Artikel veranschaulicht die Konzepte der asynchronen Programmierung und der Handhabung mehrerer Verbindungen. Wir zeigen Ihnen, wie Sie einen einfachen Chatroom-Server einrichten und mehreren Clients erlauben, sich mit ihm zu verbinden.
Was sind Websockets?
WebSocket ist ein Kommunikationsprotokoll, das Vollduplex-Kommunikation über eine einzelne TCP-Verbindung bereitstellt. WebSocket ist eine Kombination von Technologien, die eine vollständige bidirektionale Kommunikation zwischen einem Browser und einem Server ermöglicht. Dies erleichtert Datenübertragungen in Echtzeit, da Sie die Client- und Serveraktivitäten ohne Verzögerung gleichzeitig steuern können. Es hat einen geringeren Overhead als Halbduplex-Alternativen wie HTTP-Polling und erleichtert die Datenübertragung in Echtzeit vom und zum Server.
Die geringe Latenz und Multidirektionalität von WebSockets sind besonders nützlich für Anwendungen, die Nachrichten senden oder empfangen müssen, während sie Antworten vom Server erhalten. Da WebSockets mit einer HTTP-Anforderung beginnen, sind sie zu 100 % mit der vorhandenen Webinfrastruktur kompatibel und können sogar auf demselben Port ausgeführt werden wie Ihre vorhandenen Webanforderungen, z. B. diejenigen, die über die Ports 80 oder 443 (zum Beispiel) gestellt werden. Diese Kompatibilität vereinfacht die ursprungsübergreifende Sicherheit, was bedeutet, dass niemand in der client- oder serverseitigen Infrastruktur seine Netzwerke ändern muss, um eine sichere Kommunikation mit WebSockets zu unterstützen.
Voraussetzungen
Wenn Sie Ihrer Anwendung WebSocket-Unterstützung hinzufügen, müssen Sie einen Webserver wie Tornado verwenden, der über eine integrierte WebSocket-Unterstützung verfügt.
Tornado wird mit einer eigenen Implementierung von WebSocket geliefert, die beim Erstellen eines Websocket-Servers mit Python hilft. Für die Zwecke dieses Artikels ist dies genau das, was wir brauchen. Wenn Sie keine Erfahrung und keine Ahnung mit diesem Zeug haben, ist es vielleicht keine gute Idee, das Rad neu zu erfinden. Stattdessen wäre es der beste Ansatz für Sie, mit einem funktionierenden Beispiel zu beginnen und es zu verstehen, und genau das werden wir hier tun!
Der Code verwendet das Konzept von WebSockets und der Tornado-Netzwerkbibliothek unter Verwendung der Python-Sprache, und das ist alles, was wir brauchen. Wir können WebSockets und Tornado mit den folgenden Pip-Befehlen installieren:
pip install tornado
pip install websockets
Einrichten der main.py-Datei
Der Chatroom-Server und der Chatroom-Client sind der Einfachheit halber zusammen in einer einzigen Datei implementiert. Erstellen Sie zunächst eine Datei main.py und importieren Sie die erforderlichen Module, um einen Websocket-Server mit Python zu erstellen:
#main.py
import tornado.escape #for escaping/unescaping methods for HTML, JSON, URLs, etc
import tornado.ioloop #event loop for non-blocking sockets
import tornado.options #command line parsing module to define options
import tornado.web #provides a simple web framework with asynchronous featuresfrom tornado.options import define, options
import tornado.websocket #implementation of the WebSocket protocol and bidirectional communication
import logging
import os.path
import uuid
from tornado.options import define, options
Definieren der restlichen Funktionen, einschließlich der Kundenseite. Alle Dinge, die Sie möglicherweise wissen müssen, sind im unten angegebenen Code vordefiniert:
define("port", default=8080, type=int) #setting the port to 8000 which can easily be changed
class Application(tornado.web.Application): #setting a tornado class as a web application
def __init__(self):
handlers = [(r"/", MainHandler), (r"/chatsocket", ChatSocketHandler)] #setting the nesassary urls
settings = dict(
cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
template_path=os.path.join(os.path.dirname(__file__), "templates"), #providing the templates path
static_path=os.path.join(os.path.dirname(__file__), "static"), #providing the static folder's path
xsrf_cookies=True,
)
super().__init__(handlers, **settings)
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html", messages=ChatSocketHandler.cache)
Definieren des WebSocket-Handshakes
class ChatSocketHandler(tornado.websocket.WebSocketHandler): #creating our main websocket class
waiters = set() #set number of joinable users
cache = []
cache_size = 200
def get_compression_options(self):
#non-none enables compression with default options.
return {}
def open(self): #accepts a connection request and stores the parameters, a socket object for that user
ChatSocketHandler.waiters.add(self)
def on_close(self): #removes the user by removing the object
ChatSocketHandler.waiters.remove(self)
@classmethod
def update_cache(cls, chat): #Maintains a list of chat for broadcasting the messages
cls.cache.append(chat)
if len(cls.cache) > cls.cache_size:
cls.cache = cls.cache[-cls.cache_size :]
@classmethod
def send_updates(cls, chat): #mange sending messages
logging.info("sending message to %d waiters", len(cls.waiters))
for waiter in cls.waiters:
try:
waiter.write_message(chat) #outputting the messages
except: #except, in case of any errors
logging.error("Error sending message", exc_info=True)
def on_message(self, message):
logging.info("got message %r", message)
parsed = tornado.escape.json_decode(message)
chat = {"id": str(uuid.uuid4()), "body": parsed["body"]}
chat["html"] = tornado.escape.to_basestring(
self.render_string("message.html", message=chat)
)
ChatSocketHandler.update_cache(chat)
ChatSocketHandler.send_updates(chat)
Das WebSocket-Protokoll definiert zwei spezielle Arten von Übertragungen: den WebSocket-Handshake (send_updates) und die WebSocket-Nachricht (on_message). Der WebSocket-Handshake wird verwendet, um eine Verbindung zwischen Client und Server herzustellen, während die WebSocket-Nachricht gesendet wird, nachdem die Verbindung hergestellt wurde.
def main(): #and to close up everything
tornado.options.parse_command_line()
app = Application()
app.listen(options.port)
tornado.ioloop.IOLoop.current().start()
if __name__ == "__main__":
main()
Schließen Sie schließlich die Hauptfunktion (die die WebSockets enthält).
Vorlagen einrichten
Nachdem wir die obige Datei erstellt haben, müssen wir nun die Vorlage und die statischen Dateien erstellen. Wie zuvor definiert, müssen wir zuerst die Ordner erstellen, in denen diese Dateien gespeichert werden, die „Vorlagen“ und „Statisch“ heißen würden. Wir können damit beginnen, die Vorlagen wie gezeigt zu erstellen:
#templates/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Tornado Chat Demo</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>
<div id="body">
<div id="inbox">
{% for message in messages %}
{% include "message.html" %}
{% end %}
</div>
<div id="input">
<form action="/a/message/new" method="post" id="messageform">
<table>
<tr>
<td><input type="text" name="body" id="message" style="width:500px"></td>
<td style="padding-left:5px">
<input type="submit" value="{{ _("Post") }}">
<input type="hidden" name="next" value="{{ request.path }}">
{% module xsrf_form_html() %}
</td>
</tr>
</table>
</form>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js" type="text/javascript"></script>
<script src="{{ static_url("chat.js") }}" type="text/javascript"></script>
</body>
</html>
Und jetzt die statischen Dateien:
#static/chat.js
$(document).ready(function() {
if (!window.console) window.console = {};
if (!window.console.log) window.console.log = function() {};
$("#messageform").on("submit", function() {
newMessage($(this));
return false;
});
$("#messageform").on("keypress", function(e) {
if (e.keyCode == 13) {
newMessage($(this));
return false;
}
});
$("#message").select();
updater.start();
});
function newMessage(form) {
var message = form.formToDict();
updater.socket.send(JSON.stringify(message));
form.find("input[type=text]").val("").select();
}
jQuery.fn.formToDict = function() {
var fields = this.serializeArray();
var json = {}
for (var i = 0; i < fields.length; i++) {
json[fields[i].name] = fields[i].value;
}
if (json.next) delete json.next;
return json;
};
var updater = {
socket: null,
start: function() {
var url = "ws://" + location.host + "/chatsocket";
updater.socket = new WebSocket(url);
updater.socket.onmessage = function(event) {
updater.showMessage(JSON.parse(event.data));
}
},
showMessage: function(message) {
var existing = $("#m" + message.id);
if (existing.length > 0) return;
var node = $(message.html);
node.hide();
$("#inbox").append(node);
node.slideDown();
}
};
Erstellen Sie außerdem eine message.html-Datei, die einen Teil des zusätzlichen js-Codes enthält.
#template/message.html
<div class="message" id="m{{ message["id"] }}">{% module linkify(message["body"]) %}</div>
Und es ist alles bereit!
Ausgabe
Führen Sie das Projekt aus, indem Sie den Namen der Datei im Terminal ausführen, aber es wird keine Antwort ausgegeben, es sei denn, es liegt ein Fehler vor. Wir müssen zu dem Port gehen, den wir zuvor definiert haben, nämlich 8080. Öffnen Sie diesen Link http://127.0.0.1:8080/ und Sie sollten so etwas sehen:
Sie können versuchen, einen anderen Browser zu öffnen, um zu sehen, ob es funktioniert oder nicht, und nachdem Sie etwas gepostet haben, sollten Sie auch die Antworten auf Ihrem Terminal ausgedruckt sehen:
Um seine asynchronen Fähigkeiten zu überprüfen oder was wir tatsächlich mit WebSockets gemacht haben, können Sie seine Netzwerkleistung mit jedem einfachen alten Browser überprüfen:
Letzte Worte
Und das war’s, in diesem Artikel haben wir gelernt, wie man einen Websocket-Server mit Python und der Tornado-Netzwerkbibliothek erstellt. Jetzt gibt es hier viel Raum für Verbesserungen, z. B. bei der Authentifizierung. Es gibt auch viele andere Bibliotheken, die Sie ebenfalls verwenden können, aber Tornado ist bei weitem die anfängerfreundlichste und verfügt über eine Dokumentationsbibliothek. Wenn Sie weiter recherchieren möchten, ist dies der beste Ort.
Sie können sich auch auf diese Dateistruktur beziehen, wenn Sie sich verlaufen:
│ main.py
│ playbook.yml
│
├───static
│ chat.js
│
└───templates
index.html
message.html
Hier sind einige nützliche Tutorials, die Sie lesen können:
- Echtzeit-Zwischenflussschätzung für die Videoframe-Interpolation mit Python
- Extrahieren Sie gespeicherte Chrome-Passwörter und entschlüsseln Sie sie mit Python
- Vergleichen Sie zwei Bilder und markieren Sie Unterschiede mit Python
- Generieren von QR-Codes und Barcodes in Python
- Emotionserkennung mit Python
- Wie führe ich mein Python-Skript auf Docker aus?
- Wetterinformationen mit Python abrufen
- Erstellen Sie eine News-Aggregator-Site mit Python