I am going to build a chat server in Ruby's sockets library. Before this, let's clarify what sockets are. Sockets are the endpoints of the communication channels. There are two types of sockets:
- Stream sockets - These sockets use Transmission Control Protocol (TCP) for communication. They provide a reliable, connection-oriented communication channel. Web servers and clients (HTTP/HTTPS) use this kind of sockets.
- Datagram sockets - These sockets use User Datagram protocol for communication. They are less reliable than TCP sockets and are used in streaming.
Chat application contains two parts: server and client; We will organize server part first:
Create TCPServer, it accepts infinite signals, i.e it must be alive, exchange data.
Create Socket client, it connects to server, exchange data
Our first example shows synchronous chat (chat and clients sides have to wait each other to send a message, each side of the connection waits for the other to send a message before it proceeds, creating a sort of "deadlock" where both sides are waiting.)
# server.rb
require 'socket'
server = TCPServer.new('localhost', 3333)
puts "TCPServer is running port on 3333 ..."
client = server.accept
puts "Client connected."
loop do
message = client.gets.chomp
puts "Client: #{message}"
break if message == 'exit'
response = gets.chomp
client.puts response
break if response == 'exit'
end
client.close
puts "Client disconnected."
# client.rb
require 'socket'
client = TCPSocket.new('localhost', 3333)
puts "Connected to server..."
loop do
message = gets.chomp
client.puts message
# Exit if the message is 'exit'
break if message == 'exit'
# Receive and print the server's response
response = client.gets.chomp
puts "Server: #{response}"
break if response == 'exit'
end
client.close
puts "Disconnected from server."
To deliver messages in correct order, we have to use Threads to handle other tasks concurrently (the server accepts only one client).
require 'socket'
server = TCPServer.new('localhost', 3333)
puts "TCPServer is running port on 3333 ..."
client = server.accept
puts "Client connected."
# Thread to handle reading messages from the client
Thread.new do
loop do
message = client.gets.chomp
puts "Client: #{message}"
break if message == 'exit'
end
end
# Thread to handle sending messages to the client
loop do
response = gets.chomp
client.puts response
break if response == 'exit'
end
client.close
puts "Client disconnected."
require 'socket'
client = TCPSocket.new('localhost', 3333)
puts "Connected to server..."
# Thread to reading messages from the server
Thread.new do
loop do
message = client.gets.chomp
puts "Server: #{message}"
break if message == 'exit'
end
end
loop do
response = gets.chomp
client.puts response
break if response == 'exit'
end
client.close
puts "Disconnected from server."
If we want our server can communicate to all clients at the same time, we should create 1 thread (to handle communication) for each client and 1 server thread (to handle server-side input and broadcasts). As to client side, we have to create one thread to handle incoming message.
# server.rb
require 'socket'
server = TCPServer.new('localhost', 3333)
puts "TCPServer is running on port 3333 ..."
clients = []
def broadcast(message, clients, sender = nil)
clients.each do |client|
client.puts message unless client == sender
end
end
# Thread to handle server input
server_input_thread = Thread.new do
loop do
response = gets
break if response.nil? || response.chomp == 'exit'
message = "Server: #{response.chomp}"
broadcast(message, clients)
end
puts "Server shutting down."
clients.each { |client| client.close } # Close all client connections
server.close
exit
end
loop do
client = server.accept
clients << client
puts "Client connected."
Thread.new(client) do |client_socket|
begin
loop do
message = client_socket.gets
break if message.nil? || message.chomp == 'exit'
message = "Client: #{message.chomp}"
puts message
end
rescue StandardError
puts "Client disconnected."
ensure
clients.delete(client_socket)
client_socket.close
puts "Client disconnected."
end
end
end
# client.rb
require 'socket'
client = TCPSocket.new('localhost', 3333)
puts "Connected to server..."
# Thread to reading messages from the server
Thread.new do
loop do
message = client.gets&.chomp
puts message
break if message == 'exit'
end
end
loop do
response = gets.chomp
client.puts response
break if response == 'exit'
end
client.close
puts "Disconnected from server."
Top comments (0)