Unconstant Conjunction A personal blog

Communicating with UDP Sockets from R

    6 July 2018 // tagged

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)
comments powered by Disqus