Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Node.js

Cách tạo một ứng dụng thể thao thời gian thực với Node.js

by
Difficulty:IntermediateLength:LongLanguages:

Vietnamese (Tiếng Việt) translation by Thai An (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

Trong bài viết hôm nay, tôi sẽ trình bày cách tạo một ứng dụng web thể hiện điểm số trận đấu trực tiếp từ NHL. Điểm số sẽ tự động cập nhật khi trận đấu đang diễn ra.

Đây là bài viết rất thú vị với tôi, nó cho phép tôi kết nối hai đam mê ưa thích nhất cùng nhau: phát triển và thể thao.

Những công nghệ sẽ được dùng để xây dựng ứng dụng:

  1. Node.js
  2. Socket.io
  3. MySportsFeed.com

Nếu bạn chưa cài đặt Node.js, hãy truy cập trang download ngay bây giờ và cài đặt trước khi tiếp tục.

Socket.io là gì?

Socket.io là công nghệ kết nối máy khách với máy chủ. Trong ví dụ này, máy khách là trình duyệt web và máy chủ là ứng dụng Node.js. Máy chủ có thể có nhiều máy khách kết nối với nó tại thời điểm bất kỳ.

Khi kết nối đã được thiết lập, máy chủ có thể gửi thông điệp đến tất cả nhiều máy khách hoặc chỉ một máy khách. Đổi lại, máy khách có thể gửi một thông điệp đến máy chủ qua giao tiếp hai chiều theo thời gian thực.

Trước khi có Socket.io, các ứng dụng web thường dùng AJAX, và máy khách lẫn máy chủ sẽ tìm kiếm các sự kiện của mỗi bên. Ví dụ, cứ 10 giây sẽ gọi AJAX một lần để xem liệu có thông điệp bất kỳ cần xử lý hay không.

Việc thăm dò bằng thông điệp dẫn đến sự một lượng hao tổn tài nguyên đáng kể trên cả máy khách và máy chủ vì chúng sẽ liên tục nghe ngóng ngay cả khi không hề có thông điệp nào.

Với Socket.io, các thông điệp ngay lập tức nhận được mà không cần phải quá trình dò tìm, giúp giảm thiểu sự hao tổn.

Ứng dụng mẫu Socket.io

Trước khi chúng ta sử dụng dữ liệu thể thao thời gian thực, hãy tạo một ứng dụng mẫu để minh hoạ cho cách Socket.io hoạt động.

Để bắt đầu, tôi sẽ tạo một ứng dụng Node.js mới. Trong cửa sổ console, tôi sẽ điều hướng đến C:\GitHub\NodeJS, tạo một thư mục mới cho ứng dụng của tôi và tạo một ứng dụng mới:

Tôi đã sử dụng tất cả các thiết lập mặc định.

Vì chúng ta đang tạo một ứng dụng web, tôi sẽ sử dụng NPM package có tên Express để đơn giản hóa việc thiết lập. Trong cửa sộ dòng lệnh, cài đặt như sau: npm install express --save

Và dĩ nhiên ta sẽ cần cài đặt Socket.io: npm install socket.io --save

Bắt đầu bằng cách tạo máy chủ web. Tạo một file mới index.js và đặt code sau đậy vào bên trong để tạo máy chủ web bằng Express:

Nếu bạn không quen thuộc với Express, thì ví dụ code mẫu bên trên kèm theo thư viện Express và tạo một máy chủ HTTP mới. Trong ví dụ này, máy chủ HTTP đang dùng cổng 3000, http://localhost:3000. Route được tạo ở gốc của site "/". Route trả kết quả về là một file HTML: index.html.

Trước khi tạo file index.html, hãy hoàn tất cài đặt máy chủ bằng cách thiết lập Socket.io. Thêm phần sau đây vào file index.js của bạn để tạo máy chủ Socket:

Tương tự như Express, code bắt đầu bằng việc import thư viện Socket.io. Điều này được lưu trong một biến có tên là io. Kế tiếp, sử dụng biến io, trình xử lý sự kiện được tạo ra với hàm on. Sự kiện đang được nghe dùng để kết nối. Sự kiện này được gọi mỗi lần máy khách kết nối với máy chủ.

Bây giờ hãy tạo ra máy khách cơ bản của chúng ta. Tạo một file mới có tên index.html với code sau đây:

HTML bên trên nạp JavaScript máy khách Socket.io và khởi tạo kết nối đến máy chủ. Để xem ví dụ, khởi động ứng dụng Node của bạn: node index.js

Sau đó, trong trình duyệt của bạn, điều hướng đến http://localhost: 3000. Trên trang sẽ không có gì xuất hiện, tuy nhiên, nếu bạn nhìn vào console mà ứng dụng Node đang chạy, hai thông điệp được ghi lại:

  1. Máy chủ HTTP đã khởi động trên cổng 3000
  2. Đã nhận được kết nối máy khách

Bây giờ chúng ta kết nối socket thành công, hãy sử dụng nói. Bắt đầu bằng cách gửi một thông điệp từ máy chủ đến máy khách. Sau đó, khi máy khách nhận được thông điệp, nó có thể gửi một phản hồi lại cho máy chủ.

Hãy nhìn vào file index.js ngắn gọn:

Hàm io.on trước đó đã được cập nhật để bổ sung vài dòng code mới. Dòng đầu tiên, socket.emit, gửi thông điệp tới máy khách. SendToClient là tên của sự kiện. Bằng cách đặt tên sự kiện, bạn có thể gửi các kiểu thông điệp khác nhau để máy khách có thể biên dịch chúng một cách khác nhau. Dòng bổ sung thứ hai là socket.on, cũng chứa một tên sự kiện: receivedFromClient. Sự kiện này tạo ra một hàm nhận dữ liệu từ máy khách. Trong trường hợp này, dữ liệu được xuất ra trên console.

Hoàn thành việc sửa đổi cho phía máy chủ; bây giờ máy chủ có thể gửi và nhận dữ liệu từ bất kỳ máy khách nào đã được kết nối.

Hãy hoàn tất ví dụ này bằng cách cập nhật máy khách để nhận sự kiện sendToClient. Khi nhận được sự kiện, máy khách có thể phản hồi với sự kiện receiveFromClient cho máy chủ.

Phần này được JavaScript thực hiện trong file HTML, vì vậy trong file index.html, tôi đã cập nhật JavaScript như sau:

Sử dụng biến socket đã khởi tạo, chúng ta có logic khá tương tự trên máy chủ với hàm socket.on. Đối với máy khách, hàm này đang lắng nghe sự kiện sendToClient. Ngay khi máy khách được kết nối, máy chủ sẽ gửi thông điệp này. Khi máy khách nhận được, nó được xuất ra ở console trong trình duyệt. Sau đó, máy khách cùng sử dụng socket.emit mà máy chủ sử dụng để gửi sự kiện ban đầu. Trong trường hợp này, máy khách gửi lại sự kiện receiveFromClient đến máy chủ. Khi máy chủ nhận được thông điệp, nó được xuất ra cửa sổ console.

Tự mình thử nó xem. Trước tiên, trong console, chạy ứng dụng Node của bạn: node index.js. Sau đó truy cập http://localhost:3000 bằng trình duyệt của bạn.

Kiểm tra console của trình duyệt web và bạn sẽ thấy dữ liệu JSON sau được xuất ra: {hello: "world"}

Sau đó, trong cửa sổ dòng lệnh nơi ứng dụng Node đang chạy, bạn sẽ thấy điều sau đây:

Cả máy khách và máy chủ đều có thể sử dụng dữ liệu JSON đã nhận để thực hiện các tác vụ cụ thể. Chúng ta sẽ tìm hiểu thêm điều đó khi kết nối với dữ liệu thể thao theo thời gian thực.

Dữ liệu thể thao

Bây giờ chúng ta đã thành thạo cách gửi và nhận dữ liệu đến và đi từ máy khách và máy chủ, điều này có thể được tận dụng khi cung cấp các bản cập nhật theo thời gian thực. Tôi đã chọn sử dụng dữ liệu thể thao, mặc dù lý thuyết này không giới hạn với các môn thể thao. Trước khi tôi bắt đầu dự án này, tôi đã nghiên cứu dữ liệu nhiều môn thể thao khác nhau. Môn mà tôi chọn để thao tác đề xuất các tài khoản miễn phí cho nhà phát triển, là MySportsFeeds (Tôi không liên kết với họ theo bất kỳ cách nào). Để truy xuất dữ liệu thời gian thực, tôi đã đăng ký một tài khoản và sau đó đóng góp một khoản nho nhỏ. Con số đóng góp bắt đầu từ $1 để có dữ liệu được cập nhật mỗi 10 phút. Thế là đủ tốt cho ví dụ.

Sau khi tài khoản của bạn được thiết lập, bạn có thể tiến hành cấu hình quyền truy xuất vào API. Tôi sẽ sử dụng NPM package của họ để hỗ trợ: npm install mysportsfeeds-node --save

Sau khi package được cài đặt, việc gọi API có thể được thực hiện như sau:

Trong ví dụ bên trên, hãy đảm bảo cung cấp username và password của bạn cho hàm xác thực.

Đoạn code sau đây gọi API và xử lý để lấy về bảng điểm NHL cho ngày hôm nay. Biến fordate xác định ngày hôm nay. Tôi cũng xét giá trị force thành true để luôn có phản hồi trả về, cả khi dữ liệu không thay đổi.

Với thiết lập hiện tại, kết quả gọi API được ghi vào một file văn bản. Trong ví dụ sau cùng, cách làm này sẽ được thay đổi; tuy nhiên, với mục đích minh hoạ, file kết quả có thể được duyệt bằng trình soạn thảo văn bản để bạn hiểu nội dung phản hồi. Kết quả chứa một đối tượng scoreboard (bảng điểm). Đối tượng này chứa một mảng có tên gọi gameScore. Nó lưu kết quả của mỗi trận đấu. Mỗi đối tượng chứa một đối tượng con được gọi là game. Đối tượng này cung cấp thông tin về cầu thủ đang thi đấu.

Ngoài đối tượng game, có một số các biến cung cấp tình trạng hiện tại của trận đấu. Các thay đổi của dữ liệu phụ thuộc vào tình trạng của trận đấu. Ví dụ: khi trận đấu chưa bắt đầu, chỉ có một vài biến cho chúng ta biết trận đấu không được tiến hành và chưa bắt đầu.

Khi trận đấu đang diễn ra, dữ liệu bổ sung về trận đấu được cung cấp: điểm số, trận đấu đang trong thời điểm nào và lượng thời gian còn lại. Chúng ta sẽ thực hành việc này khi chúng ta thay đổi HTML để hiển thị trận đấu trong phần tiếp theo.

Cập nhật theo thời gian thực

Chúng ta có tất cả các mảnh ghép, vậy bây giờ là lúc để sắp xếp chúng lại với nhau để tìm bức trang tổng thể. Hiện tại, MySportsFeeds đã giới hạn hỗ trợ cho việc lấy dữ liệu cho chúng ta, vì vậy chúng ta sẽ phải thăm dò dữ liệu từ họ. Thật may, chúng ta biết dữ liệu chỉ thay đổi sau 10 phút, vì vậy chúng tôi không cần phải tốn thêm chi phí bằng cách thăm dò các thay đổi thường xuyên. Khi chúng ta thăm dò ​​dữ liệu từ chúng, ta có thể đưa các cập nhật đó từ máy chủ đến tất cả các máy khách được kết nối.

Để thực hiện việc thăm dò này, tôi sẽ sử dụng hàm setInterval JavaScript để gọi API (trong trường hợp của tôi) mỗi 10 phút một lần để tìm bản cập nhật. Khi nhận được dữ liệu, một sự kiện được gửi đến tất cả các máy khách được kết nối. Khi máy khách nhận sự kiện, điểm số trận đấu sẽ được cập nhật bằng JavaScript trong trình duyệt web.

MySportsFeeds sẽ được gọi khi ứng dụng Node khởi động lần đầu. Dữ liệu này sẽ được sử dụng cho bất kỳ khách hàng nào kết nối trước chu kỳ 10 phút đầu tiên. Điều này được lưu trong một biến toàn cục. Biến toàn cục này được cập nhật như một phần của thời gian thăm dò cập nhật. Điều này đảm bảo rằng khi bất kỳ máy khách mới nào kết nối sau khi thăm dò , họ sẽ có dữ liệu mới nhất.

Để giúp cho code sạch sẽ trong file index.js chính, tôi đã tạo một file mới có tên data.js. File này sẽ chứa một hàm được có sẵn từ file index.js trước đó đã gọi API MySportsFeeds. Dưới đây là toàn bộ nội dung của file đó:

Hàm getData được xuất đi và trả về kết quả gọi API, trong trường hợp này là một Promise, sẽ được xử lý ứng dụng chính.

Bây giờ hãy xem xét file index.js sau cùng:

Bảy dòng code đầu tiên ở trên khởi tạo những thư viện cần thiết và biến toàn cục latestData. Danh sách thư viện sau cùng được sử dụng gồm có: Express, Http Server do Express tạo ra, Socket.io và file data.js vừa được tạo ra trước đó.

Với nhu cầu cần thiết, ứng dụng sẽ bổ sung latestData cho các máy khách sẽ kết nối vào thời điểm máy chủ được khởi động lần đầu tiên:

Một vài dòng tiếp theo thiết lập route cho trang chủ của website (http://localhost:3000/) và khởi động máy chủ HTTP trên cổng 3000.

Kế tiếp, Socket.io được thiết lập để tìm các kết nối. Khi một kết nối mới được nhận, máy chủ phát ra một sự kiện được gọi là dữ liệu với nội dung của biến latestData.

Và cuối cùng, đoạn code còn lại tạo ra một chu kỳ thăm dò. Khi chu kỳ xảy ra, biến số latestData được cập nhật kết quả từ API trả về. Dữ liệu này sau đó phát ra một sự kiện dữ liệu giống nhau cho tất cả các máy khách.

Bạn có thể nhận thấy rằng khi máy khách kết nối và một sự kiện được phát đi, nó sẽ phát sự kiện cùng với biến socket. Cách tiếp cận này sẽ chỉ gửi sự kiện đến máy khách đã kết nối. Bên trong chu kỳ, biến toàn cục io được dùng để truyền sự kiện đi. Nó sẽ gửi sự kiện đến tất cả máy khách.

Phía máy chủ đã hoàn tất. Hãy làm việc trên với front-end của máy khách. Trong ví dụ trước đó, tôi đã tạo file index.html để thiết lập kết nối máy khách nhằm ghi lại sự kiện từ máy chủ và gửi trở lại một kết nối. Tôi sẽ mở rộng file này để chứa toàn bộ ví dụ.

Bởi vì máy chủ sẽ gửi chúng tôi một đối tượng JSON. Tôi sẽ dùng jQuery và tận dụng phần mở rộng của jQuery có tên gọi JsRender. Đây là thư viện dùng làm template. Nó cho phép tôi tạo template với HTML, template này được dùng để hiển thị nội dung của từng trận đấu NHL theo cách nhất quán và dễ sử dụng. Trong chốc lát, bạn sẽ thấy sức mạnh của thư viện này. Đoạn code cuối cùng có hơn 40 dòng, vì thế tôi sẽ chia nhỏ nó ra thành những phần nhỏ, và hiển thị cùng với HTML vào khúc cuối.

Phần đầu tiên tạo một template dùng để trình bày dữ liệu trận đấu:

Template dùng một thẻ script để định nghĩa. Nó gồm có id của template và một mã kịch bản đặc biết có tên gọi text/-jsrender. Template này đĩnh nghĩa một container div cho mỗi trận đấu, chứa một class game để áp vài kiểu cơ bản. Trong div này, template bắt đầu:

Trong div tiếp theo, đội khách và đội nhà được hiển thị. Việc này được thực hiện bằng cách nối tên thành phố và tên đội với nhau trong đối tượng game từ dữ liệu MySportsFeed.

{{:game.awayTeam.City}} là cách tôi định nghĩa một đối tượng sẽ được thay bằng một giá trị khi template được xuất ra. Cú pháp này do thư viện JsRender định nghĩa.

Khi các đội được hiển thị, đoạn code tiếp theo sẽ thực hiện một số logic có điều kiện. Khi trận đấu là unPlayed, một chuỗi sẽ được xuất thông báo rằng trận đấu sẽ bắt đầu vào lúc {{:game.time}}.

Khi trận đấu chưa kết thúc, điểm số hiện tại được hiển thị: Current Score: {{:awayScore}} - {{:homeScore}}. Và sau cùng, một số logic nhỏ để xác định trận đấu khúc côn cầu đang trong thời điểm nào hoặc nếu đang bị gián đoạn.

Nếu biến số currentIntermission xuất hiện trong kết quả, thì tôi dùng một hàm tôi đã định nghĩa có tên gọi ordinal_suffix_of, hàm này sẽ chuyển đổi số thời điểm thành read: 1st (2nd, 3rd, v.v) Intermission (thời gian gián đoạn).

Khi nó không bị gián đoạn, tôi tìm kiếm giá trị currentPeriod. Tôi cũng dùng hàm ordinal_suffix_of để cho biết trận đấu đang trong giai đoạn 1st (2nd, 3rd, v.v).

Đằng sau việc này, một hàm khác mà tôi đã định nghĩa là time_left được dùng để chuyển đổi số giây còn lại sang số phút và giây còn lại. Ví dụ: 10:12.

Phần cuối của code hiển thị điểm số kết cục bởi vì chúng ta biết trận đấu đã kết thúc.

Đây là một ví dụ cho thấy phần hiển thị sẽ ra sao nếu có nhiều trận đấu kết thúc. các trận đâu đang diễn ra, và các trận vẫn chưa bắt đầu (tôi không giỏi thiết kế cho lắm, vì vậy sẽ trông như một nhà phát triển tự tay thiết kế Giao Diện Người Dùng.

An example of finished gamesAn example of finished gamesAn example of finished games

Tiếp theo là mã JavaScript dùng tạo socket, các hàm helper ordinal_suffix_oftime_left, và một biến tham chiếu đến template của jQuery đã được tạo ra.

Đoạn code dưới cùng để nhận sự kiện từ socket và hiển thị template.

Tôi có một div placeholder với id của dữ liệu. Kết quả do template hiển thị (tmpl.render) chèn HTML vào container này. Điều khéo léo là thư viện JsRender có thể nhận một mảng dữ liệu, trong trường hợp data.scoreboard.gameScore, sẽ chạy vòng lặp qua mỗi thành phần của mảng và tạo ra một trận đấu trên từng thành phần.

Đây là HTML và JavaScript sau cùng đã kết hợp:

Khởi động ứng dụng Node và duyệt http://localhost:3000 để tự xem kết quả!

Cứ một X phút, máy chủ sẽ gửi một sự kiện đến máy khách. Máy khách sẽ tái hiện lại các thành phần của trận đấu với dữ liệu mới cập nhật. Vì vậy khi bạn vẫn mở trang và thính thoảng kiểm tra nó, bạn sẽ thấy dữ liệu trận đấu mới hơn khi trận đấu vẫn đang diễn ra.

Tổng kết

Sản phẩm sau cùng dùng Socket.io để tạo một máy chủ cho máy khách kết nối đến. Máy chủ lấy dữ liệu về và gửi chúng đến máy khách. Khi khách hàng nhận được dữ liệu, nó có thể cập nhật hiển thị liên tục. Điều này giúp giảm thiểu lượt tải trên máy chủ vì máy khách chỉ thực hiện công việc khi nó nhận được một sự kiện từ phía máy chủ.

Socket không bị giới hạn theo một chiều; máy khách cũng có thể gửi thông điệp đến máy chủ. Khi máy chủ nhận được thông điệp, nó có thể thực hiện một số xử lý.

Các ứng dụng chat thường hoạt động theo cách này. Máy chủ sẽ nhận được một thông điệp từ máy khách và sau đó phát thông điệp tới tất cả các máy khách được kết nối để cho biết có ai đó đã gửi một thông điệp mới.

Hy vọng bạn thích bài viết này vì tôi thật sự vui vẻ khi tạo ra ứng dụng thể thao theo thời gian thực cho một trong những môn thể thao tôi yêu thích nhất!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.