Mô hình luồng và khóa trình thông dịch toàn cầu luôn cản trở Python xử lý hàng nghìn kết nối tồn tại lâu dài đồng thời. Các khung web hiện đại, chẳng hạn như Tornado, sử dụng I/O mạng không chặn để làm cho Python khả thi trong việc triển khai các máy chủ WebSocket. Trong bài viết này, kỹ sư Jongwook Kim của Toptal hướng dẫn chúng ta quy trình triển khai một ứng dụng web dựa trên WebSocket đơn giản bằng Python bằng cách sử dụng khung web Tornado
Qua
Jongwook Kim
Jongwook có hơn 12 năm kinh nghiệm phát triển phần mềm đơn giản và mạnh mẽ mang lại giá trị cho khách hàng
CHIA SẺ
CHIA SẺ
Với sự gia tăng mức độ phổ biến của các ứng dụng web thời gian thực, WebSockets đã trở thành một công nghệ chủ chốt trong việc triển khai chúng. Cái thời mà bạn phải liên tục nhấn nút tải lại để nhận các bản cập nhật từ máy chủ đã qua lâu rồi. Các ứng dụng web muốn cung cấp các bản cập nhật theo thời gian thực không còn phải thăm dò ý kiến của máy chủ về các thay đổi - thay vào đó, các máy chủ đẩy các thay đổi xuống luồng khi chúng xảy ra. Các khung web mạnh mẽ đã bắt đầu hỗ trợ WebSockets ngay lập tức. Chẳng hạn, Ruby on Rails 5 đã tiến xa hơn nữa và thêm hỗ trợ cho cáp hành động
Trong thế giới của Python, tồn tại nhiều web framework phổ biến. Các khung như Django cung cấp gần như mọi thứ cần thiết để xây dựng các ứng dụng web và mọi thứ mà nó thiếu đều có thể được bù đắp bằng một trong hàng nghìn plugin có sẵn cho Django. Tuy nhiên, do cách thức hoạt động của Python hoặc hầu hết các khung web của nó, việc xử lý các kết nối tồn tại lâu dài có thể nhanh chóng trở thành cơn ác mộng. Mô hình luồng và khóa trình thông dịch toàn cầu thường được coi là gót chân achille của Python
Nhưng tất cả điều đó đã bắt đầu thay đổi. Với một số tính năng mới nhất định của Python 3 và các khung đã tồn tại cho Python, chẳng hạn như Tornado, việc xử lý các kết nối tồn tại lâu dài không còn là một thách thức nữa. Tornado cung cấp các khả năng của máy chủ web bằng Python, đặc biệt hữu ích trong việc xử lý các kết nối tồn tại lâu dài
Trong bài viết này, chúng ta sẽ xem cách một máy chủ WebSocket đơn giản có thể được xây dựng bằng Python bằng Tornado. Ứng dụng demo sẽ cho phép chúng tôi tải lên tệp giá trị được phân tách bằng tab [TSV], phân tích cú pháp và cung cấp nội dung của tệp tại một URL duy nhất
Lốc xoáy và WebSockets
Tornado là một thư viện mạng không đồng bộ và chuyên xử lý mạng hướng sự kiện. Vì nó có thể chứa hàng chục nghìn kết nối mở đồng thời một cách tự nhiên, một máy chủ có thể tận dụng lợi thế này và xử lý rất nhiều kết nối WebSocket trong một nút duy nhất. WebSocket là một giao thức cung cấp các kênh giao tiếp song công hoàn toàn qua một kết nối TCP. Vì nó là một ổ cắm mở, kỹ thuật này làm cho kết nối web có trạng thái và tạo điều kiện truyền dữ liệu theo thời gian thực đến và từ máy chủ. Máy chủ, giữ trạng thái của máy khách, giúp dễ dàng triển khai các ứng dụng trò chuyện thời gian thực hoặc trò chơi trên web dựa trên WebSockets
WebSockets được thiết kế để triển khai trong trình duyệt web và máy chủ và hiện được hỗ trợ trong tất cả các trình duyệt web chính. Một kết nối được mở một lần và các tin nhắn có thể di chuyển qua lại nhiều lần trước khi đóng kết nối
Cài đặt Tornado khá đơn giản. Nó được liệt kê trong PyPI và có thể được cài đặt bằng pip hoặc easy_install
pip install tornado
Tornado đi kèm với việc triển khai WebSockets của riêng nó. Đối với mục đích của bài viết này, đây là tất cả những gì chúng ta cần
WebSockets đang hoạt động
Một trong những lợi thế của việc sử dụng WebSocket là thuộc tính trạng thái của nó. Điều này thay đổi cách chúng ta thường nghĩ về giao tiếp máy khách-máy chủ. Một trường hợp sử dụng cụ thể của điều này là khi máy chủ được yêu cầu thực hiện các quy trình chậm kéo dài và dần dần truyền kết quả trở lại máy khách
Trong ứng dụng ví dụ của chúng tôi, người dùng sẽ có thể tải tệp lên thông qua WebSocket. Trong toàn bộ thời gian kết nối, máy chủ sẽ giữ lại tệp được phân tích cú pháp trong bộ nhớ. Khi có yêu cầu, máy chủ sau đó có thể gửi lại các phần của tệp tới giao diện người dùng. Hơn nữa, tệp sẽ được cung cấp tại một URL mà sau đó nhiều người dùng có thể xem được. Nếu một tệp khác được tải lên tại cùng một URL, mọi người đang xem nó sẽ có thể thấy tệp mới ngay lập tức
Đối với giao diện người dùng, chúng tôi sẽ sử dụng AngularJS. Khung và thư viện này sẽ cho phép chúng tôi dễ dàng xử lý tệp tải lên và phân trang. Tuy nhiên, đối với mọi thứ liên quan đến WebSockets, chúng tôi sẽ sử dụng các hàm JavaScript tiêu chuẩn
Ứng dụng đơn giản này sẽ được chia thành ba tệp riêng biệt
- trình phân tích cú pháp. py. nơi máy chủ Tornado của chúng tôi với trình xử lý yêu cầu được triển khai
- mẫu/chỉ mục. html. mẫu HTML mặt trước
- tĩnh/trình phân tích cú pháp. js. Đối với JavaScript mặt trước của chúng tôi
Mở một WebSocket
Từ giao diện người dùng, có thể thiết lập kết nối WebSocket bằng cách khởi tạo một đối tượng WebSocket
new WebSocket[WEBSOCKET_URL];
Đây là điều chúng ta sẽ phải làm khi tải trang. Khi một đối tượng WebSocket được khởi tạo, các trình xử lý phải được đính kèm để xử lý ba sự kiện quan trọng
- mở ra. được kích hoạt khi kết nối được thiết lập
- thông điệp. được kích hoạt khi nhận được tin nhắn từ máy chủ
- Thoát. được kích hoạt khi kết nối bị đóng
$scope.init = function[] {
$scope.ws = new WebSocket['ws://' + location.host + '/parser/ws'];
$scope.ws.binaryType = 'arraybuffer';
$scope.ws.onopen = function[] {
console.log['Connected.']
};
$scope.ws.onmessage = function[evt] {
$scope.$apply[function [] {
message = JSON.parse[evt.data];
$scope.currentPage = parseInt[message['page_no']];
$scope.totalRows = parseInt[message['total_number']];
$scope.rows = message['data'];
}];
};
$scope.ws.onclose = function[] {
console.log['Connection is closed...'];
};
}
$scope.init[];
Vì các trình xử lý sự kiện này sẽ không tự động kích hoạt vòng đời $scope của AngularJS, nên nội dung của hàm xử lý cần được gói trong $apply. Trong trường hợp bạn quan tâm, tồn tại các gói cụ thể của AngularJS giúp tích hợp WebSocket trong các ứng dụng AngularJS dễ dàng hơn
Điều đáng nói là các kết nối WebSocket bị ngắt không tự động được thiết lập lại và sẽ yêu cầu ứng dụng thử kết nối lại khi trình xử lý sự kiện đóng được kích hoạt. Đây là một chút ngoài phạm vi của bài viết này
Chọn một tệp để tải lên
Vì chúng tôi đang xây dựng ứng dụng một trang bằng AngularJS, cố gắng gửi biểu mẫu bằng tệp theo cách cũ sẽ không hoạt động. Để làm cho mọi việc dễ dàng hơn, chúng tôi sẽ sử dụng thư viện ng-file-upload của Danial Farid. Sử dụng which, tất cả những gì chúng ta cần làm để cho phép người dùng tải tệp lên là thêm một nút vào mẫu giao diện người dùng của chúng ta với các chỉ thị AngularJS cụ thể
Select File
Thư viện, trong số nhiều thứ, cho phép chúng tôi đặt kích thước và phần mở rộng tệp có thể chấp nhận được. Nhấp vào nút này, giống như bất kỳ phần tử
new WebSocket[WEBSOCKET_URL];
3 nào, sẽ mở bộ chọn tệp tiêu chuẩnTải lên tệp
Khi bạn muốn truyền dữ liệu nhị phân, bạn có thể chọn giữa bộ đệm mảng và blob. Nếu nó chỉ là dữ liệu thô như tệp hình ảnh, hãy chọn blob và xử lý nó đúng cách trong máy chủ. Bộ đệm mảng dành cho bộ đệm nhị phân có độ dài cố định và tệp văn bản như TSV có thể được truyền ở định dạng chuỗi byte. Đoạn mã này cho biết cách tải tệp lên ở định dạng bộ đệm mảng
$scope.uploadFile = function[file, errFiles] {
ws = $scope.ws;
$scope.f = file;
$scope.errFile = errFiles && errFiles[0];
if [file] {
reader = new FileReader[];
rawData = new ArrayBuffer[];
reader.onload = function[evt] {
rawData = evt.target.result;
ws.send[rawData];
}
reader.readAsArrayBuffer[file];
}
}
Chỉ thị ng-file-upload cung cấp chức năng uploadFile. Tại đây, bạn có thể chuyển đổi tệp thành bộ đệm mảng bằng FileReader và gửi tệp qua WebSocket
Lưu ý rằng việc gửi các tệp lớn qua WebSocket bằng cách đọc chúng vào bộ đệm mảng có thể không phải là cách tối ưu nhất để tải chúng lên vì nó có thể nhanh chóng chiếm nhiều bộ nhớ dẫn đến trải nghiệm kém
Nhận File trên Server
Tornado xác định loại thông báo bằng cách sử dụng opcode 4 bit và trả về dữ liệu chuỗi hoặc nhị phân và unicode cho văn bản
if opcode == 0x1:
# UTF-8 data
self._message_bytes_in += len[data]
try:
decoded = data.decode["utf-8"]
except UnicodeDecodeError:
self._abort[]
return self._run_callback[self.handler.on_message, decoded]
elif opcode == 0x2:
# Binary data
self._message_bytes_in += len[data]
self._run_callback[self.handler.on_message, data]
Trong máy chủ web Tornado, bộ đệm mảng được nhận theo kiểu str
Trong ví dụ này, loại nội dung chúng tôi mong đợi là TSV, vì vậy tệp được phân tích cú pháp và chuyển thành từ điển. Tất nhiên, trong các ứng dụng thực tế, có nhiều cách hợp lý hơn để xử lý việc tải lên tùy ý
def make_message[self, page_no=1]:
page_size = 100
return {
"page_no": page_no,
"total_number": len[self.rows],
"data": self.rows[page_size * [page_no - 1]:page_size * page_no]
}
def on_message[self, message]:
if isinstance[message, str]:
self.rows = [csv.reader[[line], delimiter="\t"].next[]
for line in [x.strip[] for x in message.splitlines[]] if line]
self.write_message[self.make_message[]]
Yêu cầu một trang
Vì mục tiêu của chúng tôi là hiển thị dữ liệu TSV đã tải lên trong các đoạn trang nhỏ, nên chúng tôi cần một phương tiện để yêu cầu một trang cụ thể. Để đơn giản, chúng tôi sẽ chỉ sử dụng cùng một kết nối WebSocket để gửi số trang đến máy chủ của chúng tôi
________số 8Máy chủ sẽ nhận được thông báo này dưới dạng unicode
def on_message[self, message]:
if isinstance[message, unicode]:
page_no = int[message]
self.write_message[self.make_message[page_no]]
Cố gắng phản hồi bằng lệnh từ máy chủ Tornado WebSocket sẽ tự động mã hóa nó ở định dạng JSON. Vì vậy, bạn hoàn toàn có thể gửi một lệnh chứa 100 hàng nội dung
Chia sẻ quyền truy cập với người khác
Để có thể chia sẻ quyền truy cập vào cùng một video tải lên với nhiều người dùng, chúng tôi cần có khả năng nhận dạng duy nhất các video tải lên. Bất cứ khi nào người dùng kết nối với máy chủ qua WebSocket, một UUID ngẫu nhiên sẽ được tạo và gán cho kết nối của họ
new WebSocket[WEBSOCKET_URL];
0new WebSocket[WEBSOCKET_URL];
4 tạo một UUID ngẫu nhiên và str[] chuyển đổi một UUID thành một chuỗi các chữ số hex ở dạng tiêu chuẩnNếu một người dùng khác có UUID kết nối với máy chủ, phiên bản tương ứng của FileHandler sẽ được thêm vào từ điển với UUID làm khóa và bị xóa khi đóng kết nối
new WebSocket[WEBSOCKET_URL];
0Từ điển máy khách có thể đưa ra lỗi KeyError khi thêm hoặc xóa máy khách đồng thời. Vì Tornado là một thư viện mạng không đồng bộ, nó cung cấp các cơ chế khóa để đồng bộ hóa. Một khóa đơn giản với coroutine phù hợp với trường hợp xử lý từ điển khách hàng này
Nếu bất kỳ người dùng nào tải lên một tệp hoặc di chuyển giữa các trang, tất cả người dùng có cùng UUID sẽ xem cùng một trang
new WebSocket[WEBSOCKET_URL];
1Chạy phía sau Nginx
Việc triển khai WebSockets rất đơn giản, nhưng có một số điều khó khăn cần xem xét khi sử dụng nó trong môi trường sản xuất. Tornado là một máy chủ web, vì vậy nó có thể nhận trực tiếp yêu cầu của người dùng, nhưng triển khai nó phía sau Nginx có thể là lựa chọn tốt hơn vì nhiều lý do. Tuy nhiên, phải nỗ lực hơn một chút để có thể sử dụng WebSockets thông qua Nginx
new WebSocket[WEBSOCKET_URL];
2Hai chỉ thị
new WebSocket[WEBSOCKET_URL];
5 làm cho Nginx chuyển các tiêu đề cần thiết tới các máy chủ phụ trợ cần thiết để nâng cấp kết nối lên WebSocketCái gì tiếp theo?
Trong bài viết này, chúng tôi đã triển khai một ứng dụng web Python đơn giản sử dụng WebSockets để duy trì các kết nối liên tục giữa máy chủ và từng máy khách. Với các khung mạng không đồng bộ hiện đại như Tornado, việc nắm giữ hàng chục nghìn kết nối mở đồng thời trong Python là hoàn toàn khả thi
Mặc dù một số khía cạnh triển khai của ứng dụng demo này có thể đã được thực hiện theo cách khác, tôi hy vọng nó vẫn giúp chứng minh việc sử dụng WebSockets trong https. //www. đỉnh cao. khung com/cơn lốc xoáy. Mã nguồn của ứng dụng demo có sẵn trên GitHub
Có liên quan. Hướng dẫn đa luồng Python. Đồng thời và song song
Thẻ
PythonTornadoWebSocketsNgười làm việc tự do? Tìm công việc tiếp theo của bạn.
Công việc tự do từ xa
Xem thông tin đầy đủ
Jongwook Kim
Kỹ sư phần mềm tự do
Thông tin về các Tác giả
Với hơn chục năm kinh nghiệm, Jongwook phát triển phần mềm đơn giản và mạnh mẽ mang lại giá trị cho khách hàng. Jongwook tin rằng việc cho khách hàng thấy một phần mềm đang hoạt động trước và cùng nhau phát triển sản phẩm đó là điều quan trọng đối với quá trình phát triển, vì vậy anh hoan nghênh các yêu cầu thay đổi. Anh ấy thích giải quyết vấn đề và giao tiếp với những cá nhân năng động