ChatRoom TCP console with Ruby Socket
Bài đăng này đã không được cập nhật trong 3 năm
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.
All rights reserved