Ruby Socket Programming
Ruby Socket Programming
Section titled “Ruby Socket Programming”Ruby provides access to network services at multiple levels. At a low level, the socket standard library allows you to use the underlying operating system’s socket support. This enables implementation of clients and servers for both connection-oriented (like TCP) and connectionless (like UDP) protocols.
Ruby also has higher-level libraries (e.g., net/http, net/ftp) for specific application-level network protocols. This tutorial focuses on basic socket programming using TCP.
What are Sockets?
Section titled “What are Sockets?”Sockets are endpoints of a bidirectional communication channel. They can facilitate communication within a single process, between processes on the same machine, or between processes on different machines across a network.
The socket library provides classes for various transport types like Unix domain sockets, TCP, UDP, and a generic interface for others.
Socket Terminology:
| Term | Description |
|---|---|
| Domain | The protocol family, e.g., Socket::PF_INET (for IPv4), Socket::PF_INET6 (for IPv6), Socket::PF_UNIX (for local Unix domain sockets). |
| Type | The communication type, e.g., Socket::SOCK_STREAM (for connection-oriented protocols like TCP), Socket::SOCK_DGRAM (for connectionless protocols like UDP). |
| Protocol | Typically 0 or Socket::IPPROTO_TCP / Socket::IPPROTO_UDP. It can specify a variant within a domain and type. |
| Hostname | A network interface identifier: a hostname string (e.g., ‘localhost’, ‘www.example.com’), an IP address string (e.g., ‘127.0.0.1’, ‘::1’), or special strings like ' |
| Port | A numerical identifier for a service on a host. Servers listen on ports; clients connect to them. It can be an integer (e.g., 80, 443) or a service name string (e.g., ‘http’, ‘https’) which gets resolved. |
A Simple TCP Client
Section titled “A Simple TCP Client”This client connects to a specified host and port, sends a message, reads the server’s response, and then closes the connection. TCPSocket.new(hostname, port) (or TCPSocket.open) establishes a TCP connection.
#!/usr/bin/env ruby# frozen_string_literal: true
require 'socket'
hostname = 'localhost'port = 2000
begin socket = TCPSocket.new(hostname, port) puts "Connected to #{hostname}:#{port}"
# Send a message to the server message = "Hello from client!" socket.puts message puts "Sent: #{message}"
# Read the server's response while line = socket.gets # Read lines from the socket puts "Received: #{line.chomp}" # chomp removes the newline end
rescue Errno::ECONNREFUSED => e puts "Connection refused. Is the server running at #{hostname}:#{port}?"rescue StandardError => e puts "An error occurred: #{e.message}"ensure socket.close if socket # Close the socket when done puts "Connection closed."endA Simple TCP Server
Section titled “A Simple TCP Server”This server listens on a port for incoming connections. When a client connects, it accepts the connection, sends a greeting, and closes the client socket. TCPServer.new(port) creates a server socket.
#!/usr/bin/env ruby# frozen_string_literal: true
require 'socket'
port = 2000server = TCPServer.new(port) # Socket to listen on port 2000puts "Server listening on port #{port}..."
loop do # Servers typically run forever begin client_socket = server.accept # Wait for a client to connect client_addr = client_socket.peeraddr # Get client address info puts "Accepted connection from #{client_addr[2]}:#{client_addr[1]}" # IP:Port
# Read client's message client_message = client_socket.gets.chomp puts "Client said: #{client_message}"
# Send a response to the client server_response = "Hello from server! You said: #{client_message}" client_socket.puts server_response client_socket.puts "Server closing the connection. Bye!"
rescue StandardError => e puts "Error handling client: #{e.message}" ensure client_socket.close if client_socket # Disconnect from the client puts "Closed connection to client." endendTo test, run the server script in one terminal, then run the client script in another.
Multi-Client TCP Server (using Threads)
Section titled “Multi-Client TCP Server (using Threads)”Real-world servers need to handle multiple clients concurrently. A basic way to achieve this in Ruby is by spawning a new thread for each client connection.
#!/usr/bin/env ruby# frozen_string_literal: true
require 'socket'
port = 2000server = TCPServer.new(port)puts "Multi-threaded server listening on port #{port}..."
loop do Thread.start(server.accept) do |client_socket| # Pass accepted socket to the new thread begin client_addr = client_socket.peeraddr thread_id = Thread.current.object_id # For logging puts "[Thread #{thread_id}] Accepted connection from #{client_addr[2]}:#{client_addr[1]}"
client_message = client_socket.gets.chomp puts "[Thread #{thread_id}] Client said: #{client_message}"
server_response = "Hello from threaded server! Processed by thread #{thread_id}." client_socket.puts server_response client_socket.puts "Server closing your connection. Bye!"
rescue StandardError => e puts "[Thread #{thread_id}] Error handling client: #{e.message}" ensure client_socket.close if client_socket puts "[Thread #{thread_id}] Closed connection to client." end endendThis approach creates a new thread for each server.accept. While simple, for very high concurrency, more advanced patterns (like thread pools, event-driven I/O with libraries like async) might be considered to manage resources more efficiently. However, for many applications, Ruby’s threading is effective.
A Tiny Web Browser (HTTP GET Client)
Section titled “A Tiny Web Browser (HTTP GET Client)”We can use sockets to implement higher-level protocols like HTTP. This example fetches a web page.
#!/usr/bin/env ruby# frozen_string_literal: true
require 'socket'
host = 'www.example.com' # Target web serverport = 80 # Default HTTP portpath = "/" # Path to the resource (e.g., root page)
# Construct the HTTP GET requestrequest = "GET #{path} HTTP/1.1\r\n"request << "Host: #{host}\r\n"request << "Connection: close\r\n"request << "User-Agent: SimpleRubyBrowser/1.0\r\n"request << "\r\n" # Blank line signifies end of headers
begin socket = TCPSocket.new(host, port) socket.print(request) # Send the request response = socket.read # Read the complete response
# Split response into headers and body headers, body = response.split("\r\n\r\n", 2)
puts "--- HEADERS ---" puts headers puts "\n--- BODY ---" puts body if body
rescue StandardError => e puts "Error fetching page: #{e.message}"ensure socket.close if socketendUsing Net::HTTP for Web Requests
Section titled “Using Net::HTTP for Web Requests”For HTTP, Ruby’s standard library Net::HTTP provides a more convenient and robust interface than raw sockets.
#!/usr/bin/env ruby# frozen_string_literal: true
require 'net/http'require 'uri'
uri_string = 'http://www.example.com/'
begin uri = URI(uri_string) response = Net::HTTP.get_response(uri) # Sends a GET request
puts "--- STATUS ---" puts "#{response.code} #{response.message}"
puts "\n--- HEADERS ---" response.each_header { |key, value| puts "#{key}: #{value}" }
if response.is_a?(Net::HTTPSuccess) # Check for 2xx status codes puts "\n--- BODY ---" puts response.body else puts "\nRequest failed or redirected." puts "Body (if any):\n#{response.body}" end
rescue StandardError => e puts "Error with Net::HTTP: #{e.message}"endRuby provides similar standard libraries (Net::FTP, Net::SMTP, Net::IMAP, Net::POP3) for other common internet protocols.
Further Learning:
Section titled “Further Learning:”This was a brief introduction to socket programming in Ruby. For more depth, explore:
- The official Ruby documentation for the
Socket,TCPSocket,TCPServer,UDPSocketclasses. - Non-blocking I/O and event-driven programming models (e.g., using the
asyncgem orIO.select). - Secure Sockets Layer (SSL/TLS) using
OpenSSL::SSL::SSLSocketfor encrypted communication.