Trình Thực Thi
Rust cung cấp nhiều môi trường thực thi bất đồng bộ. Tuy nhiên, các môi trường này có một đặc điểm "lây nhiễm" - nếu một hàm là bất đồng bộ, tất cả các hàm gọi nó cũng phải là bất đồng bộ. Mô hình xử lý lưu lượng trước đây của chúng ta không sử dụng tính năng bất đồng bộ. Để không ảnh hưởng phần phân tích giao thức đến mô hình xử lý, chúng ta cần một trình thực thi riêng. Trình thực thi này đóng gói các thao tác bất đồng bộ và cho phép sử dụng trong mô hình xử lý lưu lượng. Giữ thói quen và mô hình của người dùng là một tiêu chuẩn quan trọng của thư viện.
Trình thực thi trong Rust về cơ bản tương tự như hàm được điều khiển bởi máy trạng thái trong C đã được đề cập trước đây. Nếu một future trả về Ready, nó đã hoàn thành và cần thực hiện bước tiếp theo. Nếu trả về Pending, nó chưa hoàn thành và cần chờ lần gọi tiếp theo. Vì chúng ta không cần thời gian chạy bất đồng bộ phức tạp, trình thực thi này chỉ cần kiểm tra giá trị trả về của future:
let waker = dummy_waker();
let mut context = Context::from_waker(&waker);
match Pin::as_mut(parser).poll(&mut context) {
Poll::Ready(Ok(())) => {
self.c2s_state = TaskState::End;
Some(Ok(()))
}
Poll::Ready(Err(())) => {
self.c2s_state = TaskState::Error;
Some(Err(()))
}
Poll::Pending => None,
}
Cách này, bên trong là bất đồng bộ, bên ngoài là đồng bộ. Với mỗi gói dữ liệu đến, trình thực thi thực hiện một lần future (bộ giải mã). Bên ngoài không cần thao tác bất đồng bộ. Chúng ta có thể gọi trình thực thi trong mã đồng bộ:
loop {
pkt = get_packet();
task.run(pkt);
}
Để nhận kết quả giải mã, chúng ta cần đặt hàm callback trước đó. Quá trình giải mã sẽ kích hoạt hàm callback khi cần.
Bộ Giải Mã
Tất cả những công việc này đều hướng đến bộ giải mã. Vì cần xử lý đồng thời nhiều kết nối, bộ giải mã không thể chờ các gói dữ liệu tiếp theo. Do đó, bộ giải mã là một future - một hàm bất đồng bộ. Khi dữ liệu tiếp theo chưa đến, bộ giải mã trả về Pending và tạm dừng. Khi trình thực thi nhận được Pending, nó không coi kết nối này đã kết thúc mà tiếp tục xử lý kết nối tiếp theo.
Tuy nhiên, sự phức tạp của các cuộc gọi bất đồng bộ này được cơ chế future của Rust đóng gói lại. Người triển khai bộ giải mã chỉ cần tập trung vào logic giải mã. Có thể viết theo mô hình đồng bộ thông thường, như một phần mã của bộ giải mã SMTP:
loop {
let (line, seq) = stm.readline_str().await?;
if line == "\r\n" {
return Ok((boundary, te));
}
}
Đây là quá trình đọc dữ liệu header đã được tái tổ chức. Có thể thấy, nó không quan tâm đến trường hợp dữ liệu chưa đến. Nếu gói dữ liệu hiện tại không đủ dữ liệu, bộ giải mã cũng không cần kiểm tra hay thoát. Nó thực thi quy trình giải mã như đồng bộ cho đến khi đọc hết header. Chỉ khác biệt duy nhất là hàm đọc dữ liệu là hàm bất đồng bộ và có từ khóa await ở cuối.
Mỗi await kích hoạt một lần gọi future. Nếu gói dữ liệu hiện tại không đủ, await sẽ trả về Pending, bộ giải mã sẽ bị tạm dừng. Trình thực thi sẽ tiếp tục xử lý gói dữ liệu tiếp theo. Khi gói dữ liệu đến, bộ giải mã sẽ được đánh thức và tiếp tục thực thi cho đến khi đọc hết header.
Bằng cách này, chúng ta có thể giao công việc phân tích giao thức cho trình thực thi. Trình thực thi sẽ tự động xử lý các trường hợp bất đồng bộ. Người dùng chỉ cần tập trung vào việc triển khai bộ giải mã. Quá trình giải mã trở nên đơn giản và dễ hiểu hơn.
Từ vòng lặp lớn trong C ghi nhận trạng thái, đến máy trạng thái trong C, đến cơ chế future của Rust, và giờ là bộ giải mã, quy trình ngày càng đơn giản. Cuối cùng, chúng ta đã thực hiện quá trình giải mã bất đồng bộ bằng mã đồng bộ kiểu nhật ký đơn giản.
Tham khảo: protolens@gitee protolens@github