You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Network programming is ultimately about sharing and communication.
Audience: Ruby devs on Unix or Unix-like systems.
Uses Ruby 1.9.
Part 1 - Introduction to the primitives of Socket programming
Create sockets
Connect sockets together
Share data
Part 2 - Advanced topics in Socket programming
Part 3 - Applications of the first two parts to a "real world" scenario
Apply concurrency to your network programs
Learn about various architectural patterns
Focuses on Berkeley Sockets API
Appeared with version 4.2 of the BSD operating system in 1983
First implementation of TCP
Implemented in C
Benefit: You can use sockets without having to know the details of the underlying protocol.
netcat
nc - A Unix utility for creating arbitrary TCP (and UDP) connections and listens. It's a useful tool to have in your toolbox when working with sockets. See man 1 nc for more details.
# creating a socket
require 'socket'
socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
Socket::AF_INET implies a socket in the IPv4 family of protocols. The Socket::SOCK_STREAM part says you'll be communicating using a stream which is provided by TCP. For a UDP socket you would use Socket::SOCK_DGRAM.
Socket::AF_INET is equivalent to :INET. Socket::SOCK_STREAM is equivalent to :STREAM.
A host is identified by a unique IP address. Think of an IP address as the host's phone number.
People don't remember IP addresses so they use words. DNS brings the two together. See How DNS Works.
The loopback interface:
a virtual interface, it's not attached to any hardware
If you want to have a conversation with someone in an office building you'll have to call their phone number, then dial their extension. The port number is the 'extension' of a socket endpoint.
N.B.: The combination of IP address and port number must be unique for each socket.
Docs
manpages
ri
TODO: Find out how to install the Ruby docs locally.
The typical lifecycle of a server socket looks like:
create
bind
listen
accept
close
Servers Bind
Following is a low-level implementation showing how to bind a TCP socket to a local port.
require 'socket'
# First, create a new TCP socket.
socket = Socket.new(:INET, :STREAM)
# Create a C struct to hold the address for listening.
addr = Socket.pack_sockaddr_in(4481, '0.0.0.0')
# Bind to it.
socket.bind(addr)
A server binds to a specific, agreed-upon port number which a client socket can then connect to.
N.B.: Ruby provides syntactic sugar so that you never have to actually use Socket.pack_sockaddr_in or Socket#bind directly.
What port should I bind to?
a port number can range from 1-65,535
port numbers in the range 0-1024 are 'well-known' ports and are reserved for system use. For example HTTP traffic defaults to port 80 and SMTP traffic defaults to port 25
port numbers in the range 49,000-65,535 are ephemeral ports. They are used by services that don't operate on a predefined port number but need ports for temporary purposes. They are also an integral part of the connection negotiation process.
port numbers in the range 1025-48999 is fair game for your uses. If you are planning on claiming one of those ports as the port for your server then you should have a look at the IANA list of registered ports and make sure that your choice does not conflict with some other popular service out there.
What address should I bind to?
When you bind to a specific interface, represented by its IP address, your socket is only listening on that interface. It will ignore the others.
If you bind to 127.0.0.1 then your socket will only be listening on the loopback interface. In this case, only connections made to localhost or 127.0.0.1 will be routed to your server socket. Since this interface is only available locally, no external connections will be allowed.
If you want to listen on all interfaces then you can use 0.0.0.0. This will bind to any available interface, loopback or otherwise.
Servers Listen
After creating a socket, and binding to a port, the socket needs to be told to listen for incoming connections.
require 'socket'
# Create a socket and bind it to port 4481
socket = Socket.new(:INET, :STREAM)
addr = Socket.pack_sockaddr_in(4481, '0.0.0.0')
socket.bind(addr)
# Tell it to listen for incoming connections
socket.listen(5)
The number passed to the listen method represents the maximum number of pending connections your server socket is willing to tolerate. This list of pending connections is called the listen queue.
If your server is busy processing a client connection, then when any new client connections arrive they'll be put into the listen queue. If a new client connection arrives and the listen queue is full then the client will raise Errno::ECONNREFUSED.
How big should the listen queue be?
Why wouldn't we want to set it to 10,000?
Why would we ever want to refuse a connection?
First, we need to understand the limits. You can get the current maximum allowed listen queue size by inspecting Socket::SOMAXCONN at runtime. On my machine it is 128. So I'm not able to use a number larger than that. The root user is able to increase this limit at the system level for servers that need it.
Let's say you're running a server and you're getting reports of Errno::ECONNREFUSED. Increasing the size of the listen queue would be a good starting point. But ultimately you don't want to have connections waiting in your listen queue. That means that users of your service are having to wait for their responses. This may be an indication that you need more server instances or that you need a different architecture.
Generally you don't want to be refusing connections. You can set the listen queue to the maximum allowed queue size using server.listen(Socket::SOMAXCONN).
Server Accept
A server handles an incoming connection by calling the accept method. Here's how to create a listening socket and receive the first connection:
require 'socket'
# Create the server socket.
socket = Socket.new(:INET, :STREAM)
addr = Socket.pack_sockaddr_in(4481, '0.0.0.0')
socket.bind(addr)
socket.listen(128)
# Accept a connection.
connection, _ = server.accept
The accept call is a blocking call. It will block the current thread indefinitely until it receives a new connection.
Remember the listen queue? accept simply pops the next pending connection off of that queue. If none are available it waits for one to be pushed onto it.
The accept method returns an Array. The Array contains two elements:
the connection, and
an Addrinfo object. This represents the remote address of the client connection.
Although accept returns a 'connection', a connection is actually an instance of Socket. Each connection is represented by a new Socket object so that the server socket can remain untouched and continue to accept new connections. The connection object knows about two addresses: the local address and the remote address. The remote address is the second return value returned from accept but can also be accessed as remote_address on the connection.
The local_address of the connection refers to the endpoint on the local machine.
The remote_address of the connection refers to the endpoint at the other end, which might be on another host or the same machine.
N.B.: The combination of local-host, local-port, remote-host and remote-port must be unique for each TCP connection.
accept returns one connection. When writing a production server we usually want to continually listen for incoming connections so long there are more available. This can be easily accomplished with a loop:
require 'socket'
# Create the server socket.
server = Socket.new(:INET, :STREAM)
addr = Socket.pack_sockaddr_in(4481, '0.0.0.0')
server.bind(addr)
server.listen(128)
# Enter an endless loop of accepting and
# handling connections.
loop do
connection, _ = server.accept
# handle connection
connection.close
end
Servers Close
Once a server has accepted a connection and finished processing it, the last thing for it to do is to close that connection as it rounds out the create-process-close lifecycle of a connection.
Where did you buy the book from?