Cân nhắc kỹ lưỡng.
Đựa ra lựa chọn ngôn ngữ cho một chương trình đơn giản đã là cơn đau đầu không mấy dễ chịu rồi thì việc phải lựa chọn ngôn ngữ để phát triển một socket server lại càng đau đầu hơn gấp bội. C/C++ hay Java hay C#, Node.js hay PHP, Dart hay RoR, GoLang hay Rust? Giả sử như chỉ có 1 lựa chọn duy nhất là C/C++ thôi thì lại dễ, đây thì quá nhiều. Vậy nên chúng ta phải cân nhắc cẩn thận trước khi đưa ra quyết định. Vì một socket server có thể lên đến hàng trăm nghìn, thậm chí hàng triệu dòng code. Việc phát triển 1 socket server cũng có thể phải kéo dài rất nhiều năm mới trở lên hoàn thiện được. Mỗi ngôn ngữ lại có những điểm mạnh, yếu khác nhau, và lại có những lượng "fan" khác nhau, mỗi người cũng có những thiên kiến xác nhận khác nhau, nên trong bài viết này mình sẽ chỉ đề cập đến ezyfox-server và các vấn đề của nó thôi nhé.
Các yếu tố cần thiết
Việc lựa chọn một ngôn ngữ chỉ đơn giản là yêu thích và theo cảm tính thì sẽ chẳng đi đến đâu cả, nên với mình, để quyết định lựa chọn ngôn ngữ nào phụ thuộc vào những yếu tố sau:
- Ngôn ngữ đó phải hỗ trợ cả TCP và UDP
- Non-bloking I/O (NIO): 1 socket server cần phải sử dụng NIO để đảm bảo khả năng phục vụ và một hiệu suất cao nhất
- I/O: 1 socket server cũng cần tương tác với byte bit, đọc các file cấu hình hay tương tác với hệ điều hành
- Concurrency hay Multi-Threading: chắc chắn là chúng ta sẽ cần xử lý đa luồng với một socket server rồi
- Generics: Socket server sẽ làm việc rất nhiều với các dữ liệu dạng collection như Map, List, Queue ... Nên generics sẽ giúp chúng ta đỡ phải ép kiểu và quản lý đối tượng dễ dàng hơn.
- Reflection: Sẽ là tốt nhất nếu chúng ta lựa chọn 1 ngôn ngữ hướng đối tượng, vì với một khối lượng code tương đối nhiều cho socket server, hướng đối tượng với các tính chất mạnh mẽ và khả năng áp dụng design patterns siêu việt sẽ giúp chúng ta mở rộng code dễ dàng trong tương lai thông qua Dependency Injection và IoC
- Annotation: Dùng để đặc tả các lớp, hỗ trợ cho việc scan tự động ở Runtime và hỗ trợ cho DI và IoC
- Just in Time: Để biên dịch các lớp và tạo ra các đối tượng ở Runtime, phục vụ cho DI và IoC
- Ngôn ngữ đó phải dễ dàng trace được log, ở lớp nào, dòng thứ bao nhiêu, vì debug server chỉ có log mà thôi và Exception sẽ là công cụ rất tốt
- Ngôn ngữ đó có khả năng tạo ra 1 nền tảng, thông qua việc module hoá và đóng gói.
- Ngôn ngữ đó phải phổ biến nhất với tất cả mọi người.
Phân tích các ngôn ngữ
Ở thời điểm mình nhen nhóm ý tưởng viết 1 socket server thì có các ngôn ngữ script phổ biến như: PHP, javascript, python và các ngôn ngữ biên dịch như: C/C++, Java, C#. Vậy nên mình sẽ chỉ nói về các ngôn ngữ này thôi nhé.
Đối với các ngôn ngữ script
Sự ra đời của các ngôn ngữ script đã thay đổi thế giới này và cho đến giờ thì javascript hay python vẫn đang thống trị các bảng xếp hạng. Trên thực tế thì nó quá đơn giản để học, để cài đặt, để viết. Ai cũng có thể lập trình được. Chỉ với một vài dòng code là chúng ta đã có thể tạo ra cả một http hay một websocket server rồi.
Thêm một điểm mạnh nữa của các ngông ngữ script đó là không cần phải restart lại server mỗi lần có update, chúng ta sẽ chỉ cần update và thế là xong. Tuy nhiên điểm mạnh này cũng rất dễ gây nên những lỗ hổng bảo mật nghiêm trọng, sẽ thế nào nếu user upload được file code lên server? Chắc chắn rồi họ là những hacker và server của chúng ta sẽ bị làm gỏi.
Các ngôn ngữ script ra đời với mục tiêu là tối ưu hoá thời gian phát triển phần mềm, tuy nhiên cái mà nó phải hy sinh đó là tốc độ của chương trình (bạn có thể tham khảo benchmark tại đây). Vì sao vậy? Vì script là ngôn ngữ thông dịch, nghĩa là bạn viết code thế nào thì nó sẽ nạp vào máy ảo và chạy như thế, vậy nên có nhiều phần nó không thể nào tối ưu được.
Các ngôn ngữ script ra đời với mục tiêu hỗ trợ tối đa việc xử lý với text, tuy nhiên được cái này thì lại mất cái kia, hỗ trợ tốt cho text thì lại hỗ trợ kém cho byte bit, ví dụ như javascript sẽ thường lưu trữ byte array ở dạng base64, chính vì thế mà bộ nhớ sẽ bị tăng lên, và khi xử lý byte cũng phải chuyển đổi qua lại, tương đối vất vả.
Các ngôn ngữ script muốn lập trình viên không cần phải biết đến đa luồng nữa, chính vì vậy mà nó đã giấu đi sự phức tạp ở tầng C/C++ của máy ảo và thường chỉ để lại 1 main thread duy nhất cho lập trình viên mà thôi. Chính vì thế mà đôi khi sự sáng tạo bị giảm đi và chúng ta không thể tận dụng hết được sức mạng của đa luồng.
Cuối cùng, các ngôn ngữ script thường không phải strong type (có python hỗ trợ cả strong và dynamic type), nên đối với các dự án lớn sẽ rất dễ để gây lỗi, đơn giản như xung đột tên biến hay so sánh nhầm kiểu.
C/C++
Chắc chắn rồi, C/C++ là ngôn ngữ biên dịch chạy nhanh nhất trong các ngôn ngữ mình vừa liệt kê. Chương trình viết bằng C/C++ chạy trực tiếp trên hệ điều hành mà không cần thông qua máy áo, chính vì thế mà hiệu năng của nó rất rất tuyệt vời. Khả năng xử lý với byte bit rất mạnh mẽ và đơn giản, ở 2 điểm này thì đúng là C/C++ là lựa chọn tuyệt vời nhất.
Thế nhưng, C/C++ quá khó viết, đặc biệt là phần quản lý bộ nhớ, chỉ cần 1 sai sót nhỏ là chương trình của chúng ta sẽ bị memleak và cứ thế sẽ nhanh chóng bị treo. Cú pháp cũng tương đối loằng ngoằng đối với người mới học. Mỗi hệ điều hành lại phải có cách code khác nhau mới chạy được sẽ làm nản lòng không ít các lập trình viên mới vào nghề.
Khả năng trace lỗi của C/C++ cũng tương đối tệ, một khi chương trình đã crash là bất đắc kì tử luôn, không để lại nhiều dấu vết, thế nên việc debug là tương đối khó khăn.
CSharp
CSharp là một ngôn ngữ tuyệt vời, với đầy đủ các yếu tố mình đã nói ở trên. Với sự hậu thuẫn đến từ microsoft thì CShaprp cũng nhanh chóng phổ biến trên toàn cầu, nó có thể dùng cho mọi loại chương trình và với socket server cũng không phải là ngoại lệ, và hiện tại Photon vẫn đang là 1 CSharp server hỗ trợ rất tốt cho việc làm game, mặc dù cái giá cũng không rẻ đối với 1 cá nhân.
Một điểm không mấy dễ chịu đó là các phần mềm viết bằng CSharp thì nên chạy trên các nền tảng của microsoft. Ngay cả khi .netcore ra đời thì chúng ta cũng vẫn sẽ sử dụng các thư viên trong hệ sinh thái của microsoft và triển khai các ứng dụng trên windows server. Mà chi phí cho windows server không hề rẻ một chút nào. Đối với các cá nhân hay startup mà nói, thì chi phí luôn là vấn đề nhức nhối.
Java
Sau tất cả, cuối cùng cũng đã tìm được 1 ngôn ngữ đáp ứng được đầy đủ các yêu tố ở trên với:
- Với java NIO support cả TCP và UDP
- Java I/O support đọc ghi file
- Java sử dụng các kiểu nguyên thuỷ boolean, byte, char, int, long short, double, float nên thao tác với byte bit rất dễ
- Java support
concurrency
hay Multi-Threading rất mạnh với thư việnjava.concurrent
. - Java Generics: hỗ trợ với đầy đủ generic type, generic method và có cả thử viện java.utl với Map, List, Set, Tree, Queue hay Stack.
- Java Reflection: truy xuất các thông tin lớp và hàm, generics type và khởi tạo đối tượng.
- Java Annotation: mô tả, đánh dấu các thông tin cho gói, lớp hàm hay các tham số.
- Java Just in Time: biên dịch và tạo ra các lớp java ngay trong thời gian chạy.
- Dễ dàng trace được log, rất nhiều thư viện hỗ trợ, đặc biệt với Exception stack trace sẽ cung cấp đầy đủ thông tin lớp nào hàm nào dòng thứ bao nhiêu đang gặp lỗi.
- Java có khả năng tạo ra 1 nền tảng, thông qua việc module hoá và đóng gói.
- Ở thời điểm mình viết socket server thì java phổ biến và được đánh giá ở top đầu.
Thêm 1 điểm nữa là Java có thể chạy được trên linux và giá server linux tương đối dễ chịu.
Go
Gần đây, với sự nổi lên của ethreum với geth thì go cũng là một ngôn ngữ khá thú vị được yêu thích. Nó hỗ trợ lập trình đa luồng rất tốt thông qua goroutine hay channel, và khả năng xử lý I/O cũng rất tuyệt vời. Tuy nhiên go vẫn phù hợp để lập trình ứng dụng hơn, nó vẫn chưa có đầy đủ các yếu tố mà mình cần và vẫn còn ít các thư viện hỗ trợ hơn ngôn ngữ khác. Vậy nên mình vẫn chưa có nhiều lý do để thay thế nó cho Java.
Bảng so sánh
Sau đây là bảng so sánh một số tính năng cơ bản của các ngôn ngữ mà mình tìm hiểu được, phần nào có dấu ?
là mình chưa chắc chắn, mình sẽ tìm hiểu và điền vào sau nhé.
# | Ngôn ngữ/Tính Năng | PHP | JS | Python | C/C++ | C# | Java | Go |
---|---|---|---|---|---|---|---|---|
1 | Hỗ trợ TCP/UDP | yes | Node.js | yes | yes | yes | yes | yes |
2 | Non-blocking I/O | yes | yes | yes | yes | yes | yes | yes |
3 | I/O | yes | yes | yes | yes | yes | yes | yes |
4 | Multi-Threading | ? | yes | yes | yes | yes | yes | |
5 | Reflections | yes | yes | yes | ||||
6 | Generics | ? | yes | yes | yes | |||
7 | Annotation | yes | ? | typescript | ? | yes | yes | yes |
8 | Just in time | ? | ? | ? | yes | yes | yes | |
9 | Exception/Call stack | yes | yes | yes | yes | yes | yes | |
10 | Modular | yes | yes | yes | not good | yes | yes | yes |
11 | Popular | yes | yes | yes | yes | yes | yes | quite |
Tổng kết
Nhìn chung việc lựa chọn ngôn ngữ nào là rất quan trọng và mình không thể cảm tính được. Khi hàng trăm nghìn, hàng triệu dòng code đã được viết ra thì việc thay đổi là điều không hề dễ dàng.
Với mình thì Java là lựa chọn tốt nhất để phát triển 1 socket server thành một nên tảng và như bây giờ nó đã thành cả một hệ sinh thái to lớn. Dù có một chút chậm chạm, một số khó khăn nhất định khi cài đặt và khởi tạo dự án, nhưng với độ phổ biến của mình thì Java vẫn đang cho thấy rằng nó là phù hợp hơn cả.