Skip to content

Instantly share code, notes, and snippets.

@Aupajo
Last active August 25, 2023 19:26
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save Aupajo/055eef576bc9cbe4918ce9719d08afb2 to your computer and use it in GitHub Desktop.
Save Aupajo/055eef576bc9cbe4918ce9719d08afb2 to your computer and use it in GitHub Desktop.
Sockets in Ruby
require 'socket'
# 1. Create
# AF_INET means IPv4 (xxx.xxx.xxx.xxx)
# SOCK_STREAM means communicating with a stream (TCP)
#
# Can be simplified to symbols :INET and :STREAM, respectively
server = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
# 2. Bind
# 4481 is the port number – pick one between 1,025-48,889
# Want to choose “the port” for your service? Check the list of IANA reserved
# ports first: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt
#
# '0.0.0.0' is the IP address to bind to. '127.0.0.1' means your socket can be
# connected to on your local machine, but not from the outside. '192.168.0.5'
# will only work for clients that can access that IP over the network, but not
# your local machine. '0.0.0.0' means anyone, anywhere can connect.
address = Socket.pack_sockaddr_in(4481, '0.0.0.0')
server.bind(address)
# 3. Listen
# 5 is the maximum number of pending connections your socket is willing to
# tolerate. Socket::SOMAXCONN is a constant that represents the maximum allowed
# listen size. Hitting this limit can result in an Errno::ECONNREFUSED.
server.listen(5)
# 4. Accept
# Accept blocks until a message is received, and returns a tuple
connection, addr_info = server.accept
# In Unix, everything is treated as a file
# Sockets are no exception – here's the server's “fileno” (file descriptor
# number) that the kernel uses to keep track of which files are open in the
# current process
print 'Server fileno: '
p server.fileno
# A “connection” is actually another instance of the Socket class
print 'Connection class: '
p connection.class
# This “connection” Socket has a different file descriptor than the server socket
print 'Connection fileno: '
p connection.fileno
# The local address is the endpoint on the machine
print 'Local address: '
p connection.local_address
# The remote address
print 'Remote address: '
p connection.remote_address # this is the same as addr_info
# 5. Close
# Listen to Alec Baldwin. Always be closing.
# There are also methods called `close_write` and `close_read`, which prevent
# the connection from being able to read or write any longer.
connection.close
# Same as before, with the comments stripped
require 'socket'
# 1. Create
server = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
# 2. Bind
address = Socket.pack_sockaddr_in(4481, '0.0.0.0')
server.bind(address)
# 3. Listen
server.listen(5)
# 4. Accept
connection, addr_info = server.accept
# 5. Close
connection.close
# The previous code, simplified!
require 'socket'
# Almost identical to Socket, most notable difference being that `accept`
# returns the connection rather than the [connection, addr_info] tuple
server = TCPServer.new(4481)
# Create two sockets, one each for IPV4 and IPV6
# servers = Socket.tcp_server_sockets(4481)
connection = server.accept
connection.close
# Processing more than one message
require 'socket'
server = TCPServer.new(4481)
loop do
connection = server.accept
connection.close
end
# `loop` sucks
require 'socket'
server = TCPServer.new(4481)
Socket.accept_loop(server) do |connection|
connection.close
end
# Compacting even further
require 'socket'
# Simplify everything
Socket.tcp_server_loop(4481) do |connection|
connection.close
end
# Reading from the remote socket
require 'socket'
Socket.tcp_server_loop(4481) do |connection|
# Connection is an IO, so `read`, `flush`, etc. are all supported
puts connection.read
connection.close
end
# Connecting with a client
require 'socket'
client = TCPSocket.new('localhost', 4481)
client.write('hello!')
# 192.168.201.172
# Reading streams (e.g. `tail -f /var/log/syslog | nc localhost 4481`)
require 'socket'
server = TCPServer.new(4481)
one_kb = 1024
Socket.accept_loop(server) do |connection|
# Read 1KB at a time and `puts` as we go!
while data = connection.read(one_kb) do
puts data
end
connection.close
end
# Sending EOF on the client
require 'socket'
client = TCPSocket.new('localhost', 4481)
client.write('hello!')
# Close the socket to send an EOF
client.close
# Partial reads > reads
require 'socket'
one_hundred_kb = 1024 * 100
Socket.tcp_server_loop(4481) do |connection|
begin
# Read data in chunks of *up to* 1 hundred kb or less
while data = connection.readpartial(one_hundred_kb) do
puts data
end
# How much to read? There's no silver bullet, but Mongrel, Unicorn, Puma,
# Passenger, and Net::HTTP use readpartial(1024 * 16). redis-rb uses 1KB.
# `readpartial` raises an EOFError when EOF is encountered
rescue EOFError
end
connection.close
end
require 'socket'
Socket.tcp_server_loop(4481) do |connection|
# Simplest way to write data to a connection.
# This returns immediately after passing the data to the kernel, which figures
# out how to send most optimally. Behind the scenes, the kernel can collect
# all the pending writes, group them and optimize when they're sent for
# maximum performance to avoid flooding the network.
connection.write('Welcome!')
connection.close
end
# A basic HTTP server
require 'socket'
Socket.tcp_server_loop(4481) do |connection|
request = connection.gets
p request
response = "Hello world!"
connection.print "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/plain\r\n" +
"Content-Length: #{response.bytesize}\r\n" +
"Connection: close\r\n" +
"\r\n" +
response
connection.close
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment