Skip to content

Instantly share code, notes, and snippets.

@cleesmith
Last active January 1, 2016 18:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cleesmith/8184934 to your computer and use it in GitHub Desktop.
Save cleesmith/8184934 to your computer and use it in GitHub Desktop.
Evented TCP server in 50 lines of ruby code

Evented TCP server using eventmachine.

TCP server:

require 'eventmachine'

PORT = 4545
puts "Listening on #{PORT}...\n"

class Counter
  attr_accessor :total_data

  def initialize
    @total_data = 0
  end

  def total_data=(num)
    @total_data += num
  end
end

class EchoServer < EM::Connection
  def initialize(counter)
    @total = 0
    @total_data = 0
    @counter = counter
  end

  def receive_data(data)
    @total += 1
    @total_data += data.length
    @counter.total_data = data.length
    # puts "\ntotal=#{@total} \t #{data.length}\n"
    # send_data(data)
    # do some work, monkey with the data, kill some time:
    lines = data.split("\n")
    # puts "lines=#{lines.length}"
    lines.each do |line|
      fields = line.split("\t")
      # puts "\tfields=#{fields.length}"
      fields.each do |field|
        # puts "\t\tfield=#{field.length}"
      end
    end
  end
end

EventMachine.run do
  counter = Counter.new
  Signal.trap("INT")  { EventMachine.stop }
  Signal.trap("TERM") { EventMachine.stop }
  EventMachine.start_server("127.0.0.1", PORT, EchoServer, counter)
  EM.add_periodic_timer(30) { puts "counter=#{counter.inspect}" }
end

TCP client:

require 'socket'
start = Time.now
begin
  if ARGV.empty?
    port = 4545
  else
    port = ARGV[0].to_i
  end
  client = TCPSocket.new('127.0.0.1', port)
  # puts "Socket::TCP_NODELAY=#{Socket::TCP_NODELAY}"
  client.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) # Nagle off
  # 420_000_000.times do |x| <<--- took about 40 minutes to process 128,100,000,000 of data (OS X)
  1_000_000.times do |x|
    # send 305 bytes:
    msg  = "Lorem \t ipsum \t dolor sit amet, consectetur adipiscing elit. Donec luctus enim mollis eros interdum egestas. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae est pellentesque, suscipit nisl et, rutrum nulla. Nullam sed justo nisl. Donec rutrum velit odio, non sollicitudin lorem amet.\n"
    # msg += "Lorem \t ipsum \t dolor sit amet, consectetur adipiscing elit. Donec luctus enim mollis eros interdum egestas. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae est pellentesque, suscipit nisl et, rutrum nulla. Nullam sed justo nisl. Donec rutrum velit odio, non sollicitudin lorem amet.\n"
    # msg += "Lorem \t ipsum \t dolor sit amet, consectetur adipiscing elit. Donec luctus enim mollis eros interdum egestas. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae est pellentesque, suscipit nisl et, rutrum nulla. Nullam sed justo nisl. Donec rutrum velit odio, non sollicitudin lorem amet.\n"
    # msg += "Lorem \t ipsum \t dolor sit amet, consectetur adipiscing elit. Donec luctus enim mollis eros interdum egestas. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae est pellentesque, suscipit nisl et, rutrum nulla. Nullam sed justo nisl. Donec rutrum velit odio, non sollicitudin lorem amet.\n"
    # puts "#{msg.length}\n"
    client.puts(msg)
    # ignore response:
    # resp = client.gets
    # puts "resp=#{resp}"
  end
ensure
  client.close if client
  puts "elapsed: #{(Time.now - start)}"
end

Why ? Well, it's all the rage (or was), it's very fast, it doesn't use a lot of memory (hovered around 30K) compared to what it's processing, and there's no worries about threading and forking and all those entail.

Sure, you are not going to traipse off to the bonneville salt flats with this server and break any speed records, but after trying all of the other options this seems to work the best for pushing data into a server for various purposes. It's amazing what 50 lines of ruby code, not counting eventmachine itself, and *nix can do. Also, all of my testing was done with ruby MRI.


fin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment