17. Sockets

Introduction

Writing a TCP Client

# A basic TCP client connection
require 'socket'
begin
    t = TCPSocket.new('www.ruby-lang.org', 'www')
rescue
    puts "error: #{$!}"
else
    # ... do something with the socket
    t.print "GET / HTTP/1.0\n\n"
    answer = t.gets(nil)
    # and terminate the connection when we're done
    t.close
end

# Using the evil low level socket API
require 'socket'
# create a socket
s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
# build the address of the remote machine
sockaddr_server = [Socket::AF_INET, 80,
    Socket.gethostbyname('www.ruby-lang.org')[3],
    0, 0].pack("snA4NN")
# connect
begin
    s.connect(sockaddr_server)
rescue
    puts "error: #{$!}"
else
    # ... do something with the socket
    s.print "GET / HTTP/1.0\n\n"
    # and terminate the connection when we're done
    s.close
end

# TCP connection with management of error (DNS)
require 'socket'
begin
    client = TCPSocket.new('does not exists', 'www')
rescue
    puts "error: #{$!}"
end

# TCP connection with a time out
require 'socket'
require 'timeout'
begin
    timeout(1) do #the server has one second to answer
        client = TCPSocket.new('www.host.com', 'www')
    end
rescue
    puts "error: #{$!}"
end

Writing a TCP Server

Communicating over TCP

Setting Up a UDP Client

Setting Up a UDP Server

Using UNIX Domain Sockets

Identifying the Other End of a Socket

Finding Your Own Name and Address

Closing a Socket After Forking

Writing Bidirectional Clients

Forking Servers

Pre-Forking Servers

require 'socket'

class Preforker 
    attr_reader (:child_count)
    
    def initialize(prefork, max_clients_per_child, port, client_handler)
        @prefork = prefork
        @max_clients_per_child = max_clients_per_child
        @port = port
        @child_count = 0
        
        @reaper = proc {
            trap('CHLD', @reaper)
            pid = Process.wait
            @child_count -= 1
        }
        
        @huntsman = proc {
            trap('CHLD', 'IGNORE')
            trap('INT', 'IGNORE')
            Process.kill('INT', 0)
            exit
        }
        
        @client_handler=client_handler
    end
    
    def child_handler
        trap('INT', 'EXIT')
        @client_handler.setUp
        # wish: sigprocmask UNblock SIGINT
        @max_clients_per_child.times {
            client = @server.accept or break
            @client_handler.handle_request(client)
            client.close
        }
        @client_handler.tearDown
    end
    
    def make_new_child
        # wish: sigprocmask block SIGINT
        @child_count += 1
        pid = fork do
            child_handler
        end
        # wish: sigprocmask UNblock SIGINT
    end
    
    def run
        @server = TCPserver.open(@port)
        trap('CHLD', @reaper)
        trap('INT', @huntsman)
        loop {
            (@prefork - @child_count).times { |i|
                make_new_child
            }
            sleep .1
        }
    end
end

#-----------------------------
#!/usr/bin/ruby

require 'Preforker'

class ClientHandler
    def setUp
    end
    
    def tearDown
    end
    
    def handle_request(client)
        # do stuff
    end
end

server = Preforker.new(1, 100, 3102, ClientHandler.new)
server.run

Non-Forking Servers

Writing a Multi-Homed Server

Making a Daemon Server

Restarting a Server on Demand

Program: backsniff

Program: fwdport