In this article we will understand Netty in simplistic way with real world example of Chat server client example.
What Netty is or is not
- Netty is not a traditional web server by itself like tomcat or jboss etc. Netty is a NIO based client server framework. This means that it provides a simplified layer over plain NIO networking which makes it easy to create low level networking application.
- Netty is not a servlet-jsp container server or basic web server. But Netty does support HTTP protocol & its possible to create a web server using Netty as base.
- Netty can be used to create both sides of networking based application i.e. server as well as client.
- Netty is non-blocking ex: when client sends a message to server, that call immediately returns & client code can move further. Client can provide Callback-style handler which will handle response from server whenever received from server.
- Netty runs embedded within code ex. it needs a class with main() method to bootstrap.
- Netty is event based. Once server client connection is established, communication happens in form of events. Channel handlers can be coded to handle these events & process them.
Netty components
In short & on high level, Netty server or client needs these basic classes.
- Main class – This class bootstraps server or client. In case of server, it will start server at given host/port. In case of client, it connects to provided server & creates connection.
- Channel Handlers – Channel handlers handles different events like channel active, channel read or exception caught etc.
Chat Server-Client application using Netty
Lets create a Chat application explained here.
- Create console input based chat client application.
- On start of client, we will ask his name & afterwards get a chat message to send to other clients.
- Send name & chat message to chat server.
- Chat Server will then publish that message to all clients along with the name of the sender.
Here is the diagram which shows components of server & client. Below diagram also traces the communication path of a chat message “Hello” from one client to another.
Lets code
Dependency – To use Netty server client framework, Netty dependency is required which is available in maven repository at netty-all
Bootstraping Netty
- ServerBootstrap – Code to bootstrap server channel & start server. It binds the server to the port on which it will listen for connection.
- Bootstrap – Code to bootstrap client channel.
- EventLoopGroup –
- Group of EventLoop. EventLoop handles all the I/O operations for a Channel once registered.
- ServerBootstrap needs 2 types of EventLoopGroup i.e. “boss” & “worker”. Client bootstrap doesn’t need boss group.
- Boss EventLoopGroup – This event loop group looks for & accepts incoming connections.
- Worker EventLoopGroup – Boss accepts connection & registers it to worker EventLoopGroup. Worker handles all the events during the communication through that connection.
- Channel NioServerSocketChannel – Configure server to use NIO selector based implementation to accept new connections.
- Decoder/encoder (StringDecoder / StringEncoder) –
- Netty communication occurs over network socket channel which is in byte format. So in case we want to send specific data types, then we provide encoder to encode data type into bytes & decode bytes to data type.
- String encoder, decoders are provided by Netty. But we can create our own encoders or decoders for any data type that we need.
Chat Server & handler
Here is the Server bootstrap code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
package com.itsallbinary.netty.chat; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; public final class ChatServer { // Port where chat server will listen for connections. static final int PORT = 8007; public static void main(String[] args) throws Exception { /* * Configure the server. */ // Create boss & worker groups. Boss accepts connections from client. Worker // handles further communication through connections. EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) // Set boss & worker groups .channel(NioServerSocketChannel.class)// Use NIO to accept new connections. .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); /* * Socket/channel communication happens in byte streams. String decoder & * encoder helps conversion between bytes & String. */ p.addLast(new StringDecoder()); p.addLast(new StringEncoder()); // This is our custom server handler which will have logic for chat. p.addLast(new ChatServerHandler()); } }); // Start the server. ChannelFuture f = b.bind(PORT).sync(); System.out.println("Chat Server started. Ready to accept chat clients."); // Wait until the server socket is closed. f.channel().closeFuture().sync(); } finally { // Shut down all event loops to terminate all threads. bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } |
Here is the handler for chat server.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
package com.itsallbinary.netty.chat; import java.util.ArrayList; import java.util.List; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; /** * Handles a server-side channel. */ public class ChatServerHandler extends SimpleChannelInboundHandler<String> { // List of connected client channels. static final List<Channel> channels = new ArrayList<Channel>(); /* * Whenever client connects to server through channel, add his channel to the * list of channels. */ @Override public void channelActive(final ChannelHandlerContext ctx) { System.out.println("Client joined - " + ctx); channels.add(ctx.channel()); } /* * When a message is received from client, send that message to all channels. * FOr the sake of simplicity, currently we will send received chat message to * all clients instead of one specific client. This code has scope to improve to * send message to specific client as per senders choice. */ @Override public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("Server received - " + msg); for (Channel c : channels) { c.writeAndFlush("-> " + msg + '\n'); } } /* * In case of exception, close channel. One may chose to custom handle exception * & have alternative logical flows. */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { System.out.println("Closing connection for client - " + ctx); ctx.close(); } } |
Chat client & handler
Here is the client bootstrap code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
package com.itsallbinary.netty.chat; import java.util.Scanner; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; public class ChatClient { static final String HOST = "127.0.0.1"; static final int PORT = 8007; static String clientName; public static void main(String[] args) throws Exception { /* * Get name of the user for this chat session. */ Scanner scanner = new Scanner(System.in); System.out.println("Please enter your name: "); if (scanner.hasNext()) { clientName = scanner.nextLine(); System.out.println("Welcome " + clientName); } /* * Configure the client. */ // Since this is client, it doesn't need boss group. Create single group. EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) // Set EventLoopGroup to handle all eventsf for client. .channel(NioSocketChannel.class)// Use NIO to accept new connections. .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); /* * Socket/channel communication happens in byte streams. String decoder & * encoder helps conversion between bytes & String. */ p.addLast(new StringDecoder()); p.addLast(new StringEncoder()); // This is our custom client handler which will have logic for chat. p.addLast(new ChatClientHandler()); } }); // Start the client. ChannelFuture f = b.connect(HOST, PORT).sync(); /* * Iterate & take chat message inputs from user & then send to server. */ while (scanner.hasNext()) { String input = scanner.nextLine(); Channel channel = f.sync().channel(); channel.writeAndFlush("[" + clientName + "]: " + input); channel.flush(); } // Wait until the connection is closed. f.channel().closeFuture().sync(); } finally { // Shut down the event loop to terminate all threads. group.shutdownGracefully(); } } } |
Here is custom channel handler to print chat messages for client.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package com.itsallbinary.netty.chat; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; public class ChatClientHandler extends SimpleChannelInboundHandler<String> { /* * Print chat message received from server. */ @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("Message: " + msg); } } |
Execute Netty chat application
Here are the steps to run this example.
- First run ChatServer.java as Java Application. Wait till it starts & is ready to accept client. Look for message in logs.
- Once server is up, run ChatClient.java as Java application. Provide name of first chat user once asked in console.
- Again start another instance of ChatClient.java as Java application & provide name of second chat user once asked in console.
- Then exchange chat messages through console input & see message delivered to other clients including that client.
ChatServer console
1 2 3 4 5 6 7 |
Chat Server started. Ready to accept chat clients. Client joined - ChannelHandlerContext(ChatServerHandler#0, [id: 0x3b2712b1, L:/127.0.0.1:8007 - R:/127.0.0.1:64468]) Client joined - ChannelHandlerContext(ChatServerHandler#0, [id: 0x7261ec2a, L:/127.0.0.1:8007 - R:/127.0.0.1:64494]) Server received - [John]: Hi Ravi. This is John. Server received - [Ravi]: Hi John. This is Ravi. Closing connection for client - ChannelHandlerContext(ChatServerHandler#0, [id: 0x3b2712b1, L:/127.0.0.1:8007 - R:/127.0.0.1:64468]) Closing connection for client - ChannelHandlerContext(ChatServerHandler#0, [id: 0x7261ec2a, L:/127.0.0.1:8007 - R:/127.0.0.1:64494]) |
First ChatClient console
User entered inputs are highlighted.
1 2 3 4 5 6 7 |
Please enter your name: Ravi Welcome Ravi Message: -> [John]: Hi Ravi. This is John. Hi John. This is Ravi. Message: -> [Ravi]: Hi John. This is Ravi. |
Second ChatClient console
User entered inputs are highlighted.
1 2 3 4 5 6 7 |
Please enter your name: John Welcome John Hi Ravi. This is John. Message: -> [John]: Hi Ravi. This is John. Message: -> [Ravi]: Hi John. This is Ravi. |
As you can see in above console logs, 2 chat users connected to chat server & exchanged messages with each other.
You can find complete code in GIT Repository.
Further improvements
This example is very simple & focuses mainly on Netty concepts instead of usability of chat. Further improvements can be done in this example to improve & try hands on. Here are some improvements that you can try.
- Currently we send chat message to all clients. Add code to select user with which user wants to chat. Send chat message only to that user.
- Currently we are only exchanging String messages using String encoder & decoder. Write your own encoder & decoder & try sending some other objects or data like file, image etc.