Recently I wanted to send some Shiny usage data from R to a certain metrics
server. Since R includes write.socket()
and
friends for opening arbitrary network sockets, it seemed at the outset that this
would be quite simple. However, I ran into an interesting roadblock along the
way.
It turns out that R’s socket API only supports TCP connections, which you can confirm by looking at the source code – and in this case I needed to send UDP packets instead. This was a little surprising to me, since most other languages would include UDP support out of the box; it’s a core internet protocol, after all. For whatever reason, this seems not to be the case with R, and even after searching CRAN and GitHub I wasn’t able to find an existing package that provides UDP socket support.
To remedy this, I put together a simple way to write messages to UDP sockets from R.
Since working with sockets is done at the system level, it makes sense to use
R’s C API in this case. The following is a self-contained implementation that
you can drop into an R package’s src
directory as udp.c
:
#include <Rinternals.h>
#include <R_ext/Rdynload.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
SEXP udp_send_impl(SEXP message, SEXP host, SEXP port) {
const char* msg = CHAR(asChar(message));
const char* host_ = CHAR(asChar(host));
int port_ = asInteger(port);
// Open the socket.
int sock;
struct sockaddr_in server;
if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
Rf_error("Failed to create UDP socket.");
return R_NilValue;
}
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons((short) port_);
if (inet_aton(host_, &server.sin_addr) == 0) {
Rf_error("Failed to parse host address.");
close(sock);
return R_NilValue;
}
// Send message.
if (sendto(sock, msg, strlen(msg), 0, (struct sockaddr *) &server,
sizeof(server)) < 0) {
Rf_error("Failed to send message.");
}
close(sock);
return R_NilValue;
}
static const R_CallMethodDef udp_entries[] = {
{"udp_send_impl", (DL_FUNC) &udp_send_impl, 3},
{NULL, NULL, 0}
};
void R_init_udp(DllInfo *info) {
R_registerRoutines(info, NULL, udp_entries, NULL, NULL);
R_useDynamicSymbols(info, FALSE);
}
Note that you may need to adjust the R_registerRoutines()
code if you have
existing C or C++ code in your package.
To use this C function from R, you’ll need something like the following in your package:
#' @export
#' @useDynLib udp udp_send_impl
udp_send <- function(message, host = "localhost", port) {
stopifnot(is.character(message))
stopifnot(is.character(host))
stopifnot(is.numeric(port))
.Call(udp_send_impl, message, host, as.integer(port))
invisible(NULL)
}
To confirm that this works, you can listen for UDP packets with from a terminal with
$ nc -lu 1001
and you can send UDP packets to that server from R with
udp_send("Hello from R!", host = "localhost", port = 1001)