Date post: | 12-Feb-2017 |
Category: |
Technology |
Upload: | trieu-nguyen |
View: | 248 times |
Download: | 3 times |
2
Solving common network programming problems
In chapter 1, you have learned core concepts and classes in Netty to build a networkingbased application. In this chapter, we will cover common problems and how to solve when building networkbased application. You will learn how to:
● Getting the local SocketAddress and the remote SocketAddress
● Sending and receiving data in Streambased TCP/IP Transport
● Sending data in POJO way
● Listening multiple sockets in one Netty instance
● Building your own protocol servers and clients with Custom Codec
● Counting Server Bandwidth in ChannelHandler
● Checking Heartbeat Server using UDP protocol
● Using Stream Control Transmission Protocol (SCTP) codec
● Building simple FTP server
● Building server RPC (Remote Procedure Call) with Apache Avro and Netty
● Building simple HTTP file downloader
Introduction In chapter 2, from recipe 2.1 to 2.6 will guide you how to use Netty in common use cases, with most important concept is Codec. From recipe 2.7 to 2.12, we will cover some examples with popular network protocol, such as TCP/IP, UDP , SCTP, FTP , RPC with Apache Avro, downloading file from HTTP and building a simple PubSub Netty server.
Getting ready Make sure you check out this code at the Git repository of book
https://github.com/trieu/nettycookbook
In this chapter, we would use the class netty.cookbook.common.BootstrapTemplate to create common Netty’s bootstrap for both server code and client code.
Recipe 2.1 Getting the local SocketAddress and the remote SocketAddress
Problem: Your server needs to log the remote or local SocketAddress, this recipe could be useful for debugging.
How to do it … In any implemented class of ChannelInboundHandler or ChannelOutboundHandler, your code must override the method “channelActive” and you can use the instance of ChannelHandlerContext to get all information about the active connected channel. This example code would illustrate how to do:
public void channelActive(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress localAddr = (InetSocketAddress) ctx.channel().localAddress();
logger.info(String.format("localAddress.hostname %s",localAddr.getHostName()));
logger.info(String.format("localAddress.port %s",localAddr.getPort()));
InetSocketAddress remoteAddr = (InetSocketAddress) ctx.channel().remoteAddress();
logger.info(String.format("remoteAddress.hostname %s",remoteAddr.getHostName()));
logger.info(String.format("remoteAddress.port %s",remoteAddr.getPort()));
}
How it works We can see the output from above code.
20141127 06:11:06 INFO LoggingHandler:100 [id: 0x8db2146f] REGISTERED
20141127 06:11:06 INFO LoggingHandler:100 [id: 0x8db2146f] BIND(0.0.0.0/0.0.0.0:8007)
20141127 06:11:06 INFO LoggingHandler:100 [id: 0x8db2146f, /0:0:0:0:0:0:0:0:8007] ACTIVE
20141127 06:11:12 INFO LoggingHandler:100 [id: 0x8db2146f, /0:0:0:0:0:0:0:0:8007] RECEIVED: [id: 0x90524633, /127.0.0.1:46697 => /127.0.0.1:8007]
20141127 06:11:12 INFO PurchaseServer:47 localAddress.hostname localhost
20141127 06:11:12 INFO PurchaseServer:48 localAddress.port 8007
20141127 06:11:12 INFO PurchaseServer:51 remoteAddress.hostname localhost
20141127 06:11:12 INFO PurchaseServer:52 remoteAddress.port 46697
Recipe 2.2 Sending and receiving data in Streambased TCP/IP Transport
Problem: When sending data over the TCP/IP, data usually must be divided into smaller packets. That’s the mechanism of a streambased transport such as TCP/IP. For example, if you want to send a long messages, the protocol transport itself would divide the message as independent set of bytes. Also, there is no guarantee that what you could receive is exactly what your remote peer sent.
Solution: Netty provides an extensible class which helps you solve this problem more elegant. One simple way you could do , by extending the class ByteToMessageDecoder or ReplayingDecoder to encode and decode received data into one or more meaningful frames instead of bytes. It’s also safer for sending and receiving a large data message in streambased transport such as TCP/IP.
How to do it … We could split MessageClientHandler into two handlers:
● MessageDecoder which deals with the fragmentation issue, and ● the initial simple version of MessageClientHandler.
Netty provides extensible classes which simplify code you would write, one of common way to send/receive string message in Netty is using StringEncoder and StringDecoder. Let’s see how simple code:
The sender class:
public class Sender {
static final int PORT = 8007;
static final String HOST = "127.0.0.1";
public static void main(String[] args) throws InterruptedException {
final String msg = "This is a long message";
ChannelInitializer<SocketChannel> initializer = new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new StringEncoder());
p.addLast(new StringDecoder());
p.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx)
throws Exception {
//on ready to send
ctx.writeAndFlush(msg);
}
@Override
public void channelRead(ChannelHandlerContext ctx,
Object data) throws Exception {
//on receive
System.out.println("got " + data);
}
});
}
};
BootstrapTemplate.newClientBootstrap(HOST, PORT, initializer );
Thread.sleep(5000);
}
}
The receiver class
public class Receiver {
static final int PORT = 8007;
static final String HOST = "127.0.0.1";
public static void main(String[] args) throws Exception {
ChannelInitializer<SocketChannel> initializer = new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new StringEncoder());
p.addLast(new StringDecoder());
p.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg);
ctx.close();
}
});
}
};
BootstrapTemplate.newServerBootstrap(HOST, PORT, initializer);
}
}
Recipe 2.3 Sending data in POJO way
Problem The POJO or Plain Old Java Object, is common way to encoding business logic into regular java objects rather than using Entity Beans. Netty supports encoding/decoding object into ByteBuf and vice versa.
Example we have this business object for modelling the data of a purchase transaction between user and ecommerce backend server.
public class PurchaseData implements Serializable{
private final int itemId;
private final float price;
private final String buyer;
private final String seller;
private final int unixTime;
private final boolean processed;
How to do it … Let’s check this diagram
Netty supports 2 classes, ObjectDecoder and ObjectEncoder, that simplifies how we decoding/encoding POJO objects
In the method initChannel, add this code for POJO codec:
ChannelPipeline p = ch.pipeline();
ClassResolver cl = ClassResolvers.weakCachingConcurrentResolver(null);
p.addLast("decoder", new ObjectDecoder(cl));
p.addLast("encoder", new ObjectEncoder());
Here is the full code of PurchaseData class
public class PurchaseData implements Serializable{
private static final long serialVersionUID = 5467453661148034694L;
private final int itemId;
private final float price;
private final String buyer;
private final String seller;
private final int unixTime;
private final boolean processed;
public PurchaseData(int itemId, float price, String buyer, String seller, int unixTime, boolean processed) {
super();
this.itemId = itemId;
this.price = price;
this.buyer = buyer;
this.seller = seller;
this.unixTime = unixTime;
this.processed = processed;
}
public PurchaseData(Object obj, boolean processed) {
super();
PurchaseData data = (PurchaseData)obj;
this.itemId = data.itemId;
this.price = data.price;
this.buyer = data.buyer;
this.seller = data.seller;
this.unixTime = data.unixTime;
this.processed = processed;
}
The code of PurchaseServer
ChannelInitializer<SocketChannel> initializer = new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new PurchaseDataDecoder());
p.addLast(new PurchaseDataEncoder());
p.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx,
Object data) throws Exception {
System.out.println("processed Purchase " + data);
PurchaseData processed = new PurchaseData(data, true);
ctx.writeAndFlush(processed);
}
});
}
};
BootstrapTemplate.newServerBootstrap(HOST, PORT, initializer);
The code of PurchaseClient
public class PurchaseClient {
String host; int port;
public PurchaseClient(String host, int port) {
super();
this.host = host;
this.port = port;
}
public PurchaseClient send(PurchaseData message, CallbackProcessor asynchCall) throws Exception{
ChannelHandler clientHandler = new PurchaseClientHandler(message, asynchCall);
ChannelInitializer<SocketChannel> initializer = new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new PurchaseDataDecoder());
p.addLast(new PurchaseDataEncoder());
p.addLast(clientHandler);
}
};
BootstrapTemplate.newTcpClientBootstrap(host, port, initializer );
return this;
}
public static void main(String[] args) throws Exception {
int time = (int) (System.currentTimeMillis() / 1000L);
PurchaseData data = new PurchaseData(1001, 499.99f, "Trieu", "Amazon", time, false );
new PurchaseClient("127.0.0.1",8007).send(data, rs > {
System.out.println(rs);
});
}
}
Recipe 2.4 Listening multiple sockets in one Netty instance
Problem: In networking development, sometimes you want your server listen multiple sockets in one instance. Example , you could implement a HTTP server , which would listen in 2 sockets. The first one for public, that serves all traffic requests from Internet. The second one for private usage, which only internal networking management or networking monitoring.
How to do it We use 2 independent objects of ServerBootstrap to solve this problem. Each of them bind to different socket, so the request would be processed in 2 independent instances of ChannelHandler. Loot at this code:
try {
//public service processor
ServerBootstrap publicServerBootstrap = new ServerBootstrap();
publicServerBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class);
//bind to public access host info
Channel ch1;
if("*".equals(ip)){
ch1 = publicServerBootstrap.bind(port).sync().channel();
} else {
ch1 = publicServerBootstrap.bind(ip, port).sync().channel();
}
ch1.config().setConnectTimeoutMillis(1800);
//admin service processor
ServerBootstrap adminServerBootstrap = new ServerBootstrap();
adminServerBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
.childOption(ChannelOption.TCP_NODELAY, false)
.childOption(ChannelOption.SO_KEEPALIVE, false)
.childHandler(new PrivateHttpServerInitializer(DEFAULT_CLASSPATH,this.privatePoolSize ));
//bind to private access (for administrator only) host info, default 10000
Channel ch2;
if("*".equals(ip)){
ch2 = adminServerBootstrap.bind(privatePort).sync().channel();
} else {
ch2 = adminServerBootstrap.bind(ip,privatePort).sync().channel();
}
ch2.config().setConnectTimeoutMillis(1800);
LogUtil.i("publicServerBootstrap ", "is started and listening at " + this.ip + ":" + this.port);
LogUtil.i("adminServerBootstrap ", "is started and listening at " + this.ip + ":" + privatePort);
ch1.closeFuture().sync();
ch2.closeFuture().sync();
System.out.println("Shutdown...");
Recipe 2.5 Counting Server Bandwidth Meter
Problem One common problem in network monitoring is how we can measure the use of bandwidth in your Netty application. In this recipe , we will cover how to do in by adding modification in pipeline and it will measure the size of sent/received ByteBuffer.
How to do it
Look at the diagram, we would hook the code into decode codec and encode codec. This code illustrates the solution
public class NettyMonitorIO {
static final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyyMMdd HH:mm");
static ConcurrentMap<String, Long> dataOutStats = new ConcurrentHashMap<String, Long>();
static ConcurrentMap<String, Long> dataInStats = new ConcurrentHashMap<String, Long>();
public static long updateDataOut(ByteBuf buf) {
String time = DATE_TIME_FORMAT.format(new Date());
long c = dataOutStats.getOrDefault(time, 0L) + buf.readableBytes();
dataOutStats.put(time, c);
return c;
}
public static long updateDataIn(ByteBuf buf) {
String time = DATE_TIME_FORMAT.format(new Date());
long c = dataInStats.getOrDefault(time, 0L) + buf.writableBytes();
dataInStats.put(time, c);
return c;
}
static {
new Timer(true).schedule(new TimerTask() {
@Override
public void run() {
System.out.println("");
System.out.println("Data In Stats:");
dataInStats.forEach((String key, Long val)>{
LogUtil.println(key + " : "+val);
});
System.out.println("Data Out Stats:");
dataOutStats.forEach((String key, Long val)>{
LogUtil.println(key + " : "+val);
});
}
}, 2000, 2000);
}
}
public class PurchaseDataDecoder extends ObjectDecoder {
public PurchaseDataDecoder() {
super(ClassResolvers.weakCachingConcurrentResolver(null));
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf buf)
throws Exception {
Object object = super.decode(ctx, buf);
NettyMonitorIO.updateDataIn(buf);
return object;
}
}
public class PurchaseDataEncoder extends ObjectEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf buf) throws Exception {
super.encode(ctx, msg, buf);
NettyMonitorIO.updateDataOut(buf);
}
}
How it works 20141205 10:56:35 INFO LoggingHandler:101 [id: 0x1fbe5190] REGISTERED
20141205 10:56:35 INFO LoggingHandler:101 [id: 0x1fbe5190] BIND(0.0.0.0/0.0.0.0:8007)
20141205 10:56:35 INFO LoggingHandler:101 [id: 0x1fbe5190, /0:0:0:0:0:0:0:0:8007] ACTIVE
20141205 10:56:39 INFO LoggingHandler:101 [id: 0x1fbe5190, /0:0:0:0:0:0:0:0:8007] RECEIVED: [id: 0x10fdf42c, /127.0.0.1:33991 => /127.0.0.1:8007]
20141205 10:56:39 INFO PurchaseServer:33 localAddress.hostname localhost
20141205 10:56:39 INFO PurchaseServer:34 localAddress.port 8007
20141205 10:56:39 INFO PurchaseServer:37 remoteAddress.hostname localhost
20141205 10:56:39 INFO PurchaseServer:38 remoteAddress.port 33991
processed Purchase {"itemId":1001,"price":499.99,"buyer":"Trieu","seller":"Amazon","unixTime":1417751798,"processed":false}
Data In Stats:
20141205 10:56 : 942
Data Out Stats:
20141205 10:56 : 82
Recipe 2.6 Checking Heartbeat Server using UDP
Problem Sometimes we don’t want to use TCP/IP for transmitting data, because the reliability is not our priority. By UDP is a connectionless protocol, so it’s more better than TCP/IP in many applications, such as:
● Broadcasting message in a publishsubscribe kind of application.
● Oneway "logging" activity can be handled nicely with UDP packets
● Implementing "heartbeat" or "I'm alive" messages, because we want to get a simple answer to another server quickly
● Video streaming and especially VoIP, voice chat ,..(e.g. Skype), that a dropped packet is not such a big deal.
How to do it … At server, loading Bootrap object with “option(ChannelOption.SO_BROADCAST, true)”
The handler of server should extend SimpleChannelInboundHandler.
The server UDP socket is bind at 255.255.255.255, port 8080
Suppose we have an ImportantServer , that must be monitored.
public class ImportantServer {
private static final int PORT = 8080;
public static void main(String[] args) throws Exception {
EventLoopGroup loopGroup = new NioEventLoopGroup();
try {
BootstrapTemplate.newBootstrapUDP(loopGroup, new HeartBeatHandler(), PORT)
.sync().channel().closeFuture().await();
} finally {
loopGroup.shutdownGracefully();
}
}
}
The code method “channelRead0” of HeartBeatHandler
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
System.err.println(packet);
String s = packet.content().toString(CharsetUtil.UTF_8);
System.out.println(s);
ByteBuf buf = Unpooled.copiedBuffer("I'm alive at "+new Date(), CharsetUtil.UTF_8);
ctx.write(new DatagramPacket(buf, packet.sender()));
}
The ServerHealthChecker, which uses UDP as main protocol
SimpleChannelInboundHandler<DatagramPacket> handler = new SimpleChannelInboundHandler<DatagramPacket>() {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg)
throws Exception {
String response = msg.content().toString(CharsetUtil.UTF_8);
System.out.println("LogClient: " + response);
ctx.close();
}
};
EventLoopGroup loopGroup = new NioEventLoopGroup();
try {
ChannelFuture chnlFuture = BootstrapTemplate.newBootstrapUDP(loopGroup, handler, 0);
Channel ch = chnlFuture.sync().channel();
// Broadcast to port 8080
InetSocketAddress udpAddr = new InetSocketAddress("255.255.255.255", PORT);
for (int i=0;i<5;i++) {
ByteBuf buf = Unpooled.copiedBuffer("ping at " + new Date(),CharsetUtil.UTF_8);
ch.write(new DatagramPacket(buf,udpAddr));
Thread.sleep(1000);
}
ch.flush().closeFuture().sync();
//If the channel is not closed within 5 seconds, quit
if (!ch.closeFuture().await(10000)) {
System.err.println("request timed out.");
}
} finally {
loopGroup.shutdownGracefully();
}
Recipe 2.9 Using Stream Control Transmission Protocol (SCTP) codec
Getting to know SCTP (Stream Control Transmission Protocol) is a protocol for transmitting multiple streams of data at the same time between two end points that have established a connection in a network. Sometimes referred to as "next generation TCP" (Transmission Control Protocol).
SCTP is a natural candidate to support telephone signaling over the Internet as well as other messageoriented applications. Applications that require high degrees of fault tolerance, such as online banking and stock market transaction exchanges, will benefit from SCTP's multihoming.
You can follow this link for more information about SCTP protocol
http://www.ibm.com/developerworks/library/lsctp
Problem Your want to build a SCTP server that accepts and prints out all received messages, and SCTP client that send multipe messages at the same time.
Getting ready In linux OS you need to install lksctp library as linux doesn't have this installed by default.
● Fedora: sudo yum install lksctptools1.0.91.fc10 or ● Ubuntu: sudo aptget install lksctptools
How to do it ... In SCTP server bootstrap
EventLoopGroup mainLoop = new NioEventLoopGroup(1);
EventLoopGroup workerLoop = new NioEventLoopGroup();
try {
ChannelFuture f = new ServerBootstrap().group(mainLoop, workerLoop)
.channel(NioSctpServerChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SctpChannel>() {
@Override
public void initChannel(SctpChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new SimpleSctpServerHandler());
}
}).bind(PORT).sync();
f.channel().closeFuture().sync();
} finally {
mainLoop.shutdownGracefully();
workerLoop.shutdownGracefully();
}
}
In the code of SCTP Server handler, at channelRead, the message is instance of SctpMessage
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println(msg);
if(msg instanceof SctpMessage){
SctpMessage sctpMsg = (SctpMessage) msg;
System.out.println(sctpMsg.content().toString(CharsetUtil.UTF_8));
ctx.write(sctpMsg);
}
}
The SCTP client bootstrap
EventLoopGroup loopGroup = new NioEventLoopGroup();
try {
ChannelFuture f = new Bootstrap().group(loopGroup)
.channel(NioSctpChannel.class)
// set SCTP option
.option(SctpChannelOption.SCTP_NODELAY, true)
.handler(new ChannelInitializer<SctpChannel>() {
@Override
public void initChannel(SctpChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new SimpleSctpClientHandler());
}
}).connect(HOST, PORT).sync();
f.channel().closeFuture().sync();
} finally {
loopGroup.shutdownGracefully();
}
The SCTP client handler code
private final ByteBuf firstMessage, secondMessage;
public SimpleSctpClientHandler() {
firstMessage = Unpooled.copiedBuffer("first message",CharsetUtil.UTF_8);
secondMessage = Unpooled.copiedBuffer("second message",CharsetUtil.UTF_8);
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.write(new SctpMessage(0, 0, firstMessage));
ctx.write(new SctpMessage(0, 0, secondMessage));
ctx.flush();
}
Recipe 2.10 Building simple FTP server
Problem FTP or File Transfer Protocol is most common protocol for transferring files between client and server. We will learn to use Netty to build a simple FTP server in this recipe.
Getting ready Due to the core Netty 4 is still support completely FTP Channel Handler, so for rapid development, you should check out this source code
https://github.com/codingtony/nettyftpreceiver
It’s the Netty handler, partial implementation of RFC 959 "File Transfer Protocol (FTP)" for receiving FTP files.
How to do it … You define the template for receiving data with FTP protocol.
Example code of the data receiver class :
class FileReceiver implements DataReceiver {
@Override
public void receive(String name, InputStream data) throws IOException {
System.out.println("got file: [" + name + "]");
//copy to local folder
Files.copy(data, new File("./data/"+name).toPath());
}
}
In Netty’s bootstrap code, add 2 classes: CrlfStringDecoder and FtpServerHandler to ChannelPipeline
public class SimpleServerFTP {
public static void main(String... args) throws Exception {
DataReceiver dataReceiver = new FileReceiver();
final DefaultCommandExecutionTemplate tpl = new DefaultCommandExecutionTemplate(dataReceiver);
ChannelInitializer<SocketChannel> initializer = new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new CrlfStringDecoder());
p.addLast(new FtpServerHandler(tpl));
}
};
BootstrapTemplate.newServerBootstrap("127.0.0.1", 2121, initializer);
}
}
Recipe 2.10 Building server RPC (Remote Procedure Call) with Apache Avro
Problem How could we make distributed programming look like as much as possible like normal programming ?
One of simple solution is the RPC or the Remote Procedure Call, is a wellunderstood mechanism and popular in distributed programming. Currently, Netty framework is not fully implemented RPC, but there are many good and open source projects do this job very well.
In this recipe, we will use Apache Avro, a data serialization system, which supports RPC with Netty as core library.
To illustrate RPC, we would build a simple server to receive mail and a client to send messages.
Getting to know Avro is a remote procedure call, fully supports data serialization framework and developed within Apache's Hadoop project. It uses JSON for defining data types and protocols, and serializes data in a compact binary format. Main features:
● Rich and complex data structures ● A compact, fast and binary data format for streaming data between hosts ● A container file, to store persistent data. ● Remote procedure call (RPC) for distributed computing ● Simple integration with dynamic languages, such as Python, Ruby, … so
integration between independent services
Getting ready Make sure you check out this code at the Git repository of book
https://github.com/trieu/nettycookbook
The full code at the package netty.cookbook.chapter2.recipe10
How to do it … Optional step, first on all, we need to create Mail protocol , or the Interface Definition Language to specify RPC call and return types.
In the current directory of nettycookbook’s code, go to tools and run
java jar avrotools1.7.7.jar compile protocol ../src/main/avro/mail.avpr ../src/main/java/
In the ProxyServerAvroRPC
public class ProxyServerAvroRPC {
public static class MailImpl implements Mail {
public Utf8 send(Message message) {
String s = String.format("message details, to:%s from:%s body:%s", message.getTo(), message.getFrom(), message.getBody());
LogUtil.println(s);
return new Utf8("Sent OK to "+ message.getTo());
}
}
private static Server server;
static void startServer() throws IOException {
server = new NettyServer(new SpecificResponder(Mail.class,new MailImpl()), new InetSocketAddress(10000));
}
public static void main(String[] args) throws Exception {
LogUtil.println("Starting ServerAvroRPC");
startServer();
LogUtil.println("ServerAvroRPC started and wait for 15s");
Thread.sleep(15000);
server.close();
}
}
In code of ClientAvroRPC
public class ClientAvroRPC {
public static void main(String[] args) throws IOException {
if(args.length < 3){
args = new String[] {"[email protected]","[email protected]","Hello !"};
}
NettyTransceiver client = new NettyTransceiver(new InetSocketAddress(10000));
Mail proxy = (Mail) SpecificRequestor.getClient(Mail.class, client);
LogUtil.println("ClientAvroRPC built OK, got proxy, ready to send data ...");
Message message = new Message();
message.setTo(new Utf8(args[0]));
message.setFrom(new Utf8(args[1]));
message.setBody(new Utf8(args[2]));
LogUtil.println("Calling proxy.send with message: " + message.toString());
LogUtil.println("Result from server: " + proxy.send(message));
client.close();
}
}
How it works When you start ProxyServerAvroRPC, the socket at port 10000 is listened.
The client, ClientAvroRPC would send a mail message “Hello !”, which is serialized and is sent to ServerAvroRPC via the method send of the instance of Mail class. The output when you run the code of ClientAvroRPC is:
Client built, got proxy
Calling proxy.send with message: {"to": "[email protected]", "from": "[email protected]", "body": "Hello !"}
Result: Sending message to [email protected] from [email protected] with body Hello !
Recipe 2.11 Building simple HTTP downloader
Problem: Using Netty framework to build a HTTP client, that downloads one specific large file from one specific host using one specific URL (https://example.com/abigfile.zip) over HTTP and saves it on disk.
Getting ready Make sure you check out this code at the Git repository of book
https://github.com/trieu/nettycookbook
The full code at the package netty.cookbook.chapter2.recipe11, class HttpDownloader.
How to do it … We will use the method newHttpClientBootstrap of BootstrapTemplate to create new bootstrap for a HTTP client, we set the URL with HttpDownloadHandler.
public class HttpDownloader {
public static class HttpDownloadHandler extends
SimpleChannelInboundHandler<HttpObject> {
int written = 0;
File file;
FileOutputStream outStream;
FileChannel fileChnl;
public HttpDownloadHandler(File file) {
super();
this.file = file;
}
void initFileChannel() throws FileNotFoundException {
outStream = new FileOutputStream(file);
fileChnl = outStream.getChannel();
}
void writeBytesToFile(ByteBuf byteBuf) throws IOException {
int writtenIndex = 0;
try {
ByteBuffer byteBuffer = byteBuf.nioBuffer();
writtenIndex += fileChnl.write(byteBuffer, written);
written += writtenIndex;
byteBuf.readerIndex(byteBuf.readerIndex() + writtenIndex);
fileChnl.force(false);
} catch (Exception e) {
fileChnl.close();
outStream.close();
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
try {
if (msg instanceof HttpRequest) {
initFileChannel();
} else if (msg instanceof HttpContent) {
if (fileChnl == null) {
initFileChannel();
}
ByteBuf byteBuf = ((HttpContent) msg).content();
writeBytesToFile(byteBuf);
} else if (msg instanceof LastHttpContent) {
if (fileChnl != null && outStream != null) {
fileChnl.close();
outStream.close();
}
ctx.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
String url = "http://www.mc2ads.com";
File file = new File("./data/www.mc2ads.com.html");
ChannelHandler handler = new HttpDownloadHandler(file);
BootstrapTemplate.newHttpClientBootstrap(url, handler);
}
}