Socket.Udp
Some protocols exchange separate short messages rather than a stream of bytes, and UDP is how such messages travel. This module provides UDP sockets, which send and receive datagrams over IPv4 and IPv6.
A datagram is one message: a sequence of bytes sent in one piece to an address and port. It
arrives whole or not at all, and the network may lose, duplicate or reorder datagrams without
telling either end. So datagrams here are values, not streams: send takes one
Bytes, and receive and onMessage hand out one
Datagram at a time, with the endpoint it came from.
A Socket is bound to one local address and port with bind. The family
of that address, IPv4 or IPv6, is the socket's family. One socket can send to any number of
endpoints and receives the datagrams sent to its own endpoint. An open socket keeps the
program running only while a receive is waiting or an onMessage
subscription exists.
import Socket
import Socket.Address as Address exposing (Family(..))
import Socket.Udp
import Task exposing (Task)
bindLoopback : Task Socket.Error Socket.Udp.Socket
bindLoopback =
Socket.Udp.bind (Socket.Udp.defaultBindOptions (Address.loopback IPv4) 0)Arriving datagrams are handed out by one rule, the same one Socket describes for the
connections of a listener. A new datagram goes to the oldest waiting receive; if there is none,
to every onMessage subscription to that socket, all of which receive the same Datagram; and
if there is neither, it is held until the next receive or subscription, which takes the held
datagrams oldest first. The kernel holds at most 64 datagrams per socket, and drops the oldest
beyond that. Where a datagram that nobody takes waits depends on the backend. On the JavaScript
backend every datagram is read as soon as it arrives, so it is held. On the native backend the
socket reads only while a receive is waiting or a subscription exists, so a datagram that
arrives while there is neither stays in the operating system's receive buffer.
A multicast group is an IP address that stands for the sockets that have joined it, so that a
datagram sent to the group's address and a port can reach each of them that is bound to that
port. joinMulticast and leaveMulticast manage a
socket's membership.
Failures are Socket.Error values, whose
Socket.ErrorKind says what went wrong. On native Windows, UDP is not
supported: bind fails with Socket.NotSupported, and an onMessage
subscription is a fatal error.
Sockets
type alias Socket =
UdpSockettype alias Datagram =
{ data : Bytes, from : InetEndpoint }A received datagram: its bytes, and the endpoint of the socket that sent it.
On a socket bound to an IPv6 address with ipv6Only = False, a datagram from an IPv4 sender
reports the sender as an IPv4-mapped address; Socket.Address.unmapIPv4
gives the IPv4 address. The sender's address is read from text the kernel prints, and text that
Socket.Address.fromString cannot read becomes the unspecified
address. That can happen for an IPv6 sender whose scope names an interface with a character
other than an ASCII letter or digit, _, . or - in its name.
type alias BindOptions =
{ address : Address
, port_ : Int
, reuseAddress : Bool
, broadcast : Bool
, ipv6Only : Bool
}The settings a socket is bound with.
address and port_ are the local endpoint. Port 0 asks the operating system to choose a free
port, which localEndpoint then reports. The unspecified address
(Socket.Address.any) receives on every interface.
reuseAddress lets several sockets that all set it bind the same address and port
(SO_REUSEADDR), for example several receivers of one multicast group on one machine.
broadcast allows sending to a broadcast address (SO_BROADCAST). Without it the operating
system refuses such a send, with Socket.PermissionDenied on Linux.
ipv6Only matters only for an IPv6 address. When it is False, a socket bound to the
unspecified IPv6 address also exchanges datagrams with IPv4 endpoints, which it sees as
IPv4-mapped addresses. When it is True, the socket uses IPv6 only.
defaultBindOptions : Address -> Int -> BindOptionsReturns the options for binding to address and port_ with reuseAddress, broadcast
and ipv6Only all False.
bind : BindOptions -> Task Error SocketCreates a UDP socket bound to the address and port the options name.
Fails, for example, with Socket.AddressInUse when another socket already
has that address and port, or with Socket.AddressNotAvailable for an
address that does not belong to this machine.
localEndpoint : Socket -> InetEndpointReturns the address and port the socket is bound to, read when the socket was bound. For a
socket bound to port 0, the port is the one the operating system chose. An address
Socket.Address.fromString cannot read, which can happen for an
IPv6 address whose scope names an interface with a character other than an ASCII letter or
digit, _, . or - in its name, is reported as the unspecified address.
close : Socket -> Task x ()Closes the socket.
Waiting receive tasks, and sends still waiting to go out, fail with
Socket.Cancelled,
and held datagrams are dropped. Closing a socket that is already closed does nothing.
Sending and receiving
send : InetEndpoint -> Bytes -> Socket -> Task Error ()Sends one datagram holding data to the endpoint to.
The task succeeds once the operating system has accepted the datagram, which does not mean that it arrived.
An IPv6 destination on an IPv4 socket fails with
Socket.AddressNotAvailable. An IPv4 destination on an IPv6 socket is sent
to its IPv4-mapped address, which works only when the socket was bound with ipv6Only = False;
otherwise the send fails with the operating system's error. A datagram too large for the
operating system to send fails, usually with Socket.MessageTooLarge; over
IPv4 no datagram can carry more than 65 507 bytes.
receive : Socket -> Task Error DatagramWaits for the socket's next datagram.
A held datagram, the oldest first, is returned at once. Otherwise the task waits, and a new
datagram goes to the oldest waiting receive before any onMessage subscription
sees it.
Fails with Socket.Cancelled when the socket is closed while the task
waits, and at once on a socket that is already closed.
onMessage : Socket -> (Datagram -> msg) -> Sub msgCreates a subscription to the datagrams the socket receives that no waiting
receive takes, including those held before the subscription started.
subscriptions : Model -> Sub Msg
subscriptions model =
Socket.Udp.onMessage model.socket GotDatagramA subscription made to a socket that is already closed never fires.
Multicast
joinMulticast : Address -> Maybe Address -> Socket -> Task Error ()Joins a multicast group on one network interface, or with Nothing on one the operating
system chooses, so that the socket can receive the datagrams sent to the group at the port it is
bound to.
The interface is given as an address of the group's family; one of the other family fails with
Socket.InvalidInput. For IPv4 it is an address of the interface. For IPv6 it is the address's scope that
names the interface, by name or by index, and the address bits are ignored, so "::%lo" names
the interface lo. With Nothing, an IPv6 group's own scope, if it has one, names the
interface instead. An IPv6 interface address with no scope leaves the choice to the operating
system, and the group's own scope is then not used.
leaveMulticast : Address -> Maybe Address -> Socket -> Task Error ()Leaves a multicast group joined with joinMulticast, given the same group
and interface.
A UDP socket bound to a local address and port, made by
bind.The socket stays open until it is closed with
close. On a closed socket,send,receive,joinMulticastandleaveMulticastfail withSocket.Cancelled, andclosedoes nothing.