Sending And Receiving UDP (User Datagram Protocol) Messages With ColdFusion
A year ago, at an NYCFUG meeting, I heard John Scott mention something called UDP (User Datagram Protocol). He described it as being like HTTP, but faster and less reliable. It sounded interesting, especially for "send it and forget it" type requests. Now, a year later, I'm finally carving out some time to look into it. For my first exploration, I figured it would be fun to have ColdFusion communicate with Node.js using UDP messages.
With UDP (User Datagram Protocol), communication can be bidirectional; but, the messages themselves are unidirectional. By this, I mean that when you send a message, you get no confirmation as to whether or not the message was delivered successfully. The best you can do is send an outgoing message and then listen for subsequent incoming messages.
Since this is my first look into UDP, I won't try to explain the protocol as I'll likely end up making mistakes. In fact, the Node.js side of this demo was, more or less, copied directly out of the Node.js documentation.
In the following exploration, I'm going to send a UDP message from ColdFusion to the Node.js server (both living on the same machine). Then, Node.js is going to send a message back, which the ColdFusion "client" will be waiting [blocking] for.
Client.cfm - Our ColdFusion UDP Client
<cfscript>
// Because our UDP client (this script) and our UDP server (the
// Node.js script) are running on the same machine, we need to set
// them up on different ports so that we don't accidentally create
// a circular chain on the Node.js side.
localPort = javaCast( "int", 9001 );
localInetAddress = createObject( "java", "java.net.InetAddress" )
.getByName( javaCast( "string", "localhost" ) )
;
// The Node.js server.
remotePort = javaCast( "int", 9002 );
remoteInetAddress = createObject( "java", "java.net.InetAddress" )
.getByName( javaCast( "string", "local.bennadel.com" ) )
;
// The DatagramSocket can [always] send and [sometimes] receive
// UPD messages. In this case, we are going to configure the socket
// to listen on the given port and address.
socket = createObject( "java", "java.net.DatagramSocket" ).init(
localPort,
localInetAddress
);
// Wrap all of this in a Try/Catch so that we can CLOSE the socket
// no matter what happens. If we fail to close the socket, we will
// receive an error the next time we try to open it.
// --
// NOTE: You can apparently use the setReuseAddress() if you forgot
// to close the socket. However, I was not able to get this to work.
// I would always get the "Already in use" error, unless I tried to
// bind on the same port, but without an address (I think).
try {
message = "Hello world!";
// Create the packet we want to send. Each packet is
// individually coded to be delivered to a given host and
// port number ("remote" port in this case).
packet = createObject( "java", "java.net.DatagramPacket" ).init(
charsetDecode( message, "utf-8" ),
javaCast( "int", len( message ) ),
remoteInetAddress,
remotePort
);
socket.send( packet );
// -------------------------------------------------- //
// Now that we've sent data, we can also listen for data. The
// data that comes back is not necessarily a RESPONSE to the
// message we just sent. The message we just sent may not even
// reach its destination. This is simply incoming data.
// --
// NOTE: Waiting for data is a BLOCKING operation. The
// request will wait until something comes in over the
// socket connection.
// -------------------------------------------------- //
// Create a packet to contain the incoming message.
response = createObject( "java", "java.net.DatagramPacket" ).init(
charsetDecode( repeatString( " ", 1024 ), "utf-8" ),
javaCast( "int", 1024 )
);
// BLOCK until we receive a message. The "timeout" will raise
// an "SocketTimeoutException" exception if no message is
// received after 5,000 milliseconds.
// --
// NOTE: If you want to "unblock" this request, you either have
// to send a message, close the socket (raises an exception),
// or kill the jrun process.
socket.setSoTimeout( javaCast( "int", 5000 ) );
socket.receive( response );
// Output the "response" message.
writeOutput(
charsetEncode( response.getData(), "utf-8" ) &
"<br /><br />"
);
} catch ( any error ) {
writeDump( error );
// No matter what happens, we want to be sure to close the socket
// binding to the local port number. This way, it can be re-bound
// at a later time.
} finally {
socket.close();
}
// So we can see change (this page runs really fast).
writeOutput( "Now: " & timeFormat( now(), "h:mm:ss.l" ) );
</cfscript>
After we send out the message, the ColdFusion code will listen for a "response." When you listen for a response over a UDP socket, the code will block. As such, I made sure to set an socket operation timeout such that if no messages were received (ie, I forgot to start the Node.js server), the ColdFusion code would eventually timeout and close the socket, releasing it for future use.
On the Node.js side, I'm simply binding to the Datagram Socket to listen for messages. Once received, I send back a new (and completely distinct) message.
Server.js - Our Node.js UDP Server
// Get our Datagram library and create our UDP socket; I
// think you can think of this as being somewhat akin to Java's
// java.net.DatagramSocket library.
var socket = require( "dgram" ).createSocket( "udp4" );
// Listen for message events on the socket.
socket.on(
"message",
function ( message, requestInfo ) {
// Log the received message.
console.log(
"Message: " + message + " from " +
requestInfo.address + ":" + requestInfo.port
);
var response = new Buffer( "Got it on " + new Date() );
// Send a response. Note that this is entirely optional.
// The client (ColdFusion) is not waiting for a response
// [necessarily]. This is an independent action and will
// not hold up the client's message.
socket.send(
response,
0, // Buffer offset
response.length,
requestInfo.port,
requestInfo.address,
function( error, byteLength ) {
console.log( "... Sent response to " + requestInfo.address + ":" + requestInfo.port );
}
);
}
);
// Listen for error events on the socket. When we get an error, we
// want to be sure to CLOSE the socket; otherwise, it's possible that
// we won't be able to get it back without restarting the process.
socket.on(
"error",
function ( error ) {
socket.close();
}
);
// When the socket is configured and ready to receive data, simply
// log a confirmation to the console.
socket.on(
"listening",
function () {
var address = socket.address();
console.log( "socket listening " + address.address + ":" + address.port );
}
);
// Start listening on the given port. Since we are not binding to
// an explicit address [just a port], Node.js will aattempt to listen
// to all addresses on the machine.
socket.bind( 9002 );
When I start up the Node.js server [node server.js] and run the above ColdFusion page, I get the following page output:
Got it on Mon Jan 20 2014 09:08:49 GMT-0500 (EST)
Now: 9:08:49.876
... and, on the Node.js side, I get the following console output:
Message: Hello world! from 127.0.0.1:9001
... Sent response to 127.0.0.1:9001
Pretty cool stuff! Slowly, but surely, I'm starting to learn more about how things operate at lower levels. I can definitely see the appeal of something small and simple like UDP for sending non-critical bits of data. Definitely more to explore here.
Want to use code from this post? Check out the license.
Reader Comments
I heard this great joke about UDP and I would tell it to you but you might not get it (ba dum).
@Michael,
Ha ha, well played :)
Nice post! A really useful application for UDP is in sending statistical data, especially when you're sending so much that it doesn't affect things if you drop a few packets here and there. We use StatsD for passing metrics to Graphite and the simple CF client I created for it uses UDP:
https://github.com/MWers/cfstatsd
@Matt,
Funny you mention that. The first time I heard about UDP was in a presentation that was all about "metrics driven development." Amongst the topics discussed was StatsD and Graphite.
I've only briefly heard about StatsD, but I love the way that it revolves around simple number collection. That and Graphite are definitely on my list of things to look into. Thanks for the wrapper component!
UDP is much more similar to TCP than HTTP. A perfect example of when you want to use UDP is streaming audio or video. If you were transferring a song to keep (as in purchasing a song on iTunes), you'd want to use TCP. But if you want to stream it in real time, not to keep, you'd want to use UDP.
In streaming, you're more concerned with keeping as close to real time as possible. If a glitch happens, you don't want to mess up the timing of subsequent packets with heroic measures to recover the content during the glitch. The streaming will be more coherent and listenable if you just say "screw it" to every glitch and move on to keep routing subsequent packets to the speakers.
The glitch happens, but if you trudge on, the user ignores it.
@WebManWalking,
I don't fully understand TCP or HTTP, but I understand what you're saying. I started looking into this UDP stuff for "stats" collection in which it wouldn't be mission critical to lose data.
Plus, I like the fact that it's non-blocking. In ColdFusion, even if I use CFHTTP with a timeout="1", it still blocks for at least one second. I could probably drop down into Java to adjust the timeout... but, with the UDP, its already non-blocking. Sweet!
@Ben,
In response to "I don't fully understand TCP or HTTP". At last! A chance for me to return the favor of all the good teaching/experimentation you provide!
First, the origins: The C term "socket" is logically just a file that's open for input and output at the same time.
If you "open a socket in the file namespace", it's also PHYSICALLY a file open for input and output at the same time. You can write something and then read it back, without ever having to do a close and reopen. At the other end of the socket is a file.
But if you "open a socket in the Internet namespace", at the other end of the socket is another process. What I write, the other process reads. What the other process writes, I read. In this way, 2 processes can "talk to each other", even if they're not on the same machine. The communication is full duplex. A C socket contains 2 file descriptors, one for reading and one for writing. That way, both processes can write at the same time. Incoming messages get queued until the receiving process is ready to read them.
TCP and UDP are 2 implementations of sockets in the Internet namespace, also known as transport protocols. TCP does an invisible back-and-forth of acknowledgements, negative acknowledgements, retransmissions, etc, to assure that no data will be lost. UDP doesn't.
TCP and UDP share the exact same assigned port numbers. TCP port 80 is reliable HTTP. UDP port 80 is unreliable HTTP. But given how people like their web experience to be (complete pages without missing chunks), everyone uses TCP port 80. And given what people like their domain name lookups to be (as fast as possible), everyone uses UDP port 53, even though TCP 53 also exists.
http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml
So now you know what TCP is, the reliable transport protocol. Layered on top of it are numerous other protocols, such as HTTP. Why?
Well imagine that process A writes a message to process B while process B writes a message to process A. No problem. Full duplex, messages queued.
But what if process A tries to READ a message from process B, but process B hasn't written anything yet. Process A goes into a wait state (blocks). Now suppose, due to some miscommunication, process B also tries to read something from process A!!! It'll never get written by A, because A is also in a wait state!!! Both processes wait forever!!! Oh noes!!!
That's what all the higher level protocols are about. They're sets of rules, the main purpose of which is to guarantee that both processes never simultaneously block due to waiting on the other to send something.
For example, HTTP is a pretty darn simple protocol. Request, response, done. Client A sends request. Server B sends response. End of story.
Web Services are pretty similar: Request, response, request, response, done. The first request/response is for the WSDL. The second request/response is the actual call to the Web Service.
I've coded both client sockets and server sockets in C. I wrote a TELNET client emulator once, in fact. I say emulator, not implementation, because its sole purpose was to fool a server into thinking it was talking to a human, even though there was no human. It wasn't done maliciously. It was to get 2 completely different machines to talk to each other when one of them had only a TELNET server port.
Fun times.
@WebManWalking,
I appreciate the explanation. But, there is one thing you said that I don't follow - the whole port 80 / port 53 thing. When you go to create a socket (at least in the Java docs), it seems you can create pick an arbitrary port to connect to. But, should I be trying to connect to port 53 for some reason?
On the flip-side to that, if I create a Node.js app (like in this example), I don't think that I can easily bind to ports lower than 1024 (if memory serves me correctly).
I guess I'm just confused about the port stuff you mentioned.
Also, if you're in a teaching mood :) not sure if this is related, but on podcasts, I often hear people refer to something called "tmux" and "multiplex". It always seems to be in the context of streams that allow two-way communication, much like the duplexing you mentioned. Maybe totally unrelated, but I'm eager to learn stuff :D
@Ben,
By role, there are 2 kinds of sockets, client sockets and server sockets. Using the telephone analogy, clients initiate the phone call and servers sit by a port and wait for someone to call them on that port.
On the C level, servers call "listen", specifying any-old port they want to listen too. This puts them into a wait state (block) waiting for a message to arrive on that port. The conventional place for HTTP servers to wait is port 80 and DNS, port 53. You can certainly listen to another port if you like (examples, 8080 or 8001 for HTTP), but if you do that, your clients have to be told to use the non-standard port. It introduces human intervention, in other words. If you go with the conventional port, clients can connect automatically by going with the convention too.
Anyway, calling "listen" is what makes a socket a server socket. Then all it does is hang around the phone (port), blocked, just pining away and wishing some handsome young client socket would call them on that port. If that happens, they perk up instantly and become marvelous conversationalists. If no one calls, the server sockets feel sad and lonely. Not really, just idle. Anthropomorphism. But you get the idea.
Clients call "open" specifying an IP and port. They normally get IP address from DNS, but what port to use is by convention again. Browsers, for example, default to 80, but they look for /:\d+$/ in the server name part of the URL. That's how they allow human intervention. I know you love regular expressions. Anyway, most folks don't do that most of the time. That's why convention is so important. And that's why I sent you the iana.org URL. It's how the handsome client socket comes up with a phone number (IP and port) to find the server socket. IP gets you to the dormitory (machine), port to the dorm room (listening server socket).
The low port numbers are reserved as shown in the iana.org page. Larger port numbers are a free-for-all, just pick one. There's also something called "port reuse". In the call to "listen", server sockets can specify "allow port reuse". Extending the phone call analogy, if you open a server socket (with listen) specifying to allow port reuse, only the ringing of the phone is on the conventional port number. The OS's comm software (aka the "TCP/IP stack") detects the ring on the port's phone. In establishing the connection, it picks a big port number from the available big port numbers pool and actually establishes the connection using that big port number. This frees up the original port number, allowing it to ring again if someone else happens to call around the same time. That's important. If you don't allow port reuse, your server is single-thread.
There are tons of options. It doesn't have to go down using listen and open. For example, there's a middleware utility called inetd that manages persistent session protocols, such as FTP and TELNET. The servers that run under inetd just receive messages on stdin and write responses to stdout. The inetd process manages all of the server socket management and servers that run under inetd "don't even know they're talking to the Internet", as the saying goes. Actually, they do, but they don't directly call any socket functions. Even so, at the foundation, everything happens as just described (with inetd doing comm calls).
See why the Event Gateway was such a big deal in ColdFusion? It allows you to listen for connections other than HTTP. You could listen to the SMS port and respond to real phone traffic (text messages), not just phone call analogies.
You too can be a server socket.
P.S.:
On a Unix system, such as your MacBook Pro, you need to be root to listen to a low-numbered reserved port number. That's how experienced users (sysadmins) prevent neophyte users from messing up existing services. That's why you have to turn on the MBP's Apache web server using System Preferences > Network > Sharing > turn on Web Sharing, not just double-clicking an application.
Hmmm... Looks like they're calling the File Namespace the "Local Namespace" nowadays:
http://www.gnu.org/software/libc/manual/pdf/libc.pdf#413
@WebManWalking,
When I was looking at the Java docs (and Googling for information) for the DatagramSocket stuff, the "port reuse" stuff was very confusing. I started looking into it because, in my first experiments, I only had the ColdFusion side of stuff running, not the Node.js side. As such, the ColdFusion code would connect to a port, and then hang while executing the .receive() command.
The problem was, when I refreshed the page, it would yell at me that the port in question was already in use. And, I'd have to restart JRUN.
Then, I started trying to set the port-reuse flag before binding to the socket; but, it didn't seem to help. Upon refresh, I would get the same error.
Eventually (not show in this code sample), I just started adding a timeout to the socket so at least it would die after a few seconds.
Quick question: If I bind to a remote port (ie, the server port), but do NOT bind to a local port (ie, just let the client send over whichever port is available), so I have to "close" the socket connection in anyway? I can't seem to find any info on that.
@Ben
I've never specified a client port number. Never. Not in C. Not in Java. IMHO, that's why you're getting the "Already in use" error. You're unnecessarily restricting your client to 9001. On the second try, the first try still has 9001.
The place to allow port reuse is on the server (Node). That frees up 9002 to be multithread. But I've never used Node, much less the "dgram" library, so I don't know what to advise as to how to do that.
Also, I confess, I've never used UDP. That's what attracted me to this post on your blog, actually. I've always needed reliable connections for the tasks at hand. So the following is conjecture:
Suppose you send Hello World to 9002, but for some reason, Node doesn't receive it. In ColdFusion, you don't know that. You blocked on socket.receive and you're waiting for a response, but Node DOESN'T KNOW YOU EXIST. It never got the Hello World, so it doesn't know to send back Got It. So you're waiting on a Got It that ain't comin'.
In other words, you may have hit the same kind of impasse as I described earlier (blocking on a read for a message that never comes). The only difference is, it's one-sided. The Node side didn't block, because it didn't know about your attempts. That's my guess.
The last time I used Java to open a client socket was around the year 2000. I had to implement a Web Services request by building the SOAP packet myself and sending it over HTTPS. This was before ColdFusion MX (6), so I had to use a Java CFX to get into Java. Also, HTTPS wasn't yet integrated into the java.net package, so I had to import an extension called JSSE (Java Secure Sockets Extension). I'm still using that code, by the way, because it hasn't needed to be modified since 2000. It's clearly due for a cfinvoke rewrite someday, but I don't task myself. It'll have to wait until my bosses give me the go-ahead to do cleanup. They only let me do that if they can't think of what else I should work on. :(
Still, I'm very curious as to what you discover with UDP. I'll keep checking back here.
At some point, when the kinks have been worked out, one or both of us should tell the folks on Twitter about this.
P.S.: I forgot to respond to the "should I close anyway" part of your quick question.
Yes.
Your ColdFusion request can time out, but the Java objects you connect to don't know about cfsetting setrequesttimeout. I'm sure you've experienced overrunning your request timeout because you were in some cf tag that took way longer than the timeout, but CFML processing never got a chance to check whether you've timed out, because you stayed in that one tag.
That can happen in Java objects too. So yes, your Java code should close all sockets you open (assuming you don't permanently hang on a read block). You don't want the Java object hanging around because it's still in use.
@WebManWalking,
So far, what I've used UDP to do is to talk to HostedGraphite.com for metrics tracking. Here's my "UDP Transport" component:
https://github.com/bennadel/GraphiteD.cfc/blob/master/lib/UDPTransport.cfc
Seems pretty cool so far. I love that it's non-blocking.
Re: closing the socket, does that matter "always"? Or, only when I'm "listening" for an incoming message on my end.
Also, I never know how "expensive" this stuff is. If I have to send a message every 10-seconds (as in the flush buffer for collecting metrics), is it expensive to 1) Create a socket, 2) Send message, 3) Close socket for every time I need to send the message?
Or, should I really try to keep it open?
@Ben
Can't answer in depth right away, but servers are typically left on, listening to ports, 24x7. It's very, very typical to never take them down at all. Between messages, the server process that listens to a port is idle (because it blocked on the listen). The active process checking for incoming messages is the TCP/IP stack. So no, it's not expensive to leave a server socket listening to a port 24x7. Not expensive with CPU cycles or I/O or anything.
The only significant expense occurs when a message arrives. The TCP/IP stack activates the server process by putting the message onto the read side of the server socket, unblocking the process. Then the expense depends on how efficiently you coded your server process.
And again, YMMV with Node. Not an expert with Node.
@WebManWalking,
Ok, cool, thanks! As always, very much appreciated.
@Ben,
Okay, now I have a moment to talk about holding connections open.
You probably are familiar with "COMET", the tongue-in-cheek opposite of AJAX. In CF terminology, COMET involves doing a bunch of cfflush commands during a process that you know is going to take a long time. Usually, you end on a boundary the browser understands, such as /div or /table, so that the browser doesn't have to wait on more HTML to determine how to repaint the screen. The user keeps seeing stuff added to their screen, so they are more patient toward a long-running process.
COMET is server push. AJAX is browser pull. You could rewrite a persistent COMET connection into a pull from the browser via repeated AJAX calls. You'd probably use setInterval with $.ajax, or $.load perhaps. The problem is reestablishing context on each call. Do you have to re-login on each ajax call? Do you have to skip over previous data to get to the point where you left off? How much reestablishing of context overhead do you have to do, just to avoid a persistent connection?
On a Unix or Mac server, you're not likely to lock your computer out of doing anything else, just because you're holding open a COMET connection. My experience with Windows multitasking, however, is that's not all that preemptive, meaning that the computer gets one-track-minded and all other processes suffer.
Everything's a trade-off. There isn't a perfect answer. You have to ask yourself whether performance seems acceptable. If not, switch to a different way of doing things.
My $0.02.
pretty cool,
but how do I connect to the server from browser or something client side?
thanks,