Java Nio Socket Example
This article introduces the SocketChannel
class and its basic usage. This class is defined in the java.nio package.
1. Standard Java sockets
Socket programming involves two systems communicating with one another. In implementations prior to NIO, Java TCP client socket code is handled by the java.net.Socket class. A socket is one end-point of a two-way communication link between two programs running on the network. Socket classes are used to represent the connection between a client program and a server program. The java.net package provides two classes, Socket
and ServerSocket
, that implement the client side of the connection and the server side of the connection, respectively. The below image illustrates the nature of this communication:
A socket is basically a blocking input/output device. It makes the thread that is using it to block on reads and potentially also block on writes if the underlying buffer is full. Therefore, different threads are required if the server has many open sockets. From a simplistic perspective, the process of a blocking socket communication is as follows:
- Create a
ServerSocket
, specifying a port to listen on. - Invoke the ServerSocket’s
accept()
method to listen on the configured port for a client connection. - When a client connects to the server, the
accept()
method returns aSocket
through which the server can communicate with the client: anInputStream
is obtained to read from the client and anOutputStream
to write to the client.
2. Nonblocking SocketChannel with java.nio
With the standard java sockets, if the server needed to be scalable, the socket had to be passed to another thread for processing so that the server could continue listening for additional connections, meaning call the ServerSocket’s accept()
method again to listen for another connection.
A SocketChannel
on the other hand is a non-blocking way to read from sockets, so that you can have one thread communicate with multiple open connections at once. With socket channel we describe the communication channel between client and server. It is identified by the server IP address and the port number. Data passes through the socket channel by buffer items. A selector monitors the recorded socket channels and serializes the requests, which the server has to satisfy. The Keys describe the objects used by the selector to sort the requests. Each key represents a single client sub-request and contains information to identify the client and the type of the request. With non-blocking I/O, someone can program networked applications to handle multiple simultaneous connections without having to manage multiple thread collection, while also taking advantage of the new server scalability that is built in to java.nio. The below image illustrates this procedure:
3. Example
The following example shows the use of SocketChannel
for creating a simple echo server, meaning it echoes back any message it receives.
3.1. The Server code
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.*; public class SocketServerExample { private Selector selector; private Map<SocketChannel,List> dataMapper; private InetSocketAddress listenAddress; public static void main(String[] args) throws Exception { Runnable server = new Runnable() { @Override public void run() { try { new SocketServerExample( "localhost" , 8090 ).startServer(); } catch (IOException e) { e.printStackTrace(); } } }; Runnable client = new Runnable() { @Override public void run() { try { new SocketClientExample().startClient(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(server).start(); new Thread(client, "client-A" ).start(); new Thread(client, "client-B" ).start(); } public SocketServerExample(String address, int port) throws IOException { listenAddress = new InetSocketAddress(address, port); dataMapper = new HashMap<SocketChannel,List>(); } // create server channel private void startServer() throws IOException { this .selector = Selector.open(); ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking( false ); // retrieve server socket and bind to port serverChannel.socket().bind(listenAddress); serverChannel.register( this .selector, SelectionKey.OP_ACCEPT); System.out.println( "Server started..." ); while ( true ) { // wait for events this .selector.select(); //work on selected keys Iterator keys = this .selector.selectedKeys().iterator(); while (keys.hasNext()) { SelectionKey key = (SelectionKey) keys.next(); // this is necessary to prevent the same key from coming up // again the next time around. keys.remove(); if (!key.isValid()) { continue ; } if (key.isAcceptable()) { this .accept(key); } else if (key.isReadable()) { this .read(key); } } } } //accept a connection made to this channel's socket private void accept(SelectionKey key) throws IOException { ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); SocketChannel channel = serverChannel.accept(); channel.configureBlocking( false ); Socket socket = channel.socket(); SocketAddress remoteAddr = socket.getRemoteSocketAddress(); System.out.println( "Connected to: " + remoteAddr); // register channel with selector for further IO dataMapper.put(channel, new ArrayList()); channel.register( this .selector, SelectionKey.OP_READ); } //read from the socket channel private void read(SelectionKey key) throws IOException { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate( 1024 ); int numRead = - 1 ; numRead = channel.read(buffer); if (numRead == - 1 ) { this .dataMapper.remove(channel); Socket socket = channel.socket(); SocketAddress remoteAddr = socket.getRemoteSocketAddress(); System.out.println( "Connection closed by client: " + remoteAddr); channel.close(); key.cancel(); return ; } byte [] data = new byte [numRead]; System.arraycopy(buffer.array(), 0 , data, 0 , numRead); System.out.println( "Got: " + new String(data)); } } |
From the above code:
- In the
main()
method on lines 43-45, one thread for creating theServerSocketChannel
is started and two client threads responsible for starting the clients which will create aSocketChannel
for sending messsages to the server.123new
Thread(server).start();
new
Thread(client,
"client-A"
).start();
new
Thread(client,
"client-B"
).start();
- In the
startServer()
method on line 54, the serverSocketChannel
is created as nonBlocking, the server socket is retrieved and bound to the specified port:1234ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(
false
);
// retrieve server socket and bind to port
serverChannel.socket().bind(listenAddress);
Finally, the
register
method associates the selector to the socket channel.1serverChannel.register(
this
.selector, SelectionKey.OP_ACCEPT);
The second parameter represents the type of the registration. In this case, we use
OP_ACCEPT
, which means the selector merely reports that a client attempts a connection to the server. Other possible options are:OP_CONNECT
, which will be used by the client;OP_READ
; andOP_WRITE
.
After that, theselect
method is used on line 67, which blocks the execution and waits for events recorded on the selector in an infinite loop.1this
.selector.select();
- The selector waits for events and creates the keys. According to the key-types, an opportune operation is performed. There are four possible types for a key:
- Acceptable: the associated client requests a connection.
- Connectable: the server accepted the connection.
- Readable: the server can read.
- Writeable: the server can write.
- If an acceptable key is found, the
accept(SelectionKey key)
on line 93 is invoked in order to create a channel which accepts this connection, creates a standard java socket on line 97 and register the channel with the selector:12345ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel channel = serverChannel.accept();
channel.configureBlocking(
false
);
Socket socket = channel.socket();
SocketAddress remoteAddr = socket.getRemoteSocketAddress();
- After receiving a readable key from the client, the
read(SelectionKey key)
is called on line 107 which reads from the socket channel. A byte buffer is allocated for reading from the channel1numRead = channel.read(buffer);
and the client’s transmitted data are echoed on System.out:
1System.out.println(
"Got: "
+
new
String(data));
3.2. The Client code
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class SocketClientExample { public void startClient() throws IOException, InterruptedException { InetSocketAddress hostAddress = new InetSocketAddress( "localhost" , 8090 ); SocketChannel client = SocketChannel.open(hostAddress); System.out.println( "Client... started" ); String threadName = Thread.currentThread().getName(); // Send messages to server String [] messages = new String [] {threadName + ": test1" ,threadName + ": test2" ,threadName + ": test3" }; for ( int i = 0 ; i < messages.length; i++) { byte [] message = new String(messages [i]).getBytes(); ByteBuffer buffer = ByteBuffer.wrap(message); client.write(buffer); System.out.println(messages [i]); buffer.clear(); Thread.sleep( 5000 ); } client.close(); } } |
- In the above client code, each client thread creates a socket channel on the server’s host address on line 12:1
SocketChannel client = SocketChannel.open(hostAddress);
- On line 19, a String array is created to be transmitted to the server using the previously created socket. The data contain also each thread’s name for distinguishing the sender:1234
String threadName = Thread.currentThread().getName();
// Send messages to server
String [] messages =
new
String []
{threadName +
": test1"
,threadName +
": test2"
,threadName +
": test3"
};
- For each string message a buffer is created on line 24:1
ByteBuffer buffer = ByteBuffer.wrap(message);
and each message is written to the channel from the given buffer on line 25:
1ByteBuffer buffer = ByteBuffer.wrap(message);
3.3. The output
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | Server started... Client... started Client... started client-A: test1 client-B: test1 Connected to: /127 .0.0.1:51468 Got: client-B: test1 Connected to: /127 .0.0.1:51467 Got: client-A: test1 client-A: test2 client-B: test2 Got: client-B: test2 Got: client-A: test2 client-A: test3 client-B: test3 Got: client-B: test3 Got: client-A: test3 Connection closed by client: /127 .0.0.1:51468 Connection closed by client: /127 .0.0.1:51467 |
4. Download Java Source Code
This was an example of java.nio.SocketChannel
You can download the full source code of this example here: SocketExampleNio.zip
I got an exception when running on Debian 8 BeagleBone Device (jdk-7u75-linux-arm-vfp-hflt.tar). It seems that the problem is on the server side because I can use the same Client code to connect to other services already running on other ports. In a regular windows installation it works. The error is “java.net.ConnectException: Connection refused) on line “SocketChannel client = SocketChannel.open(hostAddress);”.
Were you able to resolve your problem with this code? I’m receiving a similar error when I run the code on an Arm processor?