Làm cách nào để tạo máy chủ WebSocket Python?

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ẩn

Tả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ố 8

Má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];
0

new 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ẩn

Nế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];
0

Từ đ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];
1

Chạ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];
2

Hai 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 WebSocket

Cá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ẻ

PythonTornadoWebSockets

Ngườ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

Làm cách nào để khởi động máy chủ WebSocket?

Để mở kết nối websocket, chúng tôi cần tạo WebSocket mới bằng giao thức đặc biệt ws trong url . hãy để socket = new WebSocket["ws. //javascript. info"]; Ngoài ra còn có wss được mã hóa. // giao thức. Nó giống như HTTPS cho ổ cắm web.

Python có hỗ trợ WebSocket không?

websockets là thư viện để xây dựng máy chủ và máy khách WebSocket bằng Python tập trung vào tính chính xác, đơn giản, mạnh mẽ và hiệu suất. Được xây dựng dựa trên asyncio , khung I/O không đồng bộ tiêu chuẩn của Python, nó cung cấp một API dựa trên coroutine tao nhã.

Làm cách nào để cài đặt WebSocket trong Python?

Cài đặt .
Để cài đặt python-socks để sử dụng proxy và wsaccel để tăng hiệu suất nhỏ, hãy sử dụng. pip3 cài đặt websocket-client[tùy chọn]
Để cài đặt ổ cắm web để chạy thử nghiệm đơn vị bằng máy chủ tiếng vang cục bộ, hãy sử dụng. pip3 cài đặt websocket-client[test]

Làm cách nào để tạo một WebSocket?

Tạo API WebSocket bằng bảng điều khiển Cổng API .
Đăng nhập vào bảng điều khiển Cổng API và chọn Tạo API
Trong API WebSocket, chọn Xây dựng
Trong Cài đặt, trong trường Tên API, hãy nhập tên API của bạn, ví dụ: PetStore
Nhập Biểu thức lựa chọn tuyến đường cho API của bạn, ví dụ: $request

Chủ Đề