Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TCP Socket programming in Ruby

TCP Socket programming in Ruby

As software developers, a lot of the time we're building applications that rely on some sort of network connection. Due to Ruby's great abstractions we take most of the network related stuff for granted. We think we know how all of that works, but do we? Let's go over the fundamentals together, learn about how Ruby models TCP Sockets and how we can make a good use of it.

Even if you're not doing network programming, being able to dive multiple levels to understand what's going on will give you a great advantage. The sort of concepts we'll go over this talk don't apply to Ruby only. All modern languages support the Berkeley Sockets API so this knowledge is portable and it will serve you for many years to come.

During the talk we will go through the fundamentals of programming with sockets. This includes creating sockets, client and server life-cycle, reading and writing data, non-blocking IO and connection multiplexing.

Presented at: RubyConf Uruguay 2014

Sebastian Sogamoso

May 23, 2014
Tweet

More Decks by Sebastian Sogamoso

Other Decks in Programming

Transcript

  1. A way to communicate within a process, between processes on

    the same machine, or between processes on different continents
  2. socket(2). Creates an endpoint for communication listen(2). Prepares a socket

    for incoming connections bind(2). Assigns a socket to an address
  3. connect(2). Connects to a socket in the given address accept(2).

    Manages new incoming connections on a socket
  4. connect(2). Connects to a socket in the given address getpeername(2).

    Gets information about of the peer socket accept(2). Manages new incoming connections on a socket
  5. require ‘socket’ ! server = Socket.new(:INET, :STREAM) ! address =

    Socket.pack_sockaddr_in( 5000,‘0.0.0.0’)
  6. require ‘socket’ ! server = Socket.new(:INET, :STREAM) ! address =

    Socket.pack_sockaddr_in( 5000,‘0.0.0.0’) Struct containing an adress
  7. require ‘socket’ ! server = Socket.new(:INET, :STREAM) ! address =

    Socket.pack_sockaddr_in( 5000,‘0.0.0.0’) Port
  8. require ‘socket’ ! server = Socket.new(:INET, :STREAM) ! address =

    Socket.pack_sockaddr_in( 5000,‘0.0.0.0’) Host
  9. require ‘socket’ ! server = Socket.new(:INET, :STREAM) ! address =

    Socket.pack_sockaddr_in( 5000,‘0.0.0.0’) ! server.bind(address)
  10. require ‘socket’ ! server = Socket.new(:INET, :STREAM) ! address =

    Socket.pack_sockaddr_in( 5000,‘0.0.0.0’) ! server.bind(address) server.accept(128)
  11. require ‘socket’ ! server = Socket.new(:INET, :STREAM) ! address =

    Socket.pack_sockaddr_in( 5000,‘0.0.0.0’) ! server.bind(address) server.accept(128) Size of listen queue
  12. require ‘socket’ ! server = Socket.new(:INET, :STREAM) ! address =

    Socket.pack_sockaddr_in( 5000,‘0.0.0.0’) ! server.bind(address) server.accept(128) socket.close
  13. require ‘socket’ ! client = Socket.new(:INET, :STREAM) ! address =

    Socket.pack_sockaddr_in( 80,‘rubyconfuruguay.org’)
  14. require ‘socket’ ! client = Socket.new(:INET, :STREAM) ! address =

    Socket.pack_sockaddr_in( 80,‘rubyconfuruguay.org’) ! client.connect(address)
  15. require ‘socket’ ! client = Socket.new(:INET, :STREAM) ! address =

    Socket.pack_sockaddr_in( 80,‘rubyconfuruguay.org’) ! client.connect(address) ! client.close
  16. require ‘socket’ ! server = Socket.new(:INET, :STREAM) ! address =

    Socket.pack_sockaddr_in( 5000,‘0.0.0.0’) ! server.bind(address) server.accept(128) socket.close
  17. require ‘socket’ ! client = Socket.new(:INET, :STREAM) ! address =

    Socket.pack_sockaddr_in( 80,‘rubysur.org’) ! client.connect(address) ! client.close
  18. require ‘socket’ ! Socket.tcp(‘rubysur.org’, 80) do |c| . . .

    c.close end ! #OR ! TCPSocket.new(‘rubysur.org’, 80)
  19. require ‘socket’ ! chunk_size = 1024 # 1kb ! Socket.tcp_server(5000)

    do |c| while data = c.read(chunk_size) do process(data) end end
  20. require ‘socket’ ! chunk_size = 1024 # 1 kb !

    Socket.tcp_server(5000) do |c| while data = c.read(chunk_size) do process(data) end end Returns each time ‘chunk_size’ of data is received
  21. require ‘socket’ ! chunk_size = 1024 # 1kb ! Socket.tcp_server(5000)

    do |c| while data = c.read(chunk_size) do process(data) end c.close end
  22. require ‘socket’ ! chunk_size = 1024 # 1kb ! Socket.tcp_server(5000)

    do |c| while data = c.readpartial(chunk_size) do process(data) end end
  23. require ‘socket’ ! chunk_size = 1024 # 1kb ! Socket.tcp_server(5000)

    do |c| while data = c.readpartial(chunk_size) do process(data) end end Returns chunks of data upto ‘chunk_size’ when available
  24. require ‘socket’ ! chunk_size = 1024 # 1kb ! Socket.tcp_server(5000)

    do |c| begin while data = c.readpartial(chunk_size) do process(data) end recue EOFError end end
  25. require ‘socket’ ! chunk_size = 1024 # 1kb ! Socket.tcp_server(5000)

    do |c| begin while data = c.readpartial(chunk_size) do process(data) end recue EOFError end end Raises EOFError when stream is closed
  26. require ‘socket’ ! chunk_size = 1024 # 1kb ! Socket.tcp_server(5000)

    do |c| begin while data = c.readpartial(chunk_size) do process(data) end recue EOFError end c.close end
  27. readpartial is eager returning data as it is available. It

    will still block until it receives EOF
  28. require ‘socket’ ! Socket.tcp_server(5000) do |connection| loop do data =

    connection.read_nonblock(1024) process(data) end end
  29. require ‘socket’ ! Socket.tcp_server(5000) do |connection| loop do begin data

    = connection.read_nonblock(1024) process(data) rescue Error::EAGAIN IO.select([connection]) ; retry end end end
  30. require ‘socket’ ! Socket.tcp_server(5000) do |connection| loop do begin data

    = connection.read_nonblock(1024) process(data) rescue Error::EAGAIN IO.select([connection]) ; retry end end end When there is no data to read
  31. require ‘socket’ ! Socket.tcp_server(5000) do |connection| loop do begin data

    = connection.read_nonblock(1024) process(data) rescue Error::EAGAIN IO.select([connection]) ; retry end end end Wait until it is ready to be read
  32. require ‘socket’ ! Socket.tcp_server(5000) do |connection| loop do begin data

    = connection.read_nonblock(1024) process(data) rescue Error::EAGAIN IO.select([connection]) ; retry rescue EOFError ; break end end connection.close end
  33. require ‘socket’ ! Socket.tcp_server(5000) do |connection| loop do begin data

    = connection.read_nonblock(1024) process(data) rescue Error::EAGAIN IO.select([connection]) ; retry rescue EOFError ; break end end connection.close end When the connection is closed (EOF)
  34. require ‘socket’ ! TCPSocket.new(‘0.0.0.0’, 5000) message = ‘RubyConf Uruguay 2014’

    * 10_000 ! loop do bytes = connection.write_nonblock(message) break if bytes >= message.size message.slice!(0, bytes) IO.select([], [connection]) end
  35. require ‘socket’ ! TCPSocket.new(‘0.0.0.0’, 5000) message = ‘RubyConf Uruguay 2014’

    * 10_000 ! begin loop do bytes = connection.write_nonblock(message) break if bytes >= message.size message.slice!(0, bytes) IO.select([], [connection]) end rescue Error::EAGAIN IO.select([], [connection]) ; retry end
  36. require ‘socket’ ! client = Socket.new(:INET, :STREAM) address = Socket.pack_sockaddr_in(

    80,‘rubysur.org’) ! client.connect_nonblock(address) rescue Errno::EINPROGRESS
  37. require ‘socket’ ! client = Socket.new(:INET, :STREAM) address = Socket.pack_sockaddr_in(

    80,‘rubysur.org’) ! client.connect_nonblock(address) rescue Errno::EINPROGRESS Notifies and connects on background
  38. require ‘socket’ ! client = Socket.new(:INET, :STREAM) address = Socket.pack_sockaddr_in(

    80,‘rubysur.org’) ! client.connect_nonblock(address) rescue Errno::EINPROGRESS rescue Errno::EALREADY Non-blocking connection already in progress
  39. We can use select(2) to get the sockets that are

    ready for reading, writing, or have an exceptional condition pending
  40. sockets = [<TCPSocket>, <TCPSocket>, …] ! IO.select(sockets, sockets, sockets, 5)

    => [[<TCPSocket>, <TCPSocket>], [<TCPSocket>], [] ] ! IO.select(sockets, sockets, sockets, 1) => nil