ChatRoom TCP console with Ruby Socket
Hôm nay, mình muốn giới thiệu với các bạn 1 cách cơ bản nhất để tạo 1 ứng dụng chat TCP sử dụng thư viện Socket của ngôn ngữ Ruby. Mình sử dụng Ruby 2.3.1 và Ubuntu 14.04 Đầu tiên chúng ta cùng ôn lại 1 tí về TCP (Transmission Control Protocol): TCP (Transmission Control Protocol - "Giao thức ...
Hôm nay, mình muốn giới thiệu với các bạn 1 cách cơ bản nhất để tạo 1 ứng dụng chat TCP sử dụng thư viện Socket của ngôn ngữ Ruby. Mình sử dụng Ruby 2.3.1 và Ubuntu 14.04 Đầu tiên chúng ta cùng ôn lại 1 tí về TCP (Transmission Control Protocol):
TCP (Transmission Control Protocol - "Giao thức điều khiển truyền vận") là một trong các giao thức cốt lõi của bộ giao thức TCP/IP. Sử dụng TCP, các ứng dụng trên các máy chủ được nối mạng có thể tạo các "kết nối" với nhau, mà qua đó chúng có thể trao đổi dữ liệu hoặc các gói tin. Giao thức này đảm bảo chuyển giao dữ liệu tới nơi nhận một cách đáng tin cậy và đúng thứ tự. TCP còn phân biệt giữa dữ liệu của nhiều ứng dụng (chẳng hạn, dịch vụ Web và dịch vụ thư điện tử) đồng thời chạy trên cùng một máy chủ. (Theo Wikipedia)
Đây là cách mà ứng dụng Chat TCP sẽ hoạt động:
Xây dựng chương trình
Chúng ta sẽ xây dựng một server để lắng nghe và nhận kết nối từ phía client và lưu trữ chúng trong 1 data dictionaries. Dictionaries này sẽ theo dõi các client nào đang kết nối, nhận message từ client nào và chuyển message đó đến cho các client còn lại. Mỗi user sẽ có 1 username khác nhau vì dictoinaries sẽ thực hiện tìm kiếm kết nối dựa trên username. Bây giờ chúng ta sẽ tạp 2 file có tên là "server.rb" và "client.rb" Trong server.rb và client.rb, chúng ta phải require thư viện Socket của Ruby
# in client.rb and server.rb require "socket"
Sau đó chúng ta xây dựng class Client tương ứng để thực hiện việc kết nối đến Server, cho phép người dùng gửi và nhận message. Trong hàm khởi tạo sẽ có 3 thuộc tính:
- @server: một instance server để client có thể kết nối đến
- @request: gửi message đến server
- @response: nhận message từ server
Ban đầu @request và @response nhận giá trị null, nhưng sau đó chúng ta sẽ xây dựng 2 Thread và assign vào chúng để có thể gửi và nhận dữ liệu.
# in client.rb require "socket" class Client def initialize(server) @server = server @request = nil @response = nil end end
Tiếp theo chúng ta tiến hành cài đặt Server. Để khởi tạo một Server thì chúng ta phải có địa chỉ ip và port. Thông qua chúng để client có thể xác định được kết nối đến địa chỉ nào thông qua port nào để server có thể lắng nghe kết nối và thực hiện việc trao đổ dữ liệu giữa client và server. Ngoài ra, khi khởi tạo Server thì chúng ta còn có 3 thuộc tính:
- @connections (Hash): lưu trữ các kết nối đến server
- @rooms (Hash): có key là tên của room chat và chứa các user đang ở trong room
- @clients (Hash): chứa các client đang kết nối
Bây giờ chúng ta đã có thể biết được người dùng nào đang ở phòng nào. Điều đặc biệt quan trọng ở đây là tên của người dùng phải là duy nhất. Cùng preview lại các hash mà chúng ta dùng để lưu trữ
# hash Connections preview connections: { clients: { client_name: {attributes}, ... }, rooms: { room_name: [clients_names], ... } }
Tiếp đến, chúng ta cần khởi tạo 2 Thread để client có thể gửi và nhận message ở cũng 1 thời điểm.
# ( @request, @response ) objects preview and descriptions # The request and response objects implementation may look like this # in client.rb def send @request = Thread.new do loop { # write as much as you want # read from the console # with the enter key, send the message to the server } end end def listen @response = Thread.new do loop { # listen for ever # listen the server responses # show them in the console } end end
File đầy đủ client side:
require "socket" class Client def initialize( server ) @server = server @request = nil @response = nil listen send @request.join @response.join end def listen @response = Thread.new do loop { msg = @server.gets.chomp puts "#{msg}" } end end def send puts "Enter the username:" @request = Thread.new do loop { msg = $stdin.gets.chomp @server.puts( msg ) } end end end server = TCPSocket.open( "localhost", 3000 ) Client.new( server )
Ở phía Server, chúng ta cũng cần 1 cái gì đó tương tự, về cơ bản chúng ta sẽ sử dụng 1 thread cho 1 connection của người dùng. Với cách này chúng ta có thể xử lý cùng lúc nhiều client kết nối đến.
# in server.rb def run loop { Thread.start do |client| # each client thread end } end
Sau khi đã có hàm run, chúng ta sẽ xử lí unique username trong hàm run, nếu tên đó đã tồn tại, thì thông báo error message cho người dùng và kill connection đó, ngươc lại thì chấp nhận kết nối.
# server.rb ( server side ) class Server def initialize(port,ip) @server = TCPServer.open(ip, port) ... end def run loop { # for each user connected and accepted by server, it will create a new thread object # and which pass the connected client as an instance to the block Thread.start(@server.accept) do | client | nick_name = client.gets.chomp.to_sym @connections[:clients].each do |other_name, other_client| if nick_name == other_name || client == other_client client.puts "This username already exist" Thread.kill self end end puts "#{nick_name} #{client}" @connections[:clients][nick_name] = client client.puts "Connection established, Thank you for joining! Happy chatting" end } end end server = Server.new("localhost", 3000) # (ip, port) in each machine "localhost" = 127.0.0.1 server.run
Sau khi đã khởi tạ kết nốt, chúng ta cần một hàm để có thể nhận message và gửi đến các client khác:
# in server.rb def listen_user_messages(username, client) loop { # get client messages msg = client.gets.chomp # send a broadcast message, a message for all connected users, but not to its self @connections[:clients].each do |other_name, other_client| unless other_name == username other_client.puts "#{username.to_s}: #{msg}" end end } end
Gọi hàm listen_user_messages trong hàm run để có thể thực hiện viêc truyền và gửi dữ liệu:
# in server.rb def run loop { Thread.start(@server.accept) do | client | ... listen_user_messages(nick_name, client) end } end
File server.rb đầy đủ:
require "socket" class Server def initialize( port, ip ) @server = TCPServer.open( ip, port ) @connections = Hash.new @rooms = Hash.new @clients = Hash.new @connections[:server] = @server @connections[:rooms] = @rooms @connections[:clients] = @clients run end def run loop { Thread.start(@server.accept) do | client | nick_name = client.gets.chomp.to_sym @connections[:clients].each do |other_name, other_client| if nick_name == other_name || client == other_client client.puts "This username already exist" Thread.kill self end end puts "#{nick_name} #{client}" @connections[:clients][nick_name] = client client.puts "Connection established, Thank you for joining! Happy chatting" listen_user_messages( nick_name, client ) end }.join end def listen_user_messages( username, client ) loop { msg = client.gets.chomp @connections[:clients].each do |other_name, other_client| unless other_name == username other_client.puts "#{username.to_s}: #{msg}" end end } end end Server.new( 3000, "localhost" )
OK, vậy là chúng ta đã hòan thành ứng dụng chat. Bây giờ mình sẽ chạy demo ứng dụng.