SE 765 - Distributed Software Development

CS 610 - Introduction to Parallel and Distributed Computing

Erlang 5 – TCP/UDP Sockets

 This lecture uses materials and has been adapted from:

http://www.erlang.org/doc/pdf/otp-system-documentation.pdf

http://learnyousomeerlang.com/buckets-of-sockets

 

UDP Sockets

Basic operations with UDP:

1.   setting up a socket

2.   sending messages

3.   receiving messages

4.   closing a connection.

A graph showing that Opening a socket can lead to 3 options: sending data, receiving data, or closing a socket. Sending can lead to receiving data or closing a socket, receiving data can lead to sending data or closing a socket. Finally, closing a socket does nothing

First operation: open a socket.

·         Using gen_udp:open/1-2.

·         Done by calling {ok, Socket} = gen_udp:open(PortNumber).

·         Port number : any integer between 1 and 65535.

o   System ports : 0 to 102.

§  Most of the time, OS makes it impossible to listen to a system port unless you have administrative rights.

o   Registered ports : 1024 through 49151.

§  Usually require no permissions and are free to use, although some of them are registered to well known services.

o   Dynamic or private ports: remaining ports.

§  Frequently used for ephemeral ports.

§  For tests here, port numbers that are somewhat safe, such as 8789, are unlikely to be taken.

gen_udp:open/2 : second argument can be a list of options, specifying:

o   in what type we want to receive data (list or binary),

o   how we want them received: as messages ({active, true}) or as results of a function call ({active, false}).

More options:

o   whether the socket should be set with IPv4 (inet4) or IPv6 (inet6),

o   whether the UDP socket can be used to broadcast information ({broadcast, true | false}),

o   size of buffers, etc..

Opening a socket:  First start a given Erlang shell:

1> {ok, Socket} = gen_udp:open(8789, [binary, {active,true}]).

{ok,#Port<0.676>}

2> gen_udp:open(8789, [binary, {active,true}]).

{error,eaddrinuse}

 

o   First command, open the socket, order it to return me binary data, and be active.

o   A new data structure is returned: #Port<0.676>.

o   This is the representation of the socket just opened.

o   Second function call tries to open the same socket over again, which is impossible.

o   A {error, eaddrinuse} is returned.

Start a second Erlang shell and open a second UDP socket, with a different port number:

1> {ok, Socket} = gen_udp:open(8790).

{ok,#Port<0.587>}

2> gen_udp:send(Socket, {127,0,0,1}, 8789, "hey there!").

ok

 

Second call, gen_udp:send/4 is used to send messages.

o   Arguments are: gen_udp:send(OwnSocket, RemoteAddress, RemotePort, Message).

o   The RemoteAddress can be either:

§  a string or an atom containing a domain name ("example.org"),

§  4-tuple describing an IPv4 address

§  8-tuple describing an IPv6 address.

o   The receiver's port number (in what mailbox are we going to drop our slip of paper?),

o   The message, which can be a string, a binary, or an IO list.

Did the message ever get sent? Go back to your first shell and try to flush the data:

3> flush().

Shell got {udp,#Port<0.676>,{127,0,0,1},8790,<<"hey there!">>}

ok

 

o   The process that opened the socket will receive messages of the form: {udp, Socket, FromIp, FromPort, Message}.

What about passive mode? Close the socket from the first shell and open a new one:

4> gen_udp:close(Socket).

ok

5> f(Socket).

ok

6> {ok, Socket} = gen_udp:open(8789, [binary, {active,false}]).

{ok,#Port<0.683>}

The socket is closed, the Socket variable unbounded, then rebound as it is open again in passive mode.

Before sending a message back, try the following:

7> gen_udp:recv(Socket, 0).

o   The shell should be stuck.

o   The function here is recv/2.

o   This function is used to poll a passive socket for messages.

o   0 is the length of the message.

o   If a message is never sent, recv/2 will never return.

Go back to the second shell and send a new message:

3> gen_udp:send(Socket, {127,0,0,1}, 8789, "hey there!").

ok

The first shell should have printed {ok,{{127,0,0,1},8790,<<"hey there!">>}} as the return value.

What if you don't want to wait forever? Just add a time out value:

8> gen_udp:recv(Socket, 0, 2000).

{error,timeout}

 

 

TCP Sockets

While TCP sockets share a large part of their interface with UDP sockets, there are some vital differences in how they work. The biggest one is that clients and servers are two entirely different things.

A client will behave with the following operations:

A diagram similar to the UDP one: connection leads to send and receive, which both send to each other. More over, all states can then lead to the closed state

A server will rather follow this scheme:

Diagram similar to the UDP one, although a listen state is added before the whole thing. That state can either move on to the 'accept' state (similar to 'open socket' for the possible branches) or to a close state.

Weird looking, huh? The client acts a bit like what we had with gen_udp: you connect to a port, send and receive, stop doing so. When serving, however, we have one new mode there: listening. That's because of how TCP works to set sessions up.

Open a new shell and start a listen socket with gen_tcp:listen(Port, Options):

1> {ok, ListenSocket} = gen_tcp:listen(8091, [{active,true}, binary]).

{ok,#Port<0.661>}

The listen socket is just in charge of waiting for connection requests.

Once the listen socket is open, any process (and more than one) can take the listen socket and fall into an accept state, locked up until some client asks to talk with it:

2> {ok, AcceptSocket} = gen_tcp:accept(ListenSocket).

And then the process is locked.

Open a second shell:

1> {ok, Socket} = gen_tcp:connect({127,0,0,1}, 8091, [binary, {active,true}]).

{ok,#Port<0.596>}

Look back at the first shell, it should have returned with {ok, SocketNumber}.

From that point on, the accept socket and the client socket can communicate on a one-on-one basis, similarly to gen_udp.

Use the second shell and send messages to the first:

3> gen_tcp:send(Socket, "Hey there first shell!").

ok

And from the first shell:

7> flush().

Shell got {tcp,#Port<0.729>,<<"Hey there first shell!">>}

ok

Both sockets can send messages in the same way, and can then be closed with gen_tcp:close(Socket).

Note that closing an accept socket will close that socket alone, and closing a listen socket will close all of the related accept sockets.

 

Sample Server

Echo server

Echo is a service that spits back whatever data is handed to it over a TCP connection, bit-for-bit. Here it is in Erlang.

 

-module(echo).

-author('Jesse E.I. Farmer <jesse@20bits.com>').

-export([listen/1]).

-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

% Call echo:listen(Port) to start the service.

listen(Port) ->

     {ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),

     accept(LSocket).

% Wait for incoming connections and spawn the echo loop when we get one.

accept(LSocket) ->

     {ok, Socket} = gen_tcp:accept(LSocket),

     spawn(fun() -> loop(Socket) end),

     accept(LSocket).

% Echo back whatever data we receive on Socket.

loop(Socket) ->

     case gen_tcp:recv(Socket, 0) of

          {ok, Data} ->

              gen_tcp:send(Socket, Data),

              loop(Socket);

          {error, closed} ->

              ok

     end.

 

 

Here's the breakdown of the program, by function.

listen(Port)

    Creates a socket that listens for incoming connections on port Port and passes off control to accept.

accept(LSocket)

    Waits for incoming connections on LSocket. Once it receives a connection it spawns a new process that runs the loop function and then waits for the next connection.

loop(Socket)

    Waits for incoming data on Socket. Once it receives the data it immediately sends the same data back across the socket. If there is an error it exits.

Start this service by calling echo:listen(<port number>). from the Erlang shell.

e.g., echo:listen(8888). will start the echo service on port 8888 of your machine.

You can then telnet to port 8888 — telnet 127.0.0.1 8888 — and see it in action.

Exercise: write a client that talks to the echo server.