Often it is necessary to communicate runtime details from one process out to
another process or monitoring utility. The information can be used for logging,
debugging, tracing or simply to relay the status or progress of a running program.
In this post, we’ll demonstrate how to perform interprocess communication between
1) two separate R processes, one acting as client and the other as server, and 2)
an running instance of R and the netcat utility.
To follow along with the example, you’ll need a Windows client with R installed, in addition to
netcat, which can be obtained via Cygwin by searching for and selecting nc
from the Select Packages
window that renders after running the Cygwin setup executable.
Alternatively, the netcat utlity itself can be downloaded here.
To check if netcat is installed, from the terminal run nc -h
. If a list of command line flags is displayed,
you are good to go.
Communicating between Two Instances of R
The functions that we’ll be working with to facilitate socket communication in R include:
- make.socket
- write.socket
- read.socket
- close.socket
In order to setup a communication channel between separate invocations of R, it is first
necessary to initialize the socket server. A very simple socket server that echoes the
client’s messages can be implemented as follows. We’ll call it server.R
:
# server.R => Example of a listening socket server in R
serverSocket = make.socket(
host="localhost",
port=6000,
server=TRUE
)
while(TRUE) {
statusMsg = read.socket(serverSocket)
print(statusMsg)
if (nchar(statusMsg)==0) break
}
close.socket(serverSocket)
We first call the make.socket
function setting server=TRUE
, and port=6000
. The port number can be any integer between
0-65535 (since port numbers are unsigned 16-bit integers), but one should always select a port greater than 1024, since ports
0-1024 are reserved for privileged services. Next a looping construct is setup and the socket is read from until a message is received with character length 0. Once the zero-length character message is received, iteration ceases and serverSocket
is closed by calling close.socket
.
Next we implement the client socket and the supporting logic to facilitate message passing between the two R processes.
For this example, a random poisson variate is generated, the client sleeps for 1 second, then writes the current date and time using Sys.time
along with the random poisson to the server listening on port 6000
.
Note that the code comprising the client socket (referred to as client.R
) will be referenced in the R-to-netcat example, and will be used as shown below with only a minor modification:
# client.R => Example of client writing to socket in R
clientSocket = make.socket(
host="localhost",
port=6000,
server=FALSE
)
for (i in 1:10) {
rp = rpois(1,10)
Sys.sleep(1)
msg = paste0("[",Sys.time(), "] - Random Poisson: ", rp)
write.socket(clientSocket, msg)
}
close.socket(clientSocket)
The call to make.socket
is very similiar to that in server.R
, except in client.R
, server=FALSE
(note that server=FALSE
is the default for make.socket
, but listing it explicitly helps to highlight the purpose of each script).
With both scripts complete, all that remains is to kick them off. As mentioned earlier, it’s imperative to
first run server.R
. The socket server will enter listening mode, waiting on messages from client.R
.
If you attempt to run client.R
without server.R
already running, an exception similiar to the following
will be propagated:
Error in make.socket(host = "localhost", port = 6000, server = FALSE) :
socket not established
From RStudio, a separate R process can be initialized by selecting Session > New Session. First kickoff server.R
. Once running, start client.R
. The image below shows client.R
on the left and server.R
on the right:
The output written to
server.R
console confirms that the socket interface has been leveraged to communicate
runtime information between separate invocations of R. Next we demonstrate how to communicate between R and a
separate application.
Communicating between R and netcat
In the example that follows, netcat will take the place of the socket server, and the R process the client.
Initializing a socket server in netcat couldn’t be easier. Simply run:
$ nc -l -p <port>
If we again choose port 6000, the command would be:
$ nc -l 6000
As was the case with R to R communication, it is necessary to have your socket server up and running prior to executing any client side code.
The client script for this example is identical to client.R
with one addition: We include the Windows newline character "\r\n"
, at the end of our message so as to have linebreaks between messages output by netcat. Here is the full specification for what we’ll refer to as client2.R
:
# client2.R => Example of client writing to socket in R
clientSocket = make.socket(
host="localhost",
port=6000,
server=FALSE
)
for (i in 1:10) {
rp = rpois(1,10)
Sys.sleep(1)
msg = paste0("[",Sys.time(), "] - Random Poisson: ", rp, "\r\n")
write.socket(clientSocket, msg)
}
close.socket(clientSocket)
Assuming netcat is already listening for incoming messages on port 6000, executing client2.R
will produce output similiar to the following in the terminal running netcat:
Conclusion
The examples in this post demonstrate how to utilize R’s builtin socket functionality to perform interprocess communication in R. From this foundation, more complex interprocess communication systems can be designed and implemented. Until next time, happy coding!