Programming sockets using temporary ports
In an earlier posting, I showed where there was code in Apache Harmony that had some unsafe parameter checking logic, and I gave a pattern for how to do it right.
Another "anti-pattern" that I see recurring in the Harmony test cases is around client-server socket programming. The typical scenario is that the tester wants to exercise some socket code, so they spin up a new Thread to act as the server, and run the tests on the main thread. The tester either picks a port that they assume will be free, or use some horrible code that tries to find a free port:
/*
* Returns a different port number every 6 seconds or so. The port number
* should be about += 100 at each 6 second interval
*/
private static int somewhatRandomPort() {
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
int minutes = c.get(Calendar.MINUTE);
int seconds = c.get(Calendar.SECOND);
return 6000 + (1000 * minutes) + ((seconds / 6) * 100);
}
Guessing port numbers like this is simply awful and hopeless. Once the port has been established, the poorly written test case opens a server socket to accept connections in the server Thread, while to get the timing right the client goes into a sleep for a while to give the server time to start up. Apart from the fact that the code is going to fail intermittently, it means the test case always takes a minimum length of time to execute as it spends time sleeping.
There is no need for guessing ports or separate threads. In Java, just like other programming languages that expose the underlying platform's TCP/IP stack behaviour, binding to port zero instructs the stack to allocate an ephemeral port.
Furthermore, server sockets have a listen backlog queue capable of remembering 50 outstanding connect requests by default. Here is a example of simple client-server code using an ephemeral port and a single thread to exchange a simple message over TCP/IP sockets.
// Set-up
ServerSocket server = new ServerSocket(0);
Socket client = new Socket();
client.connect(server.getLocalSocketAddress());
Socket worker = server.accept();
// Do some stuff
client.getOutputStream().write("Hello world!".getBytes("UTF-8"));
byte[] buffer = new byte[1024];
int length = worker.getInputStream().read(buffer);
// Tidy-up
client.close();
worker.close();
server.close();
System.out.println(new String(buffer, 0, length, "UTF-8"));
Hopefully the code is simple enough to understand without further comment. I'll just point out that the server socket is created with an argument of "0" to mean the listening socket should be opened on any network adapter, with a stack allocated port number, and supporting up to fifty pending connections. Then the client connects to the actual interface and port that was used using getLocalSocketAddress().
The same simple example can also be written using the NIO APIs, which requires passing null to the bind() method, like this:
// Set-up
ServerSocketChannel server = ServerSocketChannel.open();
server.socket().bind(null);
SocketChannel client = SocketChannel.open();
client.connect(server.socket().getLocalSocketAddress());
SocketChannel worker = server.accept();
// Do some stuff
client.write(ByteBuffer.wrap("Hello world!".getBytes("UTF-8")));
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
worker.read(readBuffer);
readBuffer.flip();
// Tidy-up
worker.close();
client.close();
server.close();
System.out.println(Charset.forName("UTF-8").decode(readBuffer));
Of course, the simple examples still ignore a few return codes and exceptions that should be considered to make the code safer, but the purpose here is to show the structure for tests and applications that need to use sockets to exchange information.
1 comment:
Good brief and this enter helped me alot in my college assignement. Gratefulness you as your information.
Post a Comment