Quy về một mối

Một socket server nên hỗ trợ các giao thức thông dụng như: TCP, UDP, Websocket và HTTP. Tuy nhiên nó không chỉ đơn giản là chúng ta cứ nhồi nhét các giao thức vào là xong. Nó còn liên quan đến việc chúng ta sẽ điều hướng dữ liệu như thế nào để quy hết về một mối và xử lý trên các controller duy nhất. Chỉ khi làm được điều đó thì socket server mới dễ dàng sử dụng và các lập trình viên mới có thể tiếp cận được.

Nhắc lại các giao thức

Có lẽ chúng ta đã quá quen với các giao thức mạng rồi nhỉ, tuy nhiên mình sẽ nhắc lại dưới góc độ của một socket server nhé.

  1. TCP: Là giao thức truyền dữ liệu đáng tin cậy, thế nên chúng ta sẽ xác thực thông qua giao thức này và sử dụng nó cho các yêu cầu quan trọng.
  2. UDP: Là giao thức truyền tin nhẹ nhàng hơn TCP, tuy nhiên nhược điểm của nó là không đáng tin cậy (không cần khởi tạo kết nối và có thể mất gói tin), nên chúng ta sẽ dùng giao thức này cho các dữ liệu cần gửi nhận nhiều và chấp nhận được mất mát như live streaming hay các game có FPS cao.
  3. Websocket: Các giao thức TCP và UDP sẽ phù hợp để cho các ứng dụng không phải web. Và thật đáng buồn là các trình duyệt hiện mới chỉ hỗ trợ tốt nhất cho websocket mà thôi, nên chúng ta buộc phải có giao thức này.
  4. HTTP: Thường thì chúng ta sẽ tổ chức HTTP thành một server độc lập, thế nên ở đây chúng ta sẽ dùng HTTP cho mục đích health check hoặc export metrics cho các datasource như prometheus mà thôi.

Điều hướng dữ liệu

Một socket server chỉ nên có duy nhất một nơi để xử lý request của người dùng thôi, vì nếu mỗi một giao thức lại phải code phần xử lý logic sẽ dẫn đến duplicate source code và không khả thi.

Vậy nên tất cả dữ liệu khi đến từ socket sẽ được deserialize thành đối tượng request, đưa vào queue và sử dụng Even Loop để gom lại dữ liệu từ các nguồn và tập trung xử lý tại một nơi duy nhất mà chúng ta vẫn hay quen gọi là tầng MVC.

Dữ liệu sau khi được xử lý xong, nếu có sinh ra các response thì cũng được đưa vào response queue và lại dùng Even Loop để gửi đến các client thông qua giao thức tương ứng. Chi tiết hơn về phần này, bạn có thể đọc bài điều hướng dữ liệu socket nhé.

Nhiều kiểu dữ liệu

Vấn đề phát sinh tiếp theo khi chúng ta muốn hỗ trợ nhiều giao thức đó là các định dạng dữ liệu. Đối với dữ liệu đến từ web sử dụng websocket thì tốt nhất nên là JSON string. Tuy nhiên dữ liệu đến từ TCP hay UDP thì lại nên là byte array và có thể sử dụng Message Pack hay Protobuf để đóng gói dữ liệu cho gọn nhẹ và hiện tại thì ezyfox-server cũng đang sử dụng Message Pack.

Như vậy chúng ta cần sinh ra 2 loại converter để deserialize các byte array thành các đối tượng request, và cũng chuyển đổi đối tượng response thành byte array theo các định dạng tương ứng với client. Ví dụ mình đang có 2 module:

  1. jackson: Dùng để chuyển đổi byte array có định dạng json thành các đối tượng request, và chuyển đối các đối tượng response thành các byte array dạng json.
  2. msgpack: Dùng để chuyển đổi byte array được đóng gói theo định dạng của Message Pack thành đối tượng request và đóng gói các đối tượng response thành byte array cũng theo Message Pack này.

Cẩn thận với UDP

Mặc dù việc xác thực của UDP đã dựa vào TCP, nghĩa là khi user login thành công bằng username, password hoặc token rồi mới đến lượt UDP, tuy nhiên các gói tin của UDP dễ bị mất và dễ bị giả mạo, thế nên chúng ta không nên dùng UDP cho các logic quan trọng ví dụ như gửi tin nhắn bí mật hay gửi các thông tin mật khác, và chúng ta cũng không nên sử dụng lẫn lộn UDP và TCP cho cùng 1 logic, 2 thứ có độ tin cậy khác nhau không nên kết hợp bừa bãi.

Tổng kết

Việc hỗ trợ nhiều giao thức cho một socket server là điều chúng ta cần phải thực hiện, vì ngày nay là thế giới của IoT, vạn vật điều có thế kết nối với nhau qua đủ mọi loại giao thức, nhưng thật may là chúng vẫn dựa trên TCP, UDP hay websocket.

Việc đưa các dữ liệu từ nhiều giao thức về để xử lý logic ở 1 nơi duy nhất tuy khó khăn nhưng chúng ta phải làm được, và thông qua Queue với Even Loop, mọi việc 1 lần nữa lại được giải quyết êm đẹp.