Written from a line-by-line source review; every example output is from a real run.
Introduction
The callbackudpclient plugin is the counterpart of callbackudpserver: it opens a UDP-sending endpoint on loopback and works **bidirectionally**. With SendText/SendBlob calls it sends datagrams to a given host:port address, and a concurrent background thread forwards any incoming replies to a script-level callback. So a single plugin serves both request sending and reply receiving — a classic UDP request-response pattern.
Relationship to callbackudpserver and asyncmeta
The two plugins (client and server) share lifecycle and state model. State returns asyncmeta.RouteStateOpen (=1) or RouteStateClosed (=0); StateText is the “open”/“closed” text counterpart. The callback blob layout is identical on both sides: 4 bytes port (LE u32) + 4 bytes hostLen (LE u32) + hostLen bytes host string + payload. A UDP client used together with the server thus reads the same blob format.
Lifecycle in a nutshell
(1) `Open(callbackName, timeoutMs, localPort?)` opens a UDP socket on loopback and starts a background thread for incoming replies. (2) `SendText` or `SendBlob` sends a datagram. (3) If a reply arrives, the background thread forwards it to the callback in a blob with port + hostLen + host + payload. (4) `Close` shuts down the socket and waits for the thread to stop. Meanwhile, the state and diagnostic functions (LocalPort, State, Delivered, Dropped, Info, OpenCount) can be queried any time.
Loading the plugin
plugin "../plugins/print/PrintPlugin"; plugin "../plugins/callback_udp_client/CallbackUdpClientPlugin";
A typical flow — UDP request-response
callback i32 OnReply(ref blob $X)
{
// ... process the reply ...
return(1);
}
i32 main()
{
i64 $cli;
$cli = callbackudpclient.Open("OnReply", -1, 0);
callbackudpclient.SendText($cli, "127.0.0.1", 9999, "PING");
// ... wait (e.g. timer.SleepMs), meanwhile OnReply may fire ...
callbackudpclient.Close($cli);
return(0);
}What to know about every function (the basics)
Open always binds to LOOPBACK (127.0.0.1) — a security default, as on the server side.
localPort = 0 lets the OS pick a free port; the actual port is returned by LocalPort.
timeout = -1 means infinite wait for callback invocation. With a positive timeoutMs, the background thread DROPS an incoming packet if the callback does not fit in the time limit.
SendBlob and SendText return the ACTUALLY sent byte count — for UDP, this is normally exactly the payload size.
SendText internally calls SendBlob: it wraps the string as a read-only external blob and sends it.
EnableAnsiColors returns 1 on Linux (already supported); on Windows it tries to enable virtual-terminal mode (ENABLE_VIRTUAL_TERMINAL_PROCESSING) — handy for colored logging/diagnostic output.
An error (stopping the script) is caused by a wrong argument count or type, an out-of-range port (0..65535) or timeout, an invalid clientId, a host resolution failure, touching an already-closed client, and a socket/thread startup failure.
How to read the signatures
callbackName is the script-level reply callback name. timeoutMs is the maximum wait for callback invocation in milliseconds; -1 = infinite. localPort is the local (client-side) UDP port (0 = OS picks). clientId is the positive id given by Open. host is an IPv4 string or a name-resolvable name (typically “127.0.0.1” in the sandbox). The type after the -> arrow is the return type.
Lifecycle
Opening the client endpoint on a loopback port and closing it. Open starts a background thread for incoming replies; Close waits for its shutdown.
callbackudpclient.Open
callbackudpclient.Open(callbackName, timeoutMs, localPort?) -> int
Opens a UDP client endpoint on loopback IP and forwards incoming replies to the callback via a background thread.
| Parameter | Type | Description |
|---|---|---|
| callbackName | string | The name of the script-level reply callback. Signature: `callback i32 NAME(ref blob $X)`. |
| timeoutMs | int | The maximum wait for callback invocation in milliseconds. -1 = infinite; otherwise 0..2147483647. |
| localPort | int | Optional local UDP port to bind. 0..65535; 0 lets the OS pick. Default: 0. |
The clientId (positive i64). A bad argument or a bind failure raises a runtime error.
callback i32 OnReply(ref blob $X) { return(1); }
i64 $cli;
$cli = callbackudpclient.Open("OnReply", -1, 0);
printf("%d\n", ($cli > 0));1
For opening communication with a UDP service. The 0-localPort is handy in CI/test environments since it does not clash with bound ports. The returned clientId is the input of SendText/SendBlob and every further operation.
callbackudpclient.Close
callbackudpclient.Close(clientId) -> int
Closes the client: stops the background thread, shuts down the socket, and releases resources.
| Parameter | Type | Description |
|---|---|---|
| clientId | int | The positive client id from Open. |
1 if the client was open and closed successfully; 0 if it was already closed. An unknown or non-positive clientId raises a runtime error.
i64 $cli;
$cli = callbackudpclient.Open("OnReply", -1, 0);
printf("%d\n", callbackudpclient.Close($cli));1
For a clean client shutdown. Close waits for the background thread to actually stop, so the reply callback will no longer fire afterward.
Sending
Sending datagrams to a host:port address. Both verbs return the actually sent byte count.
callbackudpclient.SendText
callbackudpclient.SendText(clientId, host, port, text) -> int
Sends a text datagram to the given address (host:port). Internally calls SendBlob with the text as a read-only blob.
| Parameter | Type | Description |
|---|---|---|
| clientId | int | The client id. |
| host | string | The destination IPv4 or name-resolvable host (e.g. “127.0.0.1”). |
| port | int | The destination UDP port (1..65535). |
| text | string | The text to send (UDP payload bytes). |
The actually sent byte count. A bad argument, an unknown clientId, or a `sendto` failure raises a runtime error.
i64 $cli;
$cli = callbackudpclient.Open("OnReply", -1, 0);
printf("%d\n", callbackudpclient.SendText($cli, "127.0.0.1", 9999,
"hello"));5
For text protocols (e.g. ASCII commands, JSON payloads). The return is usually the text length; if smaller, it's truncation or a network error — worth checking.
callbackudpclient.SendBlob
callbackudpclient.SendBlob(clientId, host, port, blob) -> int
Sends a binary data packet to the given address. Passes the blob's full content to `sendto`.
| Parameter | Type | Description |
|---|---|---|
| clientId | int | The client id. |
| host | string | The destination IPv4 or name-resolvable host. |
| port | int | The destination UDP port (1..65535). |
| blob | blob | The binary data to send. The blob's full (Size field) length is sent. |
The actually sent byte count. A bad argument, an unknown clientId, or a `sendto` failure raises a runtime error.
i64 $cli;
blob $b[4];
$b[0] = 65;
$b[1] = 66;
$b[2] = 67;
$b[3] = 68;
$cli = callbackudpclient.Open("OnReply", -1, 0);
printf("%d\n", callbackudpclient.SendBlob($cli, "127.0.0.1", 9999,
$b));4
For binary protocols (struct.assign-ed packets, length-prefixed messages, frame packets). Struct-shaped blobs can be sent directly if you cared about correct endianness when building the content.
State queries
Inspecting the client's current state: the local (bound) port, and the “open/closed” state in both numeric and textual form.
callbackudpclient.LocalPort
callbackudpclient.LocalPort(clientId) -> int
Returns the client's local (bound) UDP port.
| Parameter | Type | Description |
|---|---|---|
| clientId | int | The client id. |
The local port (1..65535).
i64 $cli;
$cli = callbackudpclient.Open("OnReply", -1, 0);
printf("%d\n", (callbackudpclient.LocalPort($cli) > 0));1
Particularly useful when you passed localPort=0 to Open (asking for an OS-picked port). You query the returned port number with this so you know which port replies arrive on.
callbackudpclient.State
callbackudpclient.State(clientId) -> int
Returns the client's state using the asyncmeta route-state constants.
| Parameter | Type | Description |
|---|---|---|
| clientId | int | The client id. |
asyncmeta.RouteStateOpen (=1) if open; asyncmeta.RouteStateClosed (=0) if closed.
i64 $cli;
$cli = callbackudpclient.Open("OnReply", -1, 0);
printf("%d\n", callbackudpclient.State($cli));1
For programmatic state checking, comparing against asyncmeta.RouteStateOpen. Shared numeric code in the callback_* family — uniform processing.
callbackudpclient.StateText
callbackudpclient.StateText(clientId) -> string
Returns the state in human-readable text.
| Parameter | Type | Description |
|---|---|---|
| clientId | int | The client id. |
“open” if open; “closed” if closed.
i64 $cli;
$cli = callbackudpclient.Open("OnReply", -1, 0);
printf("%s\n", callbackudpclient.StateText($cli));open
For logging or UI display. The textual counterpart of State's numeric value.
Diagnostics
Counters for received replies, an aggregated key=value state, a plugin-level OpenCount, and a diag helper for ANSI colors.
callbackudpclient.Delivered
callbackudpclient.Delivered(clientId) -> int
Returns the count of reply packets successfully processed (forwarded to the reply callback) so far.
| Parameter | Type | Description |
|---|---|---|
| clientId | int | The client id. |
The number of processed replies.
i64 $cli;
$cli = callbackudpclient.Open("OnReply", -1, 0);
printf("%d\n", callbackudpclient.Delivered($cli));0
For monitoring reply throughput. The difference between start and end values of a period gives the replies handled in it.
callbackudpclient.Dropped
callbackudpclient.Dropped(clientId) -> int
Returns the count of dropped reply packets (due to callback timeout or buffer allocation failure).
| Parameter | Type | Description |
|---|---|---|
| clientId | int | The client id. |
The number of dropped replies.
i64 $cli;
$cli = callbackudpclient.Open("OnReply", -1, 0);
printf("%d\n", callbackudpclient.Dropped($cli));0
An early signal if the reply callback does not fit in timeoutMs, or the buffer pool is exhausted. A bigger timeoutMs or a faster callback fixes it.
callbackudpclient.Info
callbackudpclient.Info(clientId) -> string
Returns the client's full key=value diagnostic state as a string.
| Parameter | Type | Description |
|---|---|---|
| clientId | int | The client id. |
A space-separated key=value list (id, state, stateCode, callback, timeout, localPort, delivered, dropped).
i64 $cli;
$cli = callbackudpclient.Open("OnReply", -1, 0);
printf("%s\n", callbackudpclient.Info($cli));id=1 state=open stateCode=1 callback=OnReply timeout=-1 localPort=<auto> delivered=0 dropped=0
For detailed diagnostics in a single call. The keys are stable; easy to break down further with str.* operations.
callbackudpclient.OpenCount
callbackudpclient.OpenCount() -> int
Returns the number of currently open UDP clients (plugin-level, global view).
This function takes no arguments.
The number of open clients (0 if none).
printf("%d\n", callbackudpclient.OpenCount());
i64 $cli;
$cli = callbackudpclient.Open("OnReply", -1, 0);
printf("%d\n", callbackudpclient.OpenCount());
callbackudpclient.Close($cli);
printf("%d\n", callbackudpclient.OpenCount());0 1 0
For resource control and diagnostics: after Close it should return to 0 if everything was properly released. Useful for early detection of resource leaks.
callbackudpclient.EnableAnsiColors
callbackudpclient.EnableAnsiColors() -> int
Enables ANSI tools (colored logging) on the current standard output.
This function takes no arguments.
1 if enabled or already supported (Linux/macOS); 0 if not (e.g. non-console output on Windows).
printf("%d\n", callbackudpclient.EnableAnsiColors());1
For colored output in logs or diag messages. On Linux/macOS it's natively supported (always 1); on Windows it tries to enable the virtual-terminal mode (ENABLE_VIRTUAL_TERMINAL_PROCESSING).
Practical notes
What the callbackudpclient plugin is good for
Sending requests to UDP server(s) on loopback (test environment, mock client, debug tool).
Request-reply pattern: after a Send*, the background thread forwards incoming replies to a callback, so the script does not block on the recv.
Both text and binary payloads are supported (SendText / SendBlob).
Simple telemetry with Delivered/Dropped counters, and a readable Info string for the full state.
Processing the callback blob
The incoming reply blob has the same format as in callbackudpserver: 4 bytes port (LE u32) + 4 bytes hostLen (LE u32) + host string + payload. A struct: `struct UdpPacket packet { i32 port; i32 hostLen; }` — after `$X assign UdpPacket;`, `$X.port` and `$X.hostLen` are directly accessible, while the host string and payload can be read with offset-based blob slicing. The two plugins together build a complete request-response channel.
Timeout strategy
timeoutMs=-1 (infinite) gives the callback the full run — the background thread only resumes recv when the callback returns. For high-throughput flows, a shorter (e.g. 50-200 ms) timeoutMs is recommended: the callback must fit in the time window, or the packet is DROPPED (the Dropped counter goes up). If Dropped grows, either raise timeoutMs or speed up the callback.
Loopback-only binding
The plugin intentionally binds only to loopback (127.0.0.1) — a security default, optimized for sandbox-style, tested use. For sending to the external network (e.g. a service on another host), you will need another tool; this plugin is for local communication. The `host` argument must resolve to a loopback address — “127.0.0.1”, “localhost”, or sandbox loopback aliases all work.
Error handling
A wrong argument count or type, an out-of-range port (0..65535) or timeoutMs, an invalid or closed clientId, a host resolution failure, a failed socket/thread startup, and a `sendto` failure are reported by the runtime as a runtime error, and the script stops. A successful Send* returns the sent byte count (normally exactly the payload size for UDP).
The callbackworker async job runner plugin
Script callbacks scheduled on a background thread, asyncmeta-compatible job list — complete function reference