DEV Community

FaxriddinMaxmadiyorov
FaxriddinMaxmadiyorov

Posted on • Edited on

Chat Server in Ruby Sockets

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:

  1. 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.
  2. 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."
Enter fullscreen mode Exit fullscreen mode
# 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."
Enter fullscreen mode Exit fullscreen mode

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."
Enter fullscreen mode Exit fullscreen mode
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."
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
# 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."
Enter fullscreen mode Exit fullscreen mode

Top comments (0)