docs » hs.socket.udp

Talk to custom protocols using asynchronous UDP sockets

For TCP sockets see hs.socket

You can do a lot of neat trivial and non-trivial things with these. A simple ping ponger:

function ping(data, addr)
  print(data)
  addr = hs.socket.parseAddress(addr)
  hs.timer.doAfter(1, function()
    client:send("ping", addr.host, addr.port)
  end)
end

function pong(data, addr)
  print(data)
  addr = hs.socket.parseAddress(addr)
  hs.timer.doAfter(1, function()
    server:send("pong", addr.host, addr.port)
  end)
end

server = hs.socket.udp.server(9001, pong):receive()
client = hs.socket.udp.new(ping):send("ping", "localhost", 9001):receive()
Resulting in the following endless exchange:
20:26:56    LuaSkin: (secondary thread): Data written to UDP socket
            LuaSkin: (secondary thread): Data read from UDP socket
ping
20:26:57    LuaSkin: (secondary thread): Data written to UDP socket
            LuaSkin: (secondary thread): Data read from UDP socket
pong
20:26:58    LuaSkin: (secondary thread): Data written to UDP socket
            LuaSkin: (secondary thread): Data read from UDP socket
ping
20:26:59    LuaSkin: (secondary thread): Data written to UDP socket
            LuaSkin: (secondary thread): Data read from UDP socket
pong
...

You can do some silly things with a callback factory and enabling broadcasting:

local function callbackMaker(name)
  local fun = function(data, addr)
    addr = hs.socket.parseAddress(addr)
    print(name.." received data:\n"..data.."\nfrom host: "..addr.host.." port: "..addr.port)
  end
  return fun
end

local listeners = {}
local port = 9001

for i=1,3 do
  table.insert(listeners, hs.socket.udp.new(callbackMaker("listener "..i)):reusePort():listen(port):receive())
end

broadcaster = hs.socket.udp.new():broadcast()
broadcaster:send("hello!", "255.255.255.255", port)
Since neither IPv4 nor IPv6 have been disabled, the broadcast is received on both protocols ('dual-stack' IPv6 addresses shown):
listener 2 received data:
hello!
from host: ::ffff:192.168.0.3 port: 53057
listener 1 received data:
hello!
from host: ::ffff:192.168.0.3 port: 53057
listener 3 received data:
hello!
from host: ::ffff:192.168.0.3 port: 53057
listener 1 received data:
hello!
from host: 192.168.0.3 port: 53057
listener 3 received data:
hello!
from host: 192.168.0.3 port: 53057
listener 2 received data:
hello!
from host: 192.168.0.3 port: 53057

API Overview

API Documentation

Variables

timeout
Signature hs.socket.udp.timeout
Type Variable
Description

Timeout for the socket operations, in seconds. New hs.socket.udp objects will be created with this timeout value, but can individually change it with the setTimeout method

If the timeout value is negative, the operations will not use a timeout. The default value is -1

Functions

parseAddress
Signature hs.socket.udp.parseAddress(sockaddr) -> table or nil
Type Function
Description

Alias for hs.socket.parseAddress

Constructors

new
Signature hs.socket.udp.new([fn]) -> hs.socket.udp object
Type Constructor
Description

Creates an unconnected asynchronous UDP socket object

Parameters:

  • fn - An optional callback function for reading data from the socket, settable here for convenience

Returns:

server
Signature hs.socket.udp.server(port[, fn]) -> hs.socket.udp object
Type Constructor
Description

Creates and binds an hs.socket.udp instance to a port for listening

Parameters:

  • port - A port number [0-65535]. Ports [1-1023] are privileged. Port 0 allows the OS to select any available port
  • fn - An optional callback function for reading data from the socket, settable here for convenience

Returns:

Methods

broadcast
Signature hs.socket.udp:broadcast([flag]) -> self or nil
Type Method
Description

Enables broadcasting on the underlying socket

Parameters:

  • flag - An optional boolean: true to enable broadcasting, false to disable it. Defaults to true

Returns:

Notes:

  • By default, the underlying socket in the OS will not allow you to send broadcast messages
  • In order to send broadcast messages, you need to enable this functionality in the socket
  • A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is delivered to every host on the network.
  • The reason this is generally disabled by default (by the OS) is to prevent accidental broadcast messages from flooding the network.
close
Signature hs.socket.udp:close() -> self
Type Method
Description

Immediately closes the underlying socket, freeing the hs.socket.udp instance for reuse. Any pending send operations are discarded

Parameters:

  • None

Returns:

closed
Signature hs.socket.udp:closed() -> bool
Type Method
Description

Returns the closed status of the hs.socket.udp instance

Parameters:

  • None

Returns:

  • true if closed, otherwise false

Notes:

  • UDP sockets are typically meant to be connectionless
  • Sending a packet anywhere, regardless of whether or not the destination receives it, opens the socket until it is explicitly closed
  • An active listening socket will not be closed, but will not be 'connected' unless the connect method has been called
connect
Signature hs.socket.udp:connect(host, port[, fn]) -> self or nil
Type Method
Description

Connects an unconnected hs.socket.udp instance

Parameters:

  • host - A string containing the hostname or IP address
  • port - A port number [1-65535]
  • fn - An optional single-use callback function to execute after establishing the connection. Receives no parameters

Returns:

Notes:

  • By design, UDP is a connectionless protocol, and connecting is not needed
  • Choosing to connect to a specific host/port has the following effect:
    • You will only be able to send data to the connected host/port
    • You will only be able to receive data from the connected host/port
    • You will receive ICMP messages that come from the connected host/port, such as "connection refused"
  • The actual process of connecting a UDP socket does not result in any communication on the socket. It simply changes the internal state of the socket
  • You cannot bind a socket after it has been connected
  • You can only connect a socket once
connected
Signature hs.socket.udp:connected() -> bool
Type Method
Description

Returns the connection status of the hs.socket.udp instance

Parameters:

  • None

Returns:

  • true if connected, otherwise false

Notes:

  • UDP sockets are typically meant to be connectionless
  • This method will only return true if the connect method has been explicitly called
enableIPv
Signature hs.socket.udp:enableIPv(version[, flag]) -> self or nil
Type Method
Description

Enables or disables IPv4 or IPv6 on the underlying socket. By default, both are enabled

Parameters:

  • version - A number containing the IP version (4 or 6) to enable or disable
  • flag - A boolean: true to enable the chosen IP version, false to disable it. Defaults to true

Returns:

Notes:

  • Must be called before binding the socket. If you want to create an IPv6-only server, do something like:
    • hs.socket.udp.new(callback):enableIPv(4, false):listen(port):receive()
  • The convenience constructor hs.socket.server will automatically bind the socket and requires closing and relistening to use this method
info
Signature hs.socket.udp:info() -> table
Type Method
Description

Returns information on the hs.socket.udp instance

Parameters:

  • None

Returns:

  • A table containing the following keys:
    • connectedAddress - string (sockaddr struct)
    • connectedHost - string
    • connectedPort - number
    • isClosed - boolean
    • isConnected - boolean
    • isIPv4 - boolean
    • isIPv4Enabled - boolean
    • isIPv4Preferred - boolean
    • isIPv6 - boolean
    • isIPv6Enabled - boolean
    • isIPv6Preferred - boolean
    • isIPVersionNeutral - boolean
    • localAddress - string (sockaddr struct)
    • localAddress_IPv4 - string (sockaddr struct)
    • localAddress_IPv6 - string (sockaddr struct)
    • localHost - string
    • localHost_IPv4 - string
    • localHost_IPv6 - string
    • localPort - number
    • localPort_IPv4 - number
    • localPort_IPv6 - number
    • maxReceiveIPv4BufferSize - number
    • maxReceiveIPv6BufferSize - number
    • timeout - number
    • userData - string
listen
Signature hs.socket.udp:listen(port) -> self or nil
Type Method
Description

Binds an unconnected hs.socket.udp instance to a port for listening

Parameters:

  • port - A port number [0-65535]. Ports [1-1023] are privileged. Port 0 allows the OS to select any available port

Returns:

pause
Signature hs.socket.udp:pause() -> self
Type Method
Description

Suspends reading of packets from the socket. Call one of the receive methods to resume

Parameters:

  • None

Returns:

preferIPv
Signature hs.socket.udp:preferIPv([version]) -> self
Type Method
Description

Sets the preferred IP version: IPv4, IPv6, or neutral (first to resolve)

Parameters:

  • version - An optional number containing the IP version to prefer. Anything but 4 or 6 else sets the default neutral behavior

Returns:

Notes:

  • If a DNS lookup returns only IPv4 results, the socket will automatically use IPv4
  • If a DNS lookup returns only IPv6 results, the socket will automatically use IPv6
  • If a DNS lookup returns both IPv4 and IPv6 results, then the protocol used depends on the configured preference
read
Signature hs.socket.udp:read(delimiter[, tag]) -> self
Type Method
Description

Alias for hs.socket.udp:receive

readOne
Signature hs.socket.udp:readOne(delimiter[, tag]) -> self
Type Method
Description

Alias for hs.socket.udp:receiveOne

receive
Signature hs.socket.udp:receive([fn]) -> self or nil
Type Method
Description

Reads packets from the socket as they arrive. Results are passed to the callback function, which must be set to use this method

Parameters:

Returns:

Notes:

  • There are two modes of operation for receiving packets: one-at-a-time & continuous
  • In one-at-a-time mode, you call receiveOne every time you are ready process an incoming UDP packet
  • Receiving packets one-at-a-time may be better suited for implementing certain state machine code where your state machine may not always be ready to process incoming packets
  • In continuous mode, the callback is invoked immediately every time incoming udp packets are received
  • Receiving packets continuously is better suited to real-time streaming applications
  • You may switch back and forth between one-at-a-time mode and continuous mode
  • If the socket is currently in one-at-a-time mode, calling this method will switch it to continuous mode
receiveOne
Signature hs.socket.udp:receiveOne([fn]) -> self or nil
Type Method
Description

Reads a single packet from the socket. Results are passed to the callback function, which must be set to use this method

Parameters:

Returns:

Notes:

  • There are two modes of operation for receiving packets: one-at-a-time & continuous
  • In one-at-a-time mode, you call receiveOne every time you are ready process an incoming UDP packet
  • Receiving packets one-at-a-time may be better suited for implementing certain state machine code where your state machine may not always be ready to process incoming packets
  • In continuous mode, the callback is invoked immediately every time incoming udp packets are received
  • Receiving packets continuously is better suited to real-time streaming applications
  • You may switch back and forth between one-at-a-time mode and continuous mode
  • If the socket is currently in continuous mode, calling this method will switch it to one-at-a-time mode
reusePort
Signature hs.socket.udp:reusePort([flag]) -> self or nil
Type Method
Description

Enables port reuse on the underlying socket

Parameters:

  • flag - An optional boolean: true to enable port reuse, false to disable it. Defaults to true

Returns:

Notes:

  • By default, only one socket can be bound to a given IP address+port at a time
  • To enable multiple processes to simultaneously bind to the same address+port, you need to enable this functionality in the socket
  • All processes that wish to use the address+port simultaneously must all enable reuse port on the socket bound to that port
  • Must be called before binding the socket
send
Signature hs.socket.udp:send(message, host, port[, tag][, fn]) -> self
Type Method
Description

Sends a packet to the destination address

Parameters:

  • message - A string containing data to be sent on the socket
  • host - A string containing the hostname or IP address
  • port - A port number [1-65535]
  • tag - An optional integer to assist with labeling writes
  • fn - An optional single-use callback function to execute after sending the packet. Receives the tag parameter

Returns:

Notes:

  • For non-connected sockets, the remote destination is specified for each packet
  • If the socket has been explicitly connected with connect, only the message parameter and an optional tag and/or write callback can be supplied
  • Recall that connecting is optional for a UDP socket
  • For connected sockets, data can only be sent to the connected address
setBufferSize
Signature hs.socket.udp:setBufferSize(size[, version]) -> self
Type Method
Description

Sets the maximum size of the buffer that will be allocated for receive operations

Parameters:

  • size - An number containing the receive buffer size in bytes
  • version - An optional number containing the IP version for which to set the buffer size. Anything but 4 or 6 else sets the same size for both

Returns:

Notes:

  • The default maximum size is 9216 bytes
  • The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535
  • The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295
  • Since the OS notifies us of the size of each received UDP packet, the actual allocated buffer size for each packet is exact
  • In practice the size of UDP packets is generally much smaller than the max. Most protocols will send and receive packets of only a few bytes, or will set a limit on the size of packets to prevent fragmentation in the IP layer.
  • If you set the buffer size too small, the sockets API in the OS will silently discard any extra data
setCallback
Signature hs.socket.udp:setCallback([fn]) -> self
Type Method
Description

Sets the read callback for the hs.socket.udp instance. Must be set to read data from the socket

The callback receives 2 parameters:

  • data - The data read from the socket as a string
  • sockaddr - The sending address as a binary socket address structure. See parseAddress

Parameters:

  • fn - An optional callback function to process data read from the socket. nil or no argument clears the callback

Returns:

setTimeout
Signature hs.socket.udp:setTimeout(timeout) -> self
Type Method
Description

Sets the timeout for the socket operations. If the timeout value is negative, the operations will not use a timeout, which is the default

Parameters:

  • timeout - A number containing the timeout duration, in seconds

Returns:

write
Signature hs.socket.udp:write(message[, tag]) -> self
Type Method
Description

Alias for hs.socket.udp:send