sách gpt4 ai đã đi

Thực hành Netty (Phần 3)

In lại Tác giả: Tôi là một chú chim nhỏ Thời gian cập nhật: 2023-05-25 14:31:30 29 4
mua khóa gpt4 Nike

Mục lục
  • 1. Channel, EventLoop và ChannelFuture
    • 1.1 Giao diện kênh
    • 1.2 Giao diện EventLoop
    • 1.3 Giao diện ChannelFuture
  • 2. ChannelHandler và ChannelPipeline
    • 2.1 Giao diện ChannelHandler
    • 2.2 Giao diện ChannelPipeline
    • 2.3 Bộ mã hóa và giải mã
    • 2.4 Lớp trừu tượng SimpleChannelInboundHandler
  • 3. Hướng dẫn

1. Channel, EventLoop và ChannelFuture

Trong bài đăng trên blog trước, chúng tôi đã giới thiệu một số lớp mới về xây dựng máy chủ và máy khách. Một số học viên có thể không hiểu các chức năng cụ thể của chúng. Không sao, chúng ta hãy thêm chi tiết vào cuộc thảo luận của chúng ta về các lớp Channel, EventLoop và ChannelFuture, khi kết hợp lại có thể được coi là đại diện cho sự trừu tượng hóa mạng Netty:

  • Kênh : Ổ cắm;
  • EventLoop: kiểm soát luồng, đa luồng, đồng thời;
  • ChannelFuture: Thông báo không đồng bộ.

1.1 Giao diện kênh

Các hoạt động I/O cơ bản (bind(), connect(), read() và write()) dựa vào các nguyên hàm do phương thức vận chuyển mạng cơ bản cung cấp. Trong lập trình mạng dựa trên Java, cấu trúc cơ bản là lớp Socket. API do giao diện Channel của Netty cung cấp giúp giảm đáng kể độ phức tạp khi sử dụng trực tiếp lớp Socket. Ngoài ra, Channel là gốc của một hệ thống phân cấp lớp mở rộng với nhiều triển khai chuyên biệt được xác định trước, sau đây là danh sách một phần ngắn gọn:

  • Kênh nhúng;
  • Kênh Máy chủ cục bộ;
  • Kênh NioDatagram;
  • Kênh NioSctp;
  • Kênh NioSocket.

1.2 Giao diện EventLoop

EventLoop định nghĩa khái niệm trừu tượng cốt lõi của Netty, được sử dụng để xử lý các sự kiện xảy ra trong suốt vòng đời của một kết nối. Hình minh họa mối quan hệ giữa Channel, EventLoop, Thread và EventLoopGroup ở cấp độ cao.

chèn mô tả hình ảnh ở đây

Những mối quan hệ này có thể được thể hiện như sau:

  • EventLoopGroup chứa một hoặc nhiều EventLoop;
  • EventLoop chỉ được liên kết với một Luồng trong suốt vòng đời của nó;
  • Tất cả các sự kiện I/O được EventLoop xử lý sẽ được xử lý trên Luồng chuyên dụng của nó;
  • Mỗi Kênh chỉ được đăng ký với một EventLoop trong suốt vòng đời của nó;
  • EventLoop có thể được gán cho một hoặc nhiều Kênh.

Lưu ý rằng trong thiết kế này, tất cả các hoạt động I/O cho một Kênh nhất định đều được thực hiện bởi cùng một Luồng, do đó loại bỏ nhu cầu đồng bộ hóa.

1.3 Giao diện ChannelFuture

Tất cả các hoạt động I/O trong Netty đều không đồng bộ. Vì một hoạt động có thể không trả về kết quả ngay lập tức nên chúng ta cần có cách để xác định kết quả của nó tại một thời điểm sau đó. Để đạt được mục đích này, Netty cung cấp giao diện ChannelFuture, trong đó phương thức addListener() đăng ký ChannelFutureListener để được thông báo khi một hoạt động hoàn tất (cho dù thành công hay không).

ChannelFuture có thể được coi như một trình giữ chỗ cho kết quả của một hoạt động sẽ được thực hiện trong tương lai. Thời điểm chính xác nó sẽ được thực hiện có thể phụ thuộc vào một số yếu tố và do đó không thể dự đoán chắc chắn, nhưng chắc chắn là nó sẽ được thực hiện. Hơn nữa, tất cả các hoạt động thuộc cùng một Kênh đều được đảm bảo thực hiện theo thứ tự chúng được gọi.

2. ChannelHandler và ChannelPipeline

2.1 Giao diện ChannelHandler

Theo quan điểm của nhà phát triển ứng dụng, thành phần chính của Netty là ChannelHandler, hoạt động như một bộ chứa cho tất cả logic ứng dụng xử lý dữ liệu đến và đi. Điều này có thể thực hiện được vì các phương thức ChannelHandler được kích hoạt bởi các sự kiện mạng (trong đó thuật ngữ "sự kiện" được sử dụng rất lỏng lẻo). Trên thực tế, ChannelHandler có thể chuyên biệt cho hầu hết mọi loại hành động, chẳng hạn như chuyển đổi dữ liệu từ định dạng này sang định dạng khác hoặc xử lý các ngoại lệ phát sinh trong quá trình chuyển đổi.

Ví dụ, ChannelInboundHandler là một giao diện phụ mà chúng ta thường triển khai. Loại ChannelHandler này tiếp nhận các sự kiện và dữ liệu đến, sau đó được xử lý bởi logic kinh doanh của ứng dụng. Chúng ta cũng có thể xóa dữ liệu khỏi ChannelInboundHandler khi muốn gửi phản hồi tới máy khách được kết nối. Logic kinh doanh của ứng dụng của chúng tôi thường nằm trong một hoặc nhiều ChannelInboundHandlers.

Netty cung cấp một số lượng lớn các triển khai ChannelHandler mặc định dưới dạng các lớp bộ điều hợp, được thiết kế để đơn giản hóa quá trình phát triển logic xử lý ứng dụng. Ví dụ, mỗi ChannelHandler trong ChannelPipeline sẽ chịu trách nhiệm chuyển tiếp sự kiện đến ChannelHandler tiếp theo trong chuỗi. Các lớp bộ điều hợp (và các lớp con của chúng) sẽ tự động thực hiện việc này, vì vậy chúng ta chỉ cần ghi đè các phương thức và sự kiện mà bạn muốn xử lý đặc biệt.

Vậy tại sao lại cung cấp chúng dưới dạng bộ chuyển đổi?

Đó là vì có các lớp bộ điều hợp có thể giảm thiểu công sức cần thiết để viết ChannelHandler tùy chỉnh vì chúng cung cấp các triển khai mặc định của tất cả các phương thức được xác định trong các giao diện tương ứng. Sau đây là các lớp bộ điều hợp thường được sử dụng khi viết ChannelHandlers tùy chỉnh:

  • Bộ xử lý kênh
  • Bộ điều hợp xử lý kênh trong kênh
  • KênhOutboundHandlerAdapter
  • Trình xử lý ChannelDuplex

2.2 Giao diện ChannelPipeline

ChannelPipeline cung cấp một vùng chứa cho chuỗi ChannelHandler và định nghĩa một API để truyền các luồng sự kiện đến và đi qua chuỗi đó. Khi một Kênh được tạo, nó sẽ tự động được gán cho ChannelPipeline của riêng nó. Quá trình cài đặt ChannelHandler vào ChannelPipeline như sau:

  • Một triển khai ChannelInitializer được đăng ký với ServerBootstrap hoặc được sử dụng trong Bootstrap phía máy khách
  • Khi phương thức ChannelInitializer.initChannel() được gọi, ChannelInitializer sẽ cài đặt một bộ ChannelHandler tùy chỉnh trong ChannelPipeline;
  • ChannelInitializer tự xóa khỏi ChannelPipeline.

Để xem xét điều gì xảy ra khi gửi hoặc nhận dữ liệu, chúng ta hãy xem xét kỹ hơn mối quan hệ cộng sinh giữa ChannelPipeline và ChannelHandler.

ChannelHandler được thiết kế để hỗ trợ nhiều mục đích sử dụng khác nhau và có thể được coi như một bộ chứa mục đích chung cho bất kỳ mã nào xử lý các sự kiện (bao gồm dữ liệu) đến và đi từ ChannelPipeline. Như thể hiện trong hình, nó hiển thị các giao diện ChannelInboundHandler và ChannelOutboundHandler được bắt nguồn từ ChannelHandler. Nhiệm vụ của ChannelHandlers là đưa các sự kiện chạy qua ChannelPipeline, được cài đặt trong giai đoạn khởi tạo hoặc khởi động của ứng dụng. Các đối tượng này nhận sự kiện, thực thi logic xử lý mà chúng triển khai và truyền dữ liệu đến ChannelHandler tiếp theo trong chuỗi (tương tự như mô hình Chuỗi trách nhiệm). Thứ tự thực hiện của chúng được xác định bởi thứ tự chúng được thêm vào. Trên thực tế, thứ chúng ta gọi là ChannelPipeline chính là thứ tự sắp xếp của các ChannelHandler này.

Hình bên dưới minh họa sự khác biệt giữa luồng dữ liệu vào và ra trong ứng dụng Netty. Theo quan điểm của ứng dụng khách, nếu hướng di chuyển của các sự kiện là từ khách đến máy chủ, thì chúng ta gọi những sự kiện này là hướng ra ngoài và ngược lại, chúng được gọi là hướng vào trong. Từ hình trên, chúng ta có thể thấy ChannelHandler inbound và outbound có thể được cài đặt trong cùng một ChannelPipeline. Nếu một thông báo hoặc bất kỳ sự kiện đến nào được đọc, nó sẽ bắt đầu truyền từ đầu ChannelPipeline và được chuyển đến ChannelInboundHandler đầu tiên. ChannelHandler này có thể hoặc không thể sửa đổi dữ liệu, tùy thuộc vào chức năng cụ thể của nó, sau đó dữ liệu sẽ được chuyển đến ChannelInboundHandler tiếp theo trong chuỗi. Cuối cùng, dữ liệu sẽ đến cuối ChannelPipeline, tại thời điểm đó mọi quá trình xử lý đều hoàn tất.

Về mặt khái niệm, việc di chuyển dữ liệu ra ngoài (tức là dữ liệu được ghi) cũng giống như vậy. Trong trường hợp này, dữ liệu sẽ chạy từ cuối chuỗi ChannelOutboundHandler cho đến khi đến đầu chuỗi. Sau đó, dữ liệu gửi đi sẽ đến lớp truyền tải mạng, được hiển thị ở đây dưới dạng Socket. Thông thường, điều này sẽ kích hoạt thao tác ghi.

ps: Bằng cách sử dụng ChannelHandlerContext được truyền dưới dạng tham số cho mỗi phương thức, sự kiện có thể được truyền tới ChannelHandler tiếp theo trong chuỗi ChannelHandler hiện tại. Vì đôi khi bạn muốn bỏ qua các sự kiện mà bạn không quan tâm, Netty cung cấp các lớp cơ sở trừu tượng ChannelInboundHandlerAdapter và ChannelOutboundHandlerAdapter. Mỗi phương thức tương ứng trên ChannelHandlerContext đều cung cấp một triển khai của phương thức đó để chỉ truyền sự kiện đến ChannelHandler tiếp theo. Sau đó chúng ta có thể mở rộng các lớp này bằng cách ghi đè các phương thức mà bạn quan tâm.

Trong hình trên, cả ChannelHandlers outbound và inbound đều nằm trong cùng một ChannelPipeline. Vậy ChannelPipeline phân biệt và xử lý hai loại khác nhau này như thế nào?

Mặc dù cả ChannelInboundHandle và ChannelOutboundHandle đều mở rộng ChannelHandler, Netty có thể phân biệt giữa các triển khai ChannelInboundHandler và các triển khai ChannelOutboundHandler và đảm bảo rằng dữ liệu chỉ được truyền giữa hai ChannelHandler có cùng kiểu định hướng.

Khi ChannelHandler được thêm vào ChannelPipeline, nó sẽ được gán một ChannelHandlerContext, biểu diễn sự ràng buộc giữa ChannelHandler và ChannelPipeline. Mặc dù đối tượng này có thể được sử dụng để lấy Kênh cơ bản, nhưng nó chủ yếu được sử dụng để ghi dữ liệu ra.

Trong Netty, có hai cách để gửi tin nhắn. Chúng ta có thể ghi trực tiếp vào Channel hoặc có thể ghi vào đối tượng ChannelHandlerContext được liên kết với ChannelHandler. Phương pháp trước sẽ khiến thông báo chạy từ cuối ChannelPipeline, trong khi phương pháp sau sẽ khiến thông báo chạy từ ChannelHandler tiếp theo trong ChannelPipeline.

Tóm lại:

  • Viết tin nhắn tới Kênh và tin nhắn sẽ bắt đầu chảy từ cuối.
  • Viết một thông điệp tới ChannelHandler và thông điệp đó sẽ bắt đầu truyền từ ChannelHandler tiếp theo.

2.3 Bộ mã hóa và giải mã

Khi chúng tôi gửi hoặc nhận tin nhắn qua Netty, quá trình chuyển đổi dữ liệu sẽ diễn ra. Các tin nhắn đến được giải mã; nghĩa là chuyển đổi từ byte sang định dạng khác, thường là đối tượng Java. Nếu là tin nhắn gửi đi, quá trình chuyển đổi sẽ diễn ra theo hướng ngược lại: tin nhắn sẽ được mã hóa thành byte theo định dạng hiện tại. Lý do chuyển đổi theo cả hai hướng rất đơn giản: dữ liệu mạng luôn là một chuỗi byte. (Bộ giải mã).

Netty cung cấp nhiều loại lớp trừu tượng khác nhau cho bộ mã hóa và giải mã để đáp ứng các nhu cầu cụ thể. Ví dụ, ứng dụng của chúng tôi có thể sử dụng định dạng trung gian và không cần phải chuyển đổi tin nhắn thành byte ngay lập tức. Chúng ta vẫn cần một bộ mã hóa, nhưng nó sẽ được bắt nguồn từ một siêu lớp khác. Để xác định loại mã hóa thích hợp, chúng ta có thể áp dụng quy ước đặt tên đơn giản. Thông thường, các lớp cơ sở này sẽ được đặt tên như ByteToMessageDecoder hoặc MessageToByteEncoder. Đối với các loại đặc biệt, chúng ta sẽ tìm thấy những cái tên như ProtobufEncoder và ProtobufDecoder — được xây dựng sẵn để hỗ trợ Protocol Buffers của Google.

Nói một cách chính xác, các bộ xử lý khác cũng có thể thực hiện chức năng mã hóa và giải mã. Tuy nhiên, cũng giống như các lớp bộ điều hợp để đơn giản hóa việc tạo ChannelHandler, tất cả các lớp bộ điều hợp mã hóa/giải mã do Netty cung cấp đều triển khai giao diện ChannelOutboundHandler hoặc ChannelInboundHandler.

Chúng ta sẽ thấy rằng đối với dữ liệu đến, phương thức/sự kiện channelRead đã bị ghi đè. Phương pháp này sẽ được gọi cho mỗi tin nhắn được đọc từ Kênh đến. Sau đó, nó sẽ gọi phương thức decode() do bộ giải mã dựng sẵn cung cấp và chuyển tiếp các byte đã giải mã đến ChannelInboundHandler tiếp theo trong ChannelPipeline. Mẫu cho các tin nhắn gửi đi theo hướng ngược lại: bộ mã hóa chuyển đổi tin nhắn thành byte và chuyển tiếp chúng đến ChannelOutboundHandler tiếp theo.

2.4 Lớp trừu tượng SimpleChannelInboundHandler

Thông thường nhất, ứng dụng của chúng tôi sẽ sử dụng ChannelHandler để nhận các thông điệp đã giải mã và áp dụng logic kinh doanh vào dữ liệu. Để tạo ra ChannelHandler như vậy, chúng ta chỉ cần mở rộng lớp cơ sở SimpleChannelInboundHandler, trong đó T là kiểu Java của thông báo mà chúng ta muốn xử lý. Trong ChannelHandler này, chúng ta cần ghi đè một hoặc nhiều phương thức của lớp cơ sở và lấy tham chiếu đến ChannelHandlerContext, tham chiếu này sẽ được truyền dưới dạng tham số đầu vào cho tất cả các phương thức của ChannelHandler.

Trong loại ChannelHandler này, phương thức quan trọng nhất là channelRead0(ChannelHandlerContext, T). Ngoài yêu cầu không chặn luồng I/O hiện tại, việc triển khai chính xác hoàn toàn phụ thuộc vào chúng ta.

3. Hướng dẫn

Lớp bootstrap của Netty cung cấp một vùng chứa để cấu hình lớp mạng của ứng dụng, bao gồm việc liên kết một tiến trình với một cổng được chỉ định (máy chủ) hoặc kết nối một tiến trình với một tiến trình khác đang chạy trên một cổng được chỉ định trên một máy chủ được chỉ định (máy khách).

Nói một cách chính xác, thuật ngữ "kết nối" chỉ áp dụng cho các giao thức hướng kết nối, chẳng hạn như TCP, đảm bảo việc phân phối tin nhắn theo thứ tự giữa hai điểm cuối kết nối.

Do đó, có hai loại bootstrap: một loại dành cho máy khách (gọi đơn giản là Bootstrap) và một loại khác (ServerBootstrap) dành cho máy chủ. Bất kể ứng dụng của chúng ta sử dụng giao thức nào hoặc xử lý loại dữ liệu nào, điều duy nhất xác định lớp bootstrap nào được sử dụng là liệu ứng dụng đó hoạt động như một máy khách hay máy chủ (chúng ta sẽ giải thích riêng về bootstrap sau).

loại Khởi động Máy chủBootstrap
Vai trò trong lập trình mạng Kết nối tới máy chủ và cổng từ xa Liên kết với một cổng cục bộ
Số lượng EventLoopGroups 1 2

ps: Trên thực tế, lớp ServerBootstrap cũng chỉ có thể sử dụng một EventLoopGroup, trong trường hợp đó, nó sẽ chia sẻ cùng một EventLoopGroup trong cả hai trường hợp.

Những sinh viên cẩn thận sẽ phát hiện ra rằng ServerBootstrap sử dụng hai EventLoopGroup vì máy chủ yêu cầu hai bộ Kênh khác nhau.

  • Nhóm đầu tiên sẽ chỉ chứa một ServerChannel, đại diện cho socket lắng nghe của máy chủ được liên kết với một cổng cục bộ. (Được sử dụng chuyên biệt để tạo Kênh)
  • Nhóm thứ hai sẽ chứa tất cả các Kênh đã được tạo để xử lý các kết nối đến của máy khách (một kênh cho mỗi kết nối mà máy chủ đã chấp nhận). (Phân bổ EventLoop đặc biệt cho Channel)

Mối quan hệ của chúng được thể hiện trong hình: EventLoopGroup được liên kết với ServerChannel sẽ được chỉ định một EventLoop chịu trách nhiệm tạo Kênh cho các yêu cầu kết nối đến. Sau khi kết nối được chấp nhận, EventLoopGroup thứ hai sẽ gán một EventLoop cho Kênh của nó.

Cuối cùng, bài viết này về Netty Practice (Phần 3) kết thúc tại đây. Nếu bạn muốn biết thêm về Netty Practice (Phần 3), vui lòng tìm kiếm các bài viết trên CFSDN hoặc tiếp tục duyệt các bài viết liên quan. Tôi hy vọng bạn sẽ ủng hộ blog của tôi trong tương lai! .

29 4 0
Tôi là một chú chim nhỏ
Hồ sơ cá nhân

Tôi là một lập trình viên xuất sắc, rất giỏi!

Nhận phiếu giảm giá Didi Taxi miễn phí
Mã giảm giá Didi Taxi
Giấy chứng nhận ICP Bắc Kinh số 000000
Hợp tác quảng cáo: 1813099741@qq.com 6ren.com