Out-of-band data Java socket implementation

The Problem

(as documented on the official Sun Socket Options in Java page)

Fell by the wayside...

Some possible BSD options that are not supported in java:
  • MSG_OOB:
    This is really an option one passes to a read() or recv() on a socket to read data marked out-of-band or "urgent" if it present, before in-band data. If we include this we should also include an option SO_OOBINLINE (below), but this doesn't seem to be needed. The real complication against this is that we'd also have to provide a symmetrical way to write OOB data, and again this hasn't been requested.
  • SO_OOBINLINE:
    This option will inline OOB data, making it appear inline like "normal" data. It would work in conjunction with MSG_OOB.
I needed the OOB option for a socket connection the other end of which had been implemented in UNIX C. I wrote to Sun, and got a reply from none other than James Gosling that OOB was spottily supported even on UNIXen, and so there were no immediate plans to implement it in Java.

I posted the question in a comp.lang.java newsgroup and was directed to the java.networking mailing list. The list was more helpful, and several people there were interested in whatever implementation I would end up with.

My Solution

I thought I would have to write my own SocketImpl, SocketImplFactory, and so on, all to get the one ability to read an out-of-band byte, since Sockets in JDK 1.0.2 weren't sub-classable. It turned out simpler than that.

The javah application can provide perfectly good C .h header files for java.net.Socket, java.net.SocketImpl, and java.io.FileDescriptor, and thanks to C not believing in data hiding, I could get my hands on the UNIX sockfd hidden this way deep in every Socket. It's a hack ... but it's small :-). There didn't seem to be a more elegant way.

Thanks to Mark Yarvis for telling me that a Java FileDescriptor.fd is 1 greater than the UNIX file descriptor. That would have been a nasty bug to track down. Finally thanks to Elliot Eichen of GTE Labs for letting me release this implementation to the Internet.

Anyway, here is the relevant code. It's obviously not the complete application, so if I cut out any cogent details, feel free to write back. The only confusing part I can immediately see might be the Response(InputStream in) call - that just parses a formatted Response (whatever that might be) from the input stream normally, as if it were a file or standard input, say. No tricky stuff.

The following, respectively, are a Java Class, a C file, a Makefile, and a funct.exp file listing exported functions in the libOOB.so native code library. The last two were actually some of the most complicated parts!



/** OobClient.java. * Fragment of socket client, * demonstrating getting an Out-Of-Band byte acknowledgement in Java * @version 1.0 * @author George Ruban 2/10/97 */ public class OobClient { Socket socket; // the connection to the server DataOutputStream out; DataInputStream in; byte ack; // server's acknowledgement /** Socket-like constructor. * @param host Internet address of Server. * @param port Socket port for same. * @see java.net.Socket */ public OobClient(String host, int port) throws UnknownHostException, IOException { // open up the socket connection to server. socket = new Socket(host, port); out = new DataOutputStream(socket.getOutputStream()); in = new DataInputStream(socket.getInputStream()); } /** Sends a flattened Inquiry via TCP, returns a Response. */ public Response getResponse(Inquiry request) throws IOException { // copy the inquiry fields to the buffer byte sendBuf[] = request.getBytes(); // write the buffer to server out.write(sendBuf, 0, sendBuf.length); // read and react to the OOB acknowledgement byte ack = getOobByte(); if(ack == 'o') throw new IOException("Received Out of Sync Data on Server!"); else if(ack == 'b') throw new IOException("Could Not Put Message on DAS Queue!"); else if(ack != 'g') throw new IOException("Invalid OOB Acknowledgement '" + ack + "' Received!"); // return the (parsed) response from the server return new Response(in); } /** HACK to get an OOB byte from the stream. * Shamelessly dig through the Java-provided wrapper code, * pull out its internal sockfd, and call UNIX functions */ native byte getOobByte(); // load the libOOB.so library with this class, for getOobByte(); static { System.loadLibrary("OOB"); } }
/* oob.c George Ruban, Feb 10, 1997, GTE Labs, Waltham, MA C native method HACK to get Java Sockets to do Out-of-band byte reading. Great thanks to Mark Yarvis, <yarvis@Ficus.CS.UCLA.EDU>, for invaluable advice. */ #include <StubPreamble.h> /* Java stub interface.*/ #include "OobClient.h" /* Specific Java code header file */ #include "java_net_Socket.h" /* javah java.net.Socket */ #include "java_net_SocketImpl.h" /* javah java.net.SocketImpl */ #include "java_io_FileDescriptor.h" /* javah java.io.FileDescriptor */ #include <sys/types.h> #include <sys/time.h> /* for select() call */ #include <sys/socket.h> /* for recv() call */ #include <errno.h> /* for errno error diagnostics */ #ifndef NDEBUG #include <stdio.h> /* for printf debugging */ #endif /** HACK - Return an out-of-band byte from the socket. Dissects a Socket structure to get the underlying UNIX socket file descriptor, does a recv(sockfd, byte, 1, MSG_OOB). */ char OobClient_getOobByte(struct HOobClient *this) { struct Hjava_io_FileDescriptor *fd; /* Java-struct-excavation aids */ struct Hjava_net_SocketImpl *impl; struct Hjava_net_Socket *socket; int sockfd; char theByte = 0; /* what is returned */ struct timeval tv; /* how long to wait for the OOB byte to get there */ fd_set exceptfds; /* dig deep into "this" to get the UNIX file descriptor */ socket = unhand(this)->socket; impl = unhand(socket)->impl; fd = unhand(impl)->fd; sockfd = (int)(unhand(fd)->fd - 1); /* "The FileDescriptor object integer class variable fd represents the actual file descriptor plus 1." - Mark Yarvis */ /* now the actual work, raw UNIX-style. */ /* since the oob message does not seem to be cleared * off the socket before we attempt to read the next * oob, we will use select to sleep for 0.25 of a sec. */ tv.tv_sec = 0; tv.tv_usec = 250000; select(sockfd+1, 0, 0, 0, &tv); /* next we will wait up to 5 seconds to receive notice * (using select) of a pending oob message on the socket */ tv.tv_sec = 5; tv.tv_usec = 0; FD_ZERO(&exceptfds); FD_SET(sockfd, &exceptfds); select(sockfd+1, 0, 0, (struct fd_set*)&exceptfds, &tv); /* received oob message */ if (FD_ISSET(sockfd, &exceptfds)) { if(recv(sockfd, &theByte, 1, MSG_OOB) != 1) { char *description; /* description of ioctl error to throw as an exception */ switch(errno) { case EBADF: description = "EBADF - Bad socket file descriptor."; break; case ENOTSOCK: description = "ENOTSOCK - File descriptor not a socket."; break; case EINTR: description = "EINTR - Interrupted system call."; break; case EWOULDBLOCK: description = "EWOULDBLOCK - No connections present to be accepted."; break; case EFAULT: description = "EFAULT - Bad address."; break; default: description = "Unknown recv() error."; break; } SignalError(0, "java/io/IOException", description); } } else /* timed out, this is a fatal return */ { SignalError(0, "java/io/IOException", "Timed out waiting for OOB byte."); } #ifndef NDEBUG printf("Received byte %c (%d), errno %d\n", theByte, theByte, errno); if(errno != 0) perror(""); #endif return theByte; }
libOOB.so: oob.o OobClient.o rm libOOB.so ld -o libOOB.so oob.o OobClient.o -L${JAVA_HOME}/lib/aix -bE:funct.exp -e OobClient_getOobByte -lc -ljava -bM:SRE oob.o: oob.c OobClient.h cc -c -I ${JAVA_HOME}/include -I ${JAVA_HOME}/include/aix oob.c OobClient.o: OobClient.c OobClient.h cc -c -I ${JAVA_HOME}/include -I ${JAVA_HOME}/include/aix OobClient.c OobClient.c: OobClient.class javah -stubs OobClient OobClient.h: OobClient.class javah OobClient OobClient.class: OobClient.java javac OobClient.java # Other Java files, including # Response.java and Inquiry.java go here
OobClient_getOobByte Java_OobClient_getOobByte_stub

I would appreciate hearing commentary on whether this code fragment was helpful, and if so, how it was used.

George Ruban, gruban@oocities.com, 4/22/97.


This page hosted by Get your own Free Home Page