├── .gitignore ├── .scalafmt.conf ├── README.md ├── build.sbt ├── project ├── build.properties └── plugins.sbt └── src └── main └── scala └── org └── edla └── netty └── example ├── discard ├── DiscardClient.scala ├── DiscardClientHandler.scala ├── DiscardServer.scala └── DiscardServerHandler.scala ├── echo ├── EchoClient.scala ├── EchoClientHandler.scala ├── EchoServer.scala └── EchoServerHandler.scala ├── factorial ├── BigIntegerDecoder.scala ├── FactorialClient.scala ├── FactorialClientHandler.scala ├── FactorialClientPipelineFactory.scala ├── FactorialServer.scala ├── FactorialServerHandler.scala ├── FactorialServerPipelineFactory.scala └── NumberEncoder.scala ├── objectecho ├── ObjectEchoClient.scala ├── ObjectEchoClientHandler.scala ├── ObjectEchoServer.scala └── ObjectEchoServerHandler.scala ├── proxy ├── HexDumpProxy.scala ├── HexDumpProxyInboundHandler.scala └── HexDumpProxyPipelineFactory.scala ├── qotm ├── QuoteOfTheMomentClient.scala ├── QuoteOfTheMomentClientHandler.scala ├── QuoteOfTheMomentServer.scala └── QuoteOfTheMomentServerHandler.scala ├── telnet ├── TelnetClient.scala ├── TelnetClientHandler.scala ├── TelnetClientPipelineFactory.scala ├── TelnetServer.scala ├── TelnetServerHandler.scala └── TelnetServerPipelineFactory.scala └── uptime ├── UptimeClient.scala └── UptimeClientHandler.scala /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .cache-main 3 | .cache-tests 4 | .classpath 5 | .history 6 | .idea/ 7 | .project 8 | .settings/ 9 | .target/ 10 | .worksheet/ 11 | .DS_Store 12 | journal/ 13 | snapshots/ 14 | target/ 15 | /bin/ 16 | native/ 17 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | style = defaultWithAlign 2 | maxColumn = 120 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scala-netty-examples 2 | 3 | ## About ## 4 | This is a Scala port of the examples from [Netty distribution](https://github.com/netty/netty/tree/netty-3.6.0.Final/src/main/java/org/jboss/netty/example) 5 | 6 | scala-netty-examples is an EDLA project. 7 | 8 | The purpose of [edla.org](http://www.edla.org) is to promote the state of the art in various domains. 9 | 10 | ## License 11 | © 2012 Olivier ROLAND. Distributed under the GPLv3 License. 12 | 13 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | lazy val root = (project in file(".")) 2 | .settings( 3 | inThisBuild( 4 | List( 5 | organization := "org.edla", 6 | scalaVersion := "2.12.8", 7 | version := "3.10.6.Final", 8 | licenses := Seq("GNU GPL v3" -> url("http://www.gnu.org/licenses/gpl.html")), 9 | homepage := Some(url("http://github.com/newca12/scala-netty-examples")) 10 | ) 11 | ), 12 | name := "scala-netty-examples", 13 | scalacOptions ++= Seq( 14 | "-deprecation", // Emit warning and location for usages of deprecated APIs. 15 | "-encoding", 16 | "utf-8", // Specify character encoding used by source files. 17 | "-explaintypes", // Explain type errors in more detail. 18 | "-feature", // Emit warning and location for usages of features that should be imported explicitly. 19 | "-language:existentials", // Existential types (besides wildcard types) can be written and inferred 20 | "-language:experimental.macros", // Allow macro definition (besides implementation and application) 21 | "-language:higherKinds", // Allow higher-kinded types 22 | "-language:implicitConversions", // Allow definition of implicit functions called views 23 | "-language:postfixOps", // Allow postfix operator notation 24 | "-unchecked", // Enable additional warnings where generated code depends on assumptions. 25 | //https://github.com/scala/community-builds/issues/566 26 | "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access. //Fail with Scala 2.12.3 27 | //"-Xfatal-warnings", // Fail the compilation if there are any warnings. 28 | "-Xfuture", // Turn on future language features. 29 | "-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver. 30 | "-Xlint:by-name-right-associative", // By-name parameter of right associative operator. 31 | "-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error. 32 | "-Xlint:delayedinit-select", // Selecting member of DelayedInit. 33 | "-Xlint:doc-detached", // A Scaladoc comment appears to be detached from its element. 34 | "-Xlint:inaccessible", // Warn about inaccessible types in method signatures. 35 | "-Xlint:infer-any", // Warn when a type argument is inferred to be `Any`. 36 | "-Xlint:missing-interpolator", // A string literal appears to be missing an interpolator id. 37 | "-Xlint:nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'. 38 | "-Xlint:nullary-unit", // Warn when nullary methods return Unit. 39 | "-Xlint:option-implicit", // Option.apply used implicit view. 40 | "-Xlint:package-object-classes", // Class or object defined in package object. 41 | "-Xlint:poly-implicit-overload", // Parameterized overloaded implicit methods are not visible as view bounds. 42 | "-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field. 43 | "-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component. 44 | "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope. 45 | "-Xlint:unsound-match", // Pattern match may not be typesafe. 46 | "-Yno-adapted-args", // Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver. 47 | // https://groups.google.com/forum/#!topic/scalatest-users/LhdnazFoZ_k 48 | "-Ypartial-unification", // Enable partial unification in type constructor inference 49 | "-Ywarn-dead-code", // Warn when dead code is identified. 50 | "-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined. 51 | "-Ywarn-inaccessible", // Warn about inaccessible types in method signatures. 52 | "-Ywarn-infer-any", // Warn when a type argument is inferred to be `Any`. 53 | "-Ywarn-nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'. 54 | "-Ywarn-nullary-unit", // Warn when nullary methods return Unit. 55 | "-Ywarn-numeric-widen", // Warn when numerics are widened. 56 | "-Ywarn-unused:implicits", // Warn if an implicit parameter is unused. 57 | "-Ywarn-unused:imports", // Warn if an import selector is not referenced. 58 | "-Ywarn-unused:locals", // Warn if a local definition is unused. 59 | "-Ywarn-unused:params", // Warn if a value parameter is unused. 60 | "-Ywarn-unused:patvars", // Warn if a variable bound in a pattern is unused. 61 | "-Ywarn-unused:privates", // Warn if a private member is unused. 62 | "-Ywarn-value-discard" // Warn when non-Unit expression results are unused. 63 | ), 64 | libraryDependencies ++= Seq( 65 | "io.netty" % "netty" % "3.10.6.Final" 66 | ) 67 | ) 68 | 69 | 70 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.6.0-RC4") 2 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9") 3 | addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.3") 4 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/discard/DiscardClient.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.discard 2 | 3 | import java.net.InetSocketAddress 4 | import java.util.concurrent.Executors 5 | 6 | import org.jboss.netty.bootstrap.ClientBootstrap 7 | import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory 8 | import org.jboss.netty.channel.{ChannelPipeline, ChannelPipelineFactory, Channels} 9 | 10 | /** 11 | * Keeps sending random data to the specified address. 12 | */ 13 | object DiscardClient { 14 | 15 | def main(args: Array[String]): Unit = { 16 | // Print usage if no argument is specified. 17 | if (args.length < 2 || args.length > 3) { 18 | System.err.println( 19 | "Usage: " + DiscardClient.getClass.getSimpleName + 20 | " []" 21 | ) 22 | return 23 | } 24 | 25 | // Parse options. 26 | val host = args(0) 27 | val port = args(1).toInt 28 | var firstMessageSize: Int = 0 29 | if (args.length == 3) firstMessageSize = args(2).toInt 30 | else firstMessageSize = 256 31 | 32 | // Configure the server. 33 | val bootstrap = new ClientBootstrap( 34 | new NioClientSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool) 35 | ) 36 | 37 | // Set up the pipeline factory. 38 | bootstrap.setPipelineFactory(new ChannelPipelineFactory { 39 | override def getPipeline: ChannelPipeline = Channels.pipeline(new DiscardClientHandler(firstMessageSize)) 40 | }) 41 | 42 | // Start the connection attempt. 43 | val future = bootstrap.connect(new InetSocketAddress(host, port)) 44 | 45 | // Wait until the connection is closed or the connection attempt fails. 46 | future.getChannel.getCloseFuture.awaitUninterruptibly 47 | 48 | // Shut down thread pools to exit. 49 | bootstrap.releaseExternalResources() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/discard/DiscardClientHandler.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.discard 2 | 3 | import java.util.logging.Logger 4 | 5 | import scala.util.control.Breaks.break 6 | import scala.util.control.Breaks.breakable 7 | 8 | import org.jboss.netty.buffer.ChannelBuffer 9 | import org.jboss.netty.buffer.ChannelBuffers 10 | import org.jboss.netty.channel.ChannelEvent 11 | import org.jboss.netty.channel.ChannelHandlerContext 12 | import org.jboss.netty.channel.ChannelState 13 | import org.jboss.netty.channel.ChannelStateEvent 14 | import org.jboss.netty.channel.ExceptionEvent 15 | import org.jboss.netty.channel.MessageEvent 16 | import org.jboss.netty.channel.SimpleChannelUpstreamHandler 17 | import org.jboss.netty.channel.WriteCompletionEvent 18 | 19 | /** 20 | * Handles a client-side channel. 21 | */ 22 | class DiscardClientHandler(messageSize: Int) extends SimpleChannelUpstreamHandler { 23 | 24 | require(messageSize > 0) 25 | 26 | private val logger = Logger.getLogger(getClass.getName) 27 | 28 | val content = new Array[Byte](messageSize) 29 | 30 | private var transferredBytes = 0L 31 | 32 | def getTransferredBytes: Long = transferredBytes 33 | 34 | override def handleUpstream(ctx: ChannelHandlerContext, e: ChannelEvent): Unit = { 35 | e match { 36 | case c: ChannelStateEvent => if (c.getState != ChannelState.INTEREST_OPS) logger.info(e.toString) 37 | case _ => 38 | } 39 | 40 | // Let SimpleChannelHandler call actual event handler methods below. 41 | super.handleUpstream(ctx, e) 42 | } 43 | 44 | // Send the initial messages. 45 | override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { generateTraffic(e) } 46 | 47 | // Keep sending messages whenever the current socket buffer has room. 48 | override def channelInterestChanged(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { generateTraffic(e) } 49 | 50 | override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { 51 | // Server is supposed to send nothing. Therefore, do nothing. 52 | } 53 | 54 | override def writeComplete(ctx: ChannelHandlerContext, e: WriteCompletionEvent): Unit = { 55 | transferredBytes += e.getWrittenAmount 56 | } 57 | 58 | override def exceptionCaught(context: ChannelHandlerContext, e: ExceptionEvent): Unit = { 59 | // Close the connection when an exception is raised. 60 | logger.warning("Unexpected exception from downstream." + e.getCause) 61 | e.getChannel.close() 62 | } 63 | 64 | private def generateTraffic(e: ChannelStateEvent): Unit = { 65 | // Keep generating traffic until the channel is unwritable. 66 | // A channel becomes unwritable when its internal buffer is full. 67 | // If you keep writing messages ignoring this property, 68 | // you will end up with an OutOfMemoryError. 69 | val channel = e.getChannel 70 | //TODO rewrite the loop as a recursive function. (Refer to Programming in Scala, 7.6 Living without break and continue) 71 | breakable { 72 | while (channel.isWritable) { 73 | val m: ChannelBuffer = nextMessage() 74 | if (m == null) { 75 | break 76 | } 77 | channel.write(m) 78 | } 79 | } 80 | } 81 | 82 | private def nextMessage(): ChannelBuffer = ChannelBuffers.wrappedBuffer(content) 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/discard/DiscardServer.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.discard 2 | 3 | import java.net.InetSocketAddress 4 | import java.util.concurrent.Executors 5 | 6 | import org.jboss.netty.bootstrap.ServerBootstrap 7 | import org.jboss.netty.channel.{ChannelPipeline, ChannelPipelineFactory, Channels} 8 | import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory 9 | 10 | /** 11 | * Discards any incoming data. 12 | */ 13 | object DiscardServer { 14 | 15 | def main(args: Array[String]): Unit = { 16 | // Configure the server. 17 | val bootstrap = new ServerBootstrap( 18 | new NioServerSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool) 19 | ) 20 | // Set up the pipeline factory. 21 | bootstrap.setPipelineFactory(new ChannelPipelineFactory { 22 | override def getPipeline: ChannelPipeline = Channels.pipeline(new DiscardServerHandler) 23 | }) 24 | 25 | // Bind and start to accept incoming connections. 26 | bootstrap.bind(new InetSocketAddress(8080)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/discard/DiscardServerHandler.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.discard 2 | 3 | import org.jboss.netty.buffer.ChannelBuffer 4 | import org.jboss.netty.channel.{ 5 | ChannelEvent, 6 | ChannelHandlerContext, 7 | ChannelStateEvent, 8 | ExceptionEvent, 9 | MessageEvent, 10 | SimpleChannelUpstreamHandler 11 | } 12 | import java.util.logging.Logger 13 | 14 | /** 15 | * Handles a server-side channel. 16 | */ 17 | class DiscardServerHandler extends SimpleChannelUpstreamHandler { 18 | 19 | val logger: Logger = Logger.getLogger(getClass.getName) 20 | 21 | var transferredBytes = 0L 22 | 23 | def getTransferredBytes: Long = transferredBytes 24 | 25 | override def handleUpstream(ctx: ChannelHandlerContext, e: ChannelEvent): Unit = { 26 | e match { 27 | case _: ChannelStateEvent => logger.info(e.toString) 28 | case _ => None 29 | } 30 | super.handleUpstream(ctx, e) 31 | } 32 | 33 | override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { 34 | // Discard received data silently by doing nothing. 35 | transferredBytes += ((e.getMessage match { 36 | case c: ChannelBuffer => c 37 | case _ => throw new ClassCastException 38 | }) readableBytes) 39 | } 40 | 41 | override def exceptionCaught(context: ChannelHandlerContext, e: ExceptionEvent): Unit = { 42 | // Close the connection when an exception is raised. 43 | logger.warning("Unexpected exception from downstream." + e.getCause) 44 | e.getChannel.close() 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/echo/EchoClient.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.echo 2 | 3 | import java.net.InetSocketAddress 4 | import java.util.concurrent.Executors 5 | 6 | import org.jboss.netty.bootstrap.ClientBootstrap 7 | import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory 8 | import org.jboss.netty.channel.{ChannelPipeline, ChannelPipelineFactory, Channels} 9 | 10 | /** 11 | * Sends one message when a connection is open and echoes back any received 12 | * data to the server. Simply put, the echo client initiates the ping-pong 13 | * traffic between the echo client and server by sending the first message to 14 | * the server. 15 | */ 16 | object EchoClient { 17 | 18 | def main(args: Array[String]): Unit = { 19 | // Print usage if no argument is specified. 20 | if (args.length < 2 || args.length > 3) { 21 | System.err.println( 22 | "Usage: " + EchoClient.getClass.getSimpleName + 23 | " []" 24 | ) 25 | return 26 | } 27 | 28 | // Parse options. 29 | val host = args(0) 30 | val port = args(1).toInt 31 | var firstMessageSize: Int = 0 32 | if (args.length == 3) firstMessageSize = args(2).toInt 33 | else firstMessageSize = 256 34 | 35 | // Configure the server. 36 | val bootstrap = new ClientBootstrap( 37 | new NioClientSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool) 38 | ) 39 | 40 | // Set up the pipeline factory. 41 | bootstrap.setPipelineFactory(new ChannelPipelineFactory { 42 | override def getPipeline: ChannelPipeline = { 43 | Channels.pipeline(new EchoClientHandler(firstMessageSize)) 44 | } 45 | }) 46 | 47 | // Start the connection attempt. 48 | val future = bootstrap.connect(new InetSocketAddress(host, port)) 49 | 50 | // Wait until the connection is closed or the connection attempt fails. 51 | future.getChannel.getCloseFuture.awaitUninterruptibly 52 | 53 | // Shut down thread pools to exit. 54 | bootstrap.releaseExternalResources() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/echo/EchoClientHandler.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.echo 2 | 3 | import java.util.concurrent.atomic.AtomicLong 4 | import java.util.logging.Logger 5 | 6 | import org.jboss.netty.buffer.ChannelBuffer 7 | import org.jboss.netty.buffer.ChannelBuffers 8 | import org.jboss.netty.channel.ChannelHandlerContext 9 | import org.jboss.netty.channel.ChannelStateEvent 10 | import org.jboss.netty.channel.ExceptionEvent 11 | import org.jboss.netty.channel.MessageEvent 12 | import org.jboss.netty.channel.SimpleChannelUpstreamHandler 13 | 14 | /** 15 | * Handler implementation for the echo client. It initiates the ping-pong 16 | * traffic between the echo client and server by sending the first message to 17 | * the server. 18 | */ 19 | class EchoClientHandler(firstMessageSize: Int) extends SimpleChannelUpstreamHandler { 20 | 21 | require(firstMessageSize > 0) 22 | 23 | private val logger = Logger.getLogger(getClass.getName) 24 | 25 | private val transferredBytes = new AtomicLong 26 | 27 | private val firstMessage = ChannelBuffers.buffer(firstMessageSize) 28 | val range: Range = 0.until(firstMessage.capacity) 29 | for (i <- range) { firstMessage.writeByte(i.toByte) } 30 | 31 | def getTransferredBytes: Long = transferredBytes.get 32 | 33 | // Send the first message. Server will not send anything here 34 | // because the firstMessage's capacity is 0. 35 | override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { 36 | e.getChannel.write(firstMessage) 37 | } 38 | 39 | override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { 40 | // Send back the received message to the remote peer. 41 | transferredBytes.addAndGet((e.getMessage match { 42 | case c: ChannelBuffer => c 43 | case _ => throw new ClassCastException 44 | }) readableBytes) 45 | e.getChannel.write(e.getMessage) 46 | } 47 | 48 | override def exceptionCaught(context: ChannelHandlerContext, e: ExceptionEvent): Unit = { 49 | // Close the connection when an exception is raised. 50 | logger.warning("Unexpected exception from downstream." + e.getCause) 51 | e.getChannel.close() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/echo/EchoServer.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.echo 2 | 3 | import java.net.InetSocketAddress 4 | import java.util.concurrent.Executors 5 | 6 | import org.jboss.netty.bootstrap.ServerBootstrap 7 | import org.jboss.netty.channel.{ChannelPipeline, ChannelPipelineFactory, Channels} 8 | import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory 9 | 10 | /** 11 | * Echoes back any received data from a client. 12 | */ 13 | object EchoServer { 14 | 15 | def main(args: Array[String]): Unit = { 16 | // Configure the server. 17 | val bootstrap = new ServerBootstrap( 18 | new NioServerSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool) 19 | ) 20 | // Set up the pipeline factory. 21 | bootstrap.setPipelineFactory(new ChannelPipelineFactory { 22 | override def getPipeline: ChannelPipeline = Channels.pipeline(new EchoServerHandler) 23 | }) 24 | 25 | // Bind and start to accept incoming connections. 26 | bootstrap.bind(new InetSocketAddress(8080)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/echo/EchoServerHandler.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.echo 2 | 3 | import java.util.concurrent.atomic.AtomicLong 4 | import java.util.logging.Logger 5 | 6 | import org.jboss.netty.buffer.ChannelBuffer 7 | import org.jboss.netty.channel.{ChannelHandlerContext, ExceptionEvent, MessageEvent, SimpleChannelUpstreamHandler} 8 | 9 | /** 10 | * Handler implementation for the echo server. 11 | */ 12 | class EchoServerHandler extends SimpleChannelUpstreamHandler { 13 | 14 | val logger: Logger = Logger.getLogger(getClass.getName) 15 | 16 | val transferredBytes = new AtomicLong 17 | 18 | def getTransferredBytes: Long = transferredBytes.get 19 | 20 | override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { 21 | // Send back the received message to the remote peer. 22 | transferredBytes.addAndGet((e.getMessage match { 23 | case c: ChannelBuffer => c 24 | case _ => throw new ClassCastException 25 | }) readableBytes) 26 | e.getChannel.write(e.getMessage) 27 | } 28 | 29 | override def exceptionCaught(context: ChannelHandlerContext, e: ExceptionEvent): Unit = { 30 | // Close the connection when an exception is raised. 31 | logger.warning("Unexpected exception from downstream." + e.getCause) 32 | e.getChannel.close() 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/factorial/BigIntegerDecoder.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.factorial 2 | 3 | import java.math.BigInteger 4 | import org.jboss.netty.buffer.ChannelBuffer 5 | import org.jboss.netty.channel.{Channel, ChannelHandlerContext} 6 | import org.jboss.netty.handler.codec.frame.{CorruptedFrameException, FrameDecoder} 7 | 8 | /** 9 | * Decodes the binary representation of a [[BigInteger]] prepended 10 | * with a magic number ('F' or 0x46) and a 32-bit integer length prefix into a 11 | * BigInteger instance. For example, { 'F', 0, 0, 0, 1, 42 } will be 12 | * decoded into new BigInteger("42"). 13 | */ 14 | class BigIntegerDecoder extends FrameDecoder { 15 | 16 | override def decode(ctx: ChannelHandlerContext, channel: Channel, buffer: ChannelBuffer): Object = { 17 | // Wait until the length prefix is available. 18 | if (buffer.readableBytes < 5) { 19 | return null 20 | } 21 | 22 | buffer.markReaderIndex() 23 | 24 | // Check the magic number. 25 | val magicNumber: Int = buffer.readUnsignedByte 26 | if (magicNumber != 'F') { 27 | buffer.resetReaderIndex() 28 | throw new CorruptedFrameException("Invalid magic number: " + magicNumber) 29 | } 30 | 31 | // Wait until the whole data is available. 32 | val dataLength = buffer.readInt 33 | if (buffer.readableBytes < dataLength) { 34 | buffer.resetReaderIndex() 35 | return null 36 | } 37 | 38 | // Convert the received data into a new BigInteger. 39 | val decoded = new Array[Byte](dataLength) 40 | buffer.readBytes(decoded) 41 | 42 | new BigInteger(decoded) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/factorial/FactorialClient.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.factorial 2 | 3 | import java.net.InetSocketAddress 4 | import java.util.concurrent.Executors 5 | 6 | import org.jboss.netty.bootstrap.ClientBootstrap 7 | import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory 8 | 9 | /** 10 | * Sends a sequence of integers to a FactorialServer to calculate 11 | * the factorial of the specified integer. 12 | */ 13 | object FactorialClient { 14 | 15 | def main(args: Array[String]): Unit = { 16 | // Print usage if no argument is specified. 17 | if (args.length != 3) { 18 | System.err.println( 19 | "Usage: " + FactorialClient.getClass.getSimpleName + 20 | " " 21 | ) 22 | return 23 | } 24 | 25 | // Parse options. 26 | val host = args(0) 27 | val port = args(1).toInt 28 | val count = args(2).toInt 29 | if (count <= 0) { 30 | throw new IllegalArgumentException("count must be a positive integer.") 31 | } 32 | 33 | // Configure the client. 34 | val bootstrap = new ClientBootstrap( 35 | new NioClientSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool) 36 | ) 37 | 38 | // Set up the event pipeline factory. 39 | bootstrap.setPipelineFactory(new FactorialClientPipelineFactory(count)) 40 | 41 | // Make a new connection. 42 | val connectFuture = bootstrap.connect(new InetSocketAddress(host, port)) 43 | 44 | // Wait until the connection is made successfully. 45 | val channel = connectFuture.awaitUninterruptibly.getChannel 46 | 47 | // Get the handler instance to retrieve the answer. 48 | val handler: FactorialClientHandler = channel.getPipeline.getLast.asInstanceOf[FactorialClientHandler] 49 | 50 | // Print out the answer. 51 | System.err.println(s"Factorial of ${count} is: ${handler.getFactorial}") 52 | 53 | // Shut down all thread pools to exit. 54 | bootstrap.releaseExternalResources() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/factorial/FactorialClientHandler.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.factorial 2 | 3 | import java.math.BigInteger 4 | import java.util.concurrent.LinkedBlockingQueue 5 | import java.util.logging.Logger 6 | 7 | import org.jboss.netty.channel._ 8 | 9 | import scala.util.control.Breaks.{break, breakable} 10 | 11 | /** 12 | * Handler for a client-side channel. This handler maintains stateful 13 | * information which is specific to a certain channel using member variables. 14 | * Therefore, an instance of this handler can cover only one channel. You have 15 | * to create a new handler instance whenever you create a new channel and insert 16 | * this handler to avoid a race condition. 17 | */ 18 | class FactorialClientHandler(count: Int) extends SimpleChannelUpstreamHandler { 19 | 20 | private val logger = Logger.getLogger(getClass.getName) 21 | 22 | private var i: Int = 1 23 | private var receivedMessages: Int = 0 24 | private val answer = new LinkedBlockingQueue[BigInteger] 25 | 26 | logger.info("FactorialClientHandler:count:" + count) 27 | 28 | def getFactorial: BigInteger = { 29 | logger.info("FactorialClientHandler.getFactorial") 30 | var interrupted = false 31 | while (true) { 32 | try { 33 | val factorial = answer.take 34 | if (interrupted) { 35 | Thread.currentThread.interrupt() 36 | } 37 | factorial 38 | } catch { 39 | case _: InterruptedException => 40 | interrupted = true 41 | } 42 | } 43 | BigInteger.ONE 44 | } 45 | 46 | override def handleUpstream(ctx: ChannelHandlerContext, e: ChannelEvent): Unit = { 47 | logger.info("FactorialClientHandler.handleStream") 48 | e match { 49 | case _: ChannelStateEvent => logger.info(e.toString) 50 | case _ => 51 | } 52 | super.handleUpstream(ctx, e) 53 | } 54 | 55 | override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { 56 | sendNumbers(e) 57 | } 58 | 59 | override def channelInterestChanged(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { 60 | sendNumbers(e) 61 | } 62 | 63 | override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { 64 | receivedMessages += 1 65 | if (receivedMessages == count) { 66 | // Offer the answer after closing the connection. 67 | e.getChannel 68 | .close() 69 | .addListener((_: ChannelFuture) => { 70 | val offered: Boolean = answer.offer(e.getMessage.asInstanceOf[BigInteger]) 71 | assert(offered) 72 | }) 73 | } 74 | } 75 | 76 | override def exceptionCaught(context: ChannelHandlerContext, e: ExceptionEvent): Unit = { 77 | // Close the connection when an exception is raised. 78 | logger.warning("Unexpected exception from downstream." + e.getCause) 79 | e.getChannel.close() 80 | } 81 | 82 | def sendNumbers(e: ChannelStateEvent): Unit = { 83 | val channel = e.getChannel 84 | //TODO rewrite the loop as a recursive function. (Refer to Programming in Scala, 7.6 Living without break and continue) 85 | breakable { 86 | while (channel.isWritable) { 87 | if (i <= count) { 88 | channel.write(i) 89 | i += 1 90 | } else { 91 | break 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/factorial/FactorialClientPipelineFactory.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.factorial 2 | import org.jboss.netty.channel.{ChannelPipeline, ChannelPipelineFactory} 3 | 4 | /** 5 | * Creates a newly configured ChannelPipeline for a client-side channel. 6 | */ 7 | class FactorialClientPipelineFactory(count: Int) extends ChannelPipelineFactory { 8 | 9 | override def getPipeline: ChannelPipeline = { 10 | 11 | val pipeline = org.jboss.netty.channel.Channels.pipeline 12 | 13 | // Enable stream compression (you can remove these two if unnecessary) 14 | //pipeline.addLast("deflater", new ZlibEncoder(ZlibWrapper.GZIP)) 15 | //pipeline.addLast("inflater", new ZlibDecoder(ZlibWrapper.GZIP)) 16 | 17 | // Add the number codec first, 18 | pipeline.addLast("decoder", new BigIntegerDecoder) 19 | pipeline.addLast("encoder", new NumberEncoder) 20 | 21 | // and then business logic. 22 | pipeline.addLast("handler", new FactorialClientHandler(count)) 23 | 24 | pipeline 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/factorial/FactorialServer.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.factorial 2 | 3 | import java.net.InetSocketAddress 4 | import java.util.concurrent.Executors 5 | import org.jboss.netty.bootstrap.ServerBootstrap 6 | import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory 7 | 8 | /** 9 | * Receives a sequence of integers from a FactorialClient to calculate 10 | * the factorial of the specified integer. 11 | */ 12 | object FactorialServer { 13 | 14 | def main(args: Array[String]): Unit = { 15 | // Configure the server. 16 | val bootstrap = new ServerBootstrap( 17 | new NioServerSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool) 18 | ) 19 | 20 | // Set up the event pipeline factory. 21 | bootstrap.setPipelineFactory(new FactorialServerPipelineFactory) 22 | 23 | // Bind and start to accept incoming connections. 24 | bootstrap.bind(new InetSocketAddress(8080)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/factorial/FactorialServerHandler.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.factorial 2 | 3 | import java.math.BigInteger 4 | import java.util.logging.Logger 5 | 6 | import org.jboss.netty.channel._ 7 | 8 | /** 9 | * Handler for a server-side channel. This handler maintains stateful 10 | * information which is specific to a certain channel using member variables. 11 | * Therefore, an instance of this handler can cover only one channel. You have 12 | * to create a new handler instance whenever you create a new channel and insert 13 | * this handler to avoid a race condition. 14 | */ 15 | class FactorialServerHandler extends SimpleChannelUpstreamHandler { 16 | 17 | val logger: Logger = Logger.getLogger(getClass.getName) 18 | 19 | private var lastMultiplier = 1 20 | //var factorial = new BigInteger(new Array[Byte](1)) 21 | private var factorial = BigInteger.ONE 22 | 23 | override def handleUpstream(ctx: ChannelHandlerContext, e: ChannelEvent): Unit = { 24 | e match { 25 | case _: ChannelStateEvent => logger.info(e.toString) 26 | case _ => None 27 | } 28 | super.handleUpstream(ctx, e) 29 | } 30 | 31 | override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { 32 | 33 | // Calculate the cumulative factorial and send it to the client. 34 | var number = BigInteger.ONE 35 | e.getMessage match { 36 | case _: BigInteger => number = e.getMessage.asInstanceOf[BigInteger] 37 | case _ => number = new BigInteger(e.getMessage.toString) 38 | } 39 | lastMultiplier = number.intValue 40 | factorial = factorial.multiply(number) 41 | e.getChannel.write(factorial) 42 | } 43 | 44 | override def channelDisconnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { 45 | System.err.println(s"Factorial of $lastMultiplier is: $factorial") 46 | } 47 | 48 | override def exceptionCaught(context: ChannelHandlerContext, e: ExceptionEvent): Unit = { 49 | // Close the connection when an exception is raised. 50 | logger.warning("Unexpected exception from downstream." + e.getCause) 51 | e.getChannel.close() 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/factorial/FactorialServerPipelineFactory.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.factorial 2 | 3 | import org.jboss.netty.channel.{ChannelPipeline, ChannelPipelineFactory} 4 | 5 | /** 6 | * Creates a newly configured ChannelPipeline for a server-side channel. 7 | */ 8 | class FactorialServerPipelineFactory() extends ChannelPipelineFactory { 9 | 10 | override def getPipeline: ChannelPipeline = { 11 | 12 | val pipeline = org.jboss.netty.channel.Channels.pipeline 13 | 14 | // Enable stream compression (you can remove these two if unnecessary) 15 | //pipeline.addLast("deflater", new ZlibEncoder(ZlibWrapper.GZIP)) 16 | //pipeline.addLast("inflater", new ZlibDecoder(ZlibWrapper.GZIP)) 17 | 18 | // Add the number codec first, 19 | pipeline.addLast("decoder", new BigIntegerDecoder) 20 | pipeline.addLast("encoder", new NumberEncoder) 21 | 22 | // and then business logic. 23 | // Please note we create a handler for every new channel 24 | // because it has stateful properties. 25 | pipeline.addLast("handler", new FactorialServerHandler) 26 | 27 | pipeline 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/factorial/NumberEncoder.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.factorial 2 | import java.math.BigInteger 3 | import org.jboss.netty.buffer.ChannelBuffers 4 | import org.jboss.netty.channel.{Channel, ChannelHandlerContext} 5 | import org.jboss.netty.handler.codec.oneone.OneToOneEncoder 6 | 7 | /** 8 | * Encodes a Number into the binary representation prepended with 9 | * a magic number ('F' or 0x46) and a 32-bit length prefix. For example, 42 10 | * will be encoded to { 'F', 0, 0, 0, 1, 42 }. 11 | */ 12 | class NumberEncoder extends OneToOneEncoder { 13 | 14 | override def encode(ctx: ChannelHandlerContext, channel: Channel, msg: Object): Object = { 15 | msg match { 16 | case _: Number => 17 | // Ignore what this encoder can't encode. 18 | case _ => msg 19 | } 20 | 21 | // Convert to a BigInteger first for easier implementation. 22 | val v = msg match { 23 | case m: BigInteger => m 24 | // Ignore what this encoder can't encode. 25 | case _ => new BigInteger(String.valueOf(msg)) 26 | } 27 | 28 | // Convert the number into a byte array. 29 | val data = v.toByteArray 30 | val dataLength = data.length 31 | 32 | // Construct a message. 33 | val buf = ChannelBuffers.dynamicBuffer 34 | buf.writeByte('F'.toByte) // magic number 35 | buf.writeInt(dataLength) // data length 36 | buf.writeBytes(data) // data 37 | 38 | // Return the constructed message. 39 | buf 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/objectecho/ObjectEchoClient.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.objectecho 2 | 3 | import java.net.InetSocketAddress 4 | import java.util.concurrent.Executors 5 | 6 | import org.jboss.netty.bootstrap.ClientBootstrap 7 | import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory 8 | import org.jboss.netty.channel.{ChannelPipeline, ChannelPipelineFactory, Channels} 9 | import org.jboss.netty.handler.codec.serialization.{ClassResolvers, ObjectDecoder, ObjectEncoder} 10 | 11 | /** 12 | * Modification of EchoClient which utilizes Java object serialization. 13 | */ 14 | object ObjectEchoClient { 15 | 16 | def main(args: Array[String]): Unit = { 17 | // Print usage if no argument is specified. 18 | if (args.length < 2 || args.length > 3) { 19 | System.err.println( 20 | "Usage: " + ObjectEchoClient.getClass.getSimpleName + 21 | " []" 22 | ) 23 | return 24 | } 25 | 26 | // Parse options. 27 | val host = args(0) 28 | val port = args(1).toInt 29 | var firstMessageSize: Int = 0 30 | if (args.length == 3) firstMessageSize = args(2).toInt 31 | else firstMessageSize = 256 32 | 33 | // Configure the client. 34 | val bootstrap = new ClientBootstrap( 35 | new NioClientSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool) 36 | ) 37 | 38 | // Set up the pipeline factory. 39 | bootstrap.setPipelineFactory(new ChannelPipelineFactory { 40 | override def getPipeline: ChannelPipeline = 41 | Channels.pipeline( 42 | new ObjectEncoder, 43 | //original Java code still use deprecated API 44 | new ObjectDecoder(ClassResolvers.weakCachingConcurrentResolver(null)), 45 | new ObjectEchoClientHandler(firstMessageSize) 46 | ) 47 | }) 48 | 49 | // Start the connection attempt. 50 | bootstrap.connect(new InetSocketAddress(host, port)) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/objectecho/ObjectEchoClientHandler.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.objectecho 2 | 3 | import java.util 4 | import java.util.concurrent.atomic.AtomicLong 5 | import java.util.logging.Logger 6 | 7 | import org.jboss.netty.channel._ 8 | 9 | /** 10 | * Handler implementation for the object echo client. It initiates the 11 | * ping-pong traffic between the object echo client and server by sending the 12 | * first message to the server. 13 | */ 14 | class ObjectEchoClientHandler(firstMessageSize: Int) extends SimpleChannelUpstreamHandler { 15 | 16 | require(firstMessageSize > 0) 17 | 18 | private val logger = Logger.getLogger(getClass.getName) 19 | 20 | private val transferredMessages = new AtomicLong 21 | 22 | private val firstMessage = new util.ArrayList[java.lang.Integer](firstMessageSize) 23 | val range: Range = 0.until(firstMessageSize) 24 | for (i <- range) { firstMessage.add(i) } 25 | 26 | def getTransferredBytes: Long = transferredMessages.get 27 | 28 | override def handleUpstream(ctx: ChannelHandlerContext, e: ChannelEvent): Unit = { 29 | e match { 30 | case c: ChannelStateEvent => if (c.getState != ChannelState.INTEREST_OPS) logger.info(e.toString) 31 | case _ => None 32 | } 33 | super.handleUpstream(ctx, e) 34 | } 35 | 36 | override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { 37 | // Send the first message if this handler is a client-side handler. 38 | e.getChannel.write(firstMessage) 39 | } 40 | 41 | override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { 42 | // Echo back the received object to the client. 43 | transferredMessages.incrementAndGet 44 | e.getChannel.write(e.getMessage) 45 | } 46 | 47 | override def exceptionCaught(context: ChannelHandlerContext, e: ExceptionEvent): Unit = { 48 | // Close the connection when an exception is raised. 49 | logger.warning("Unexpected exception from downstream." + e.getCause) 50 | e.getChannel.close() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/objectecho/ObjectEchoServer.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.objectecho 2 | 3 | import java.net.InetSocketAddress 4 | import java.util.concurrent.Executors 5 | import org.jboss.netty.bootstrap.ServerBootstrap 6 | import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory 7 | import org.jboss.netty.channel.{ChannelPipeline, ChannelPipelineFactory, Channels} 8 | import org.jboss.netty.handler.codec.serialization.{ObjectDecoder, ObjectEncoder} 9 | import org.jboss.netty.handler.codec.serialization.ClassResolvers 10 | 11 | /** 12 | * Modification of EchoServer which utilizes Java object serialization. 13 | */ 14 | object ObjectEchoServer { 15 | 16 | def main(args: Array[String]): Unit = { 17 | // Configure the server. 18 | val bootstrap = new ServerBootstrap( 19 | new NioServerSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool) 20 | ) 21 | 22 | // Configure the pipeline factory. 23 | bootstrap.setPipelineFactory(new ChannelPipelineFactory { 24 | override def getPipeline: ChannelPipeline = 25 | Channels.pipeline( 26 | new ObjectEncoder, 27 | //original Java code still use deprecated API 28 | new ObjectDecoder(ClassResolvers.weakCachingConcurrentResolver(null)), 29 | new ObjectEchoServerHandler 30 | ) 31 | }) 32 | // Bind and start to accept incoming connections. 33 | bootstrap.bind(new InetSocketAddress(8080)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/objectecho/ObjectEchoServerHandler.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.objectecho 2 | 3 | import java.util.concurrent.atomic.AtomicLong 4 | import java.util.logging.Logger 5 | 6 | import org.jboss.netty.channel._ 7 | 8 | /** 9 | * Handles both client-side and server-side handler depending on which 10 | * constructor was called. 11 | */ 12 | class ObjectEchoServerHandler extends SimpleChannelUpstreamHandler { 13 | 14 | val logger: Logger = Logger.getLogger(getClass.getName) 15 | 16 | private val transferredMessages = new AtomicLong 17 | 18 | def getTransferredMessages: Long = transferredMessages.get 19 | 20 | override def handleUpstream(ctx: ChannelHandlerContext, e: ChannelEvent): Unit = { 21 | e match { 22 | case c: ChannelStateEvent => if (c.getState != ChannelState.INTEREST_OPS) logger.info(e.toString) 23 | case _ => None 24 | } 25 | super.handleUpstream(ctx, e) 26 | } 27 | 28 | override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { 29 | // Echo back the received object to the client. 30 | transferredMessages.incrementAndGet 31 | e.getChannel.write(e.getMessage) 32 | } 33 | 34 | override def exceptionCaught(context: ChannelHandlerContext, e: ExceptionEvent): Unit = { 35 | // Close the connection when an exception is raised. 36 | logger.warning("Unexpected exception from downstream." + e.getCause) 37 | e.getChannel.close() 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/proxy/HexDumpProxy.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.proxy 2 | 3 | import java.net.InetSocketAddress 4 | import java.util.concurrent.{Executor, Executors} 5 | import org.jboss.netty.bootstrap.ServerBootstrap 6 | import org.jboss.netty.channel.socket.ClientSocketChannelFactory 7 | import org.jboss.netty.channel.socket.nio.{NioClientSocketChannelFactory, NioServerSocketChannelFactory} 8 | 9 | object HexDumpProxy { 10 | 11 | def main(args: Array[String]): Unit = { 12 | // Validate command line options. 13 | if (args.length != 3) { 14 | System.err.println( 15 | "Usage: " + HexDumpProxy.getClass.getSimpleName + 16 | " " 17 | ) 18 | return 19 | } 20 | 21 | // Parse command line options. 22 | val localPort = args(0).toInt 23 | val remoteHost = args(1) 24 | val remotePort = args(2).toInt 25 | 26 | System.err.println( 27 | "Proxying *:" + localPort + " to " + 28 | remoteHost + ':' + remotePort + " ..." 29 | ) 30 | 31 | // Configure the bootstrap. 32 | val executor: Executor = Executors.newCachedThreadPool 33 | val sb = new ServerBootstrap(new NioServerSocketChannelFactory(executor, executor)) 34 | 35 | // Set up the event pipeline factory. 36 | val cf: ClientSocketChannelFactory = new NioClientSocketChannelFactory(executor, executor) 37 | 38 | sb.setPipelineFactory(new HexDumpProxyPipelineFactory(cf, remoteHost, remotePort)) 39 | 40 | // Start up the server. 41 | sb.bind(new InetSocketAddress(localPort)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/proxy/HexDumpProxyInboundHandler.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.proxy 2 | 3 | import org.jboss.netty.channel.{ChannelHandlerContext, ExceptionEvent, MessageEvent, SimpleChannelUpstreamHandler} 4 | import org.jboss.netty.channel.socket.ClientSocketChannelFactory 5 | import org.jboss.netty.channel.ChannelStateEvent 6 | import org.jboss.netty.bootstrap.ClientBootstrap 7 | import java.net.InetSocketAddress 8 | import org.jboss.netty.channel.ChannelFutureListener 9 | import org.jboss.netty.channel.ChannelFuture 10 | import org.jboss.netty.buffer.{ChannelBuffer, ChannelBuffers} 11 | import org.jboss.netty.channel.Channel 12 | 13 | class HexDumpProxyInboundHandler(cf: ClientSocketChannelFactory, remoteHost: String, remotePort: Int) 14 | extends SimpleChannelUpstreamHandler { 15 | 16 | // This lock guards against the race condition that overrides the 17 | // OP_READ flag incorrectly. 18 | // See the related discussion: http://markmail.org/message/x7jc6mqx6ripynqf 19 | val trafficLock = new Object 20 | 21 | @volatile 22 | private var outboundChannel: Channel = _ 23 | 24 | override def channelOpen(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { 25 | // Suspend incoming traffic until connected to the remote host. 26 | val inboundChannel = e.getChannel 27 | inboundChannel.setReadable(false) 28 | 29 | // Start the connection attempt. 30 | val cb = new ClientBootstrap(cf) 31 | cb.getPipeline.addLast("handler", new OutboundHandler(e.getChannel)) 32 | val f = cb.connect(new InetSocketAddress(remoteHost, remotePort)) 33 | 34 | outboundChannel = f.getChannel 35 | f.addListener((future: ChannelFuture) => { 36 | if (future.isSuccess) { 37 | // Connection attempt succeeded: 38 | // Begin to accept incoming traffic. 39 | inboundChannel.setReadable(true) 40 | } else { 41 | // Close the connection if the connection attempt has failed. 42 | inboundChannel.close() 43 | } 44 | }) 45 | } 46 | 47 | override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { 48 | val msg = e.getMessage.asInstanceOf[ChannelBuffer] 49 | //System.out.println(">>> " + ChannelBuffers.hexDump(msg)); 50 | trafficLock.synchronized { 51 | outboundChannel.write(msg) 52 | // If outboundChannel is saturated, do not read until notified in 53 | // OutboundHandler.channelInterestChanged(). 54 | if (!outboundChannel.isWritable) { 55 | e.getChannel.setReadable(false) 56 | } 57 | } 58 | } 59 | 60 | override def channelInterestChanged(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { 61 | // If inboundChannel is not saturated anymore, continue accepting 62 | // the incoming traffic from the outboundChannel. 63 | trafficLock.synchronized { 64 | if (e.getChannel.isWritable) { 65 | if (outboundChannel != null) { 66 | outboundChannel.setReadable(true) 67 | } 68 | } 69 | } 70 | } 71 | 72 | override def channelClosed(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { 73 | if (outboundChannel != null) { 74 | closeOnFlush(outboundChannel) 75 | } 76 | } 77 | 78 | override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent): Unit = { 79 | e.getCause.printStackTrace() 80 | closeOnFlush(e.getChannel) 81 | } 82 | 83 | private class OutboundHandler(inboundChannel: Channel) extends SimpleChannelUpstreamHandler { 84 | 85 | override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { 86 | val msg = e.getMessage.asInstanceOf[ChannelBuffer] 87 | //System.out.println("<<< " + ChannelBuffers.hexDump(msg)) 88 | trafficLock.synchronized { 89 | inboundChannel.write(msg) 90 | // If inboundChannel is saturated, do not read until notified in 91 | // HexDumpProxyInboundHandler.channelInterestChanged(). 92 | if (!inboundChannel.isWritable) { 93 | e.getChannel.setReadable(false) 94 | } 95 | } 96 | } 97 | 98 | override def channelInterestChanged(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { 99 | // If outboundChannel is not saturated anymore, continue accepting 100 | // the incoming traffic from the inboundChannel. 101 | trafficLock.synchronized { 102 | if (e.getChannel.isWritable) { 103 | inboundChannel.setReadable(true) 104 | } 105 | } 106 | } 107 | 108 | override def channelClosed(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { 109 | closeOnFlush(inboundChannel) 110 | } 111 | 112 | override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent): Unit = { 113 | e.getCause.printStackTrace() 114 | closeOnFlush(e.getChannel) 115 | } 116 | } 117 | 118 | /** 119 | * Closes the specified channel after all queued write requests are flushed. 120 | */ 121 | def closeOnFlush(ch: Channel): Unit = { 122 | if (ch.isConnected) { 123 | ch.write(ChannelBuffers.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE) 124 | } 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/proxy/HexDumpProxyPipelineFactory.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.proxy 2 | 3 | import org.jboss.netty.channel.ChannelPipeline 4 | import org.jboss.netty.channel.ChannelPipelineFactory 5 | import org.jboss.netty.channel.Channels.pipeline 6 | import org.jboss.netty.channel.socket.ClientSocketChannelFactory 7 | import org.jboss.netty.handler.logging.LoggingHandler 8 | import org.jboss.netty.logging.InternalLogLevel 9 | 10 | class HexDumpProxyPipelineFactory(cf: ClientSocketChannelFactory, remoteHost: String, remotePort: Int) 11 | extends ChannelPipelineFactory { 12 | 13 | override def getPipeline: ChannelPipeline = { 14 | val p = pipeline 15 | p.addLast("logger", new LoggingHandler(InternalLogLevel.INFO)) 16 | p.addLast("handler", new HexDumpProxyInboundHandler(cf, remoteHost, remotePort)) 17 | p 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/qotm/QuoteOfTheMomentClient.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.qotm 2 | 3 | import java.net.InetSocketAddress 4 | import java.util.concurrent.Executors 5 | import org.jboss.netty.bootstrap.ConnectionlessBootstrap 6 | import org.jboss.netty.channel.socket.nio.NioDatagramChannelFactory 7 | import org.jboss.netty.channel.{ 8 | ChannelPipeline, 9 | ChannelPipelineFactory, 10 | Channels, 11 | FixedReceiveBufferSizePredictorFactory 12 | } 13 | import org.jboss.netty.handler.codec.string.{StringDecoder, StringEncoder} 14 | import org.jboss.netty.util.CharsetUtil 15 | import org.jboss.netty.channel.socket.DatagramChannel 16 | 17 | /** 18 | * A UDP broadcast client that asks for a quote of the moment (QOTM) to QuoteOfTheMomentServer 19 | */ 20 | object QuoteOfTheMomentClient { 21 | 22 | def main(args: Array[String]): Unit = { 23 | val f = new NioDatagramChannelFactory(Executors.newCachedThreadPool) 24 | 25 | val b = new ConnectionlessBootstrap(f) 26 | 27 | // Configure the pipeline factory. 28 | b.setPipelineFactory(new ChannelPipelineFactory { 29 | override def getPipeline: ChannelPipeline = 30 | Channels.pipeline( 31 | new StringEncoder(CharsetUtil.ISO_8859_1), 32 | new StringDecoder(CharsetUtil.ISO_8859_1), 33 | new QuoteOfTheMomentClientHandler 34 | ) 35 | }) 36 | 37 | // Enable broadcast 38 | b.setOption("broadcast", "true") 39 | 40 | // Allow packets as large as up to 1024 bytes (default is 768). 41 | // You could increase or decrease this value to avoid truncated packets 42 | // or to improve memory footprint respectively. 43 | // 44 | // Please also note that a large UDP packet might be truncated or 45 | // dropped by your router no matter how you configured this option. 46 | // In UDP, a packet is truncated or dropped if it is larger than a 47 | // certain size, depending on router configuration. IPv4 routers 48 | // truncate and IPv6 routers drop a large packet. That's why it is 49 | // safe to send small packets in UDP. 50 | b.setOption("receiveBufferSizePredictorFactory", new FixedReceiveBufferSizePredictorFactory(1024)) 51 | 52 | val c: DatagramChannel = b.bind(new InetSocketAddress(0)).asInstanceOf[DatagramChannel] 53 | 54 | // Broadcast the QOTM request to port 8080. 55 | c.write("QOTM?", new InetSocketAddress("255.255.255.255", 8080)) 56 | 57 | // QuoteOfTheMomentClientHandler will close the DatagramChannel when a 58 | // response is received. If the channel is not closed within 5 seconds, 59 | // print an error message and quit. 60 | if (!c.getCloseFuture.awaitUninterruptibly(5000)) { 61 | System.err.println("QOTM request timed out.") 62 | c.close().awaitUninterruptibly 63 | } 64 | 65 | f.releaseExternalResources() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/qotm/QuoteOfTheMomentClientHandler.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.qotm 2 | 3 | import org.jboss.netty.channel.{ChannelHandlerContext, ExceptionEvent, MessageEvent, SimpleChannelUpstreamHandler} 4 | 5 | /** 6 | * Handles a client-side channel. 7 | */ 8 | class QuoteOfTheMomentClientHandler extends SimpleChannelUpstreamHandler { 9 | 10 | override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { 11 | val msg = e.getMessage.toString 12 | if (msg.startsWith("QOTM: ")) { 13 | System.out.println("Quote of the Moment: " + msg.substring(6)) 14 | e.getChannel.close() 15 | } 16 | } 17 | 18 | override def exceptionCaught(context: ChannelHandlerContext, e: ExceptionEvent): Unit = { 19 | e.getCause.printStackTrace() 20 | e.getChannel.close() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/qotm/QuoteOfTheMomentServer.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.qotm 2 | 3 | import java.net.InetSocketAddress 4 | import java.util.concurrent.Executors 5 | import org.jboss.netty.bootstrap.ConnectionlessBootstrap 6 | import org.jboss.netty.channel.socket.nio.NioDatagramChannelFactory 7 | import org.jboss.netty.channel.{ 8 | ChannelPipeline, 9 | ChannelPipelineFactory, 10 | Channels, 11 | FixedReceiveBufferSizePredictorFactory 12 | } 13 | import org.jboss.netty.handler.codec.string.{StringDecoder, StringEncoder} 14 | import org.jboss.netty.util.CharsetUtil 15 | 16 | /** 17 | * A UDP server that responds to the QOTM (quote of the moment) request to a QuoteOfTheMomentClient. 18 | */ 19 | object QuoteOfTheMomentServer { 20 | 21 | def main(args: Array[String]): Unit = { 22 | val f = new NioDatagramChannelFactory(Executors.newCachedThreadPool) 23 | 24 | val b = new ConnectionlessBootstrap(f) 25 | 26 | // Configure the pipeline factory. 27 | b.setPipelineFactory(new ChannelPipelineFactory { 28 | override def getPipeline: ChannelPipeline = 29 | Channels.pipeline( 30 | new StringEncoder(CharsetUtil.ISO_8859_1), 31 | new StringDecoder(CharsetUtil.ISO_8859_1), 32 | new QuoteOfTheMomentServerHandler 33 | ) 34 | 35 | }) 36 | // Server doesn't need to enable broadcast to listen to a broadcast. 37 | b.setOption("broadcast", "false") 38 | 39 | // Allow packets as large as up to 1024 bytes (default is 768). 40 | // You could increase or decrease this value to avoid truncated packets 41 | // or to improve memory footprint respectively. 42 | // 43 | // Please also note that a large UDP packet might be truncated or 44 | // dropped by your router no matter how you configured this option. 45 | // In UDP, a packet is truncated or dropped if it is larger than a 46 | // certain size, depending on router configuration. IPv4 routers 47 | // truncate and IPv6 routers drop a large packet. That's why it is 48 | // safe to send small packets in UDP. 49 | b.setOption("receiveBufferSizePredictorFactory", new FixedReceiveBufferSizePredictorFactory(1024)) 50 | 51 | // Bind to the port and start the service. 52 | b.bind(new InetSocketAddress(8080)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/qotm/QuoteOfTheMomentServerHandler.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.qotm 2 | 3 | import java.util.Random 4 | import org.jboss.netty.channel.{ChannelHandlerContext, ExceptionEvent, MessageEvent, SimpleChannelUpstreamHandler} 5 | 6 | /** 7 | * Handles a server-side channel. 8 | */ 9 | class QuoteOfTheMomentServerHandler extends SimpleChannelUpstreamHandler { 10 | 11 | private val random = new Random 12 | 13 | private val quotes = Array( 14 | "Where there is love there is life.", 15 | "First they ignore you, then they laugh at you, then they fight you, then you win.", 16 | "Be the change you want to see in the world.", 17 | "The weak can never forgive. Forgiveness is the attribute of the strong." 18 | ) 19 | 20 | def nextQuote(): String = { 21 | var quoteId: Int = 0 22 | synchronized { 23 | quoteId = random.nextInt(quotes.length) 24 | } 25 | quotes(quoteId) 26 | } 27 | 28 | override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { 29 | val msg = e.getMessage.toString 30 | if ("QOTM?".equals(msg)) e.getChannel.write("QOTM: " + nextQuote, e.getRemoteAddress) 31 | } 32 | 33 | override def exceptionCaught(context: ChannelHandlerContext, e: ExceptionEvent): Unit = { 34 | e.getCause.printStackTrace() 35 | // We don't close the channel because we can keep serving requests. 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/telnet/TelnetClient.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.telnet 2 | 3 | import java.io.BufferedReader 4 | import java.io.InputStreamReader 5 | import java.net.InetSocketAddress 6 | import java.util.concurrent.Executors 7 | 8 | import scala.util.control.Breaks.break 9 | import scala.util.control.Breaks.breakable 10 | 11 | import org.jboss.netty.bootstrap.ClientBootstrap 12 | import org.jboss.netty.channel.ChannelFuture 13 | import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory 14 | 15 | /** 16 | * Simplistic telnet client. 17 | */ 18 | object TelnetClient { 19 | def main(args: Array[String]): Unit = { 20 | // Print usage if no argument is specified. 21 | if (args.length != 2) { 22 | System.err.println( 23 | "Usage: " + TelnetClient.getClass.getSimpleName + 24 | " " 25 | ) 26 | return 27 | } 28 | 29 | // Parse options. 30 | val host = args(0) 31 | val port = args(1).toInt 32 | 33 | // Configure the client. 34 | val bootstrap = new ClientBootstrap( 35 | new NioClientSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool) 36 | ) 37 | 38 | // Set up the pipeline factory. 39 | bootstrap.setPipelineFactory(new TelnetClientPipelineFactory) 40 | 41 | // Start the connection attempt. 42 | val future: ChannelFuture = bootstrap.connect(new InetSocketAddress(host, port)) 43 | 44 | // Wait until the connection attempt succeeds or fails. 45 | val channel = future.awaitUninterruptibly.getChannel 46 | if (!future.isSuccess) { 47 | future.getCause.printStackTrace() 48 | bootstrap.releaseExternalResources() 49 | return 50 | } 51 | 52 | // Read commands from the stdin. 53 | var lastWriteFuture: ChannelFuture = null 54 | val in = new BufferedReader(new InputStreamReader(System.in)) 55 | //TODO rewrite the loop as a recursive function. (Refer to Programming in Scala, 7.6 Living without break and continue) 56 | breakable { 57 | while (true) { 58 | val line = in.readLine 59 | if (line == null) break 60 | 61 | // Sends the received line to the server. 62 | lastWriteFuture = channel.write(line + "\r\n") 63 | 64 | // If user typed the 'bye' command, wait until the server closes 65 | // the connection. 66 | if ("bye".equals(line.toLowerCase())) { 67 | channel.getCloseFuture.awaitUninterruptibly 68 | break 69 | } 70 | } 71 | } 72 | 73 | // Wait until all messages are flushed before closing the channel. 74 | if (lastWriteFuture != null) lastWriteFuture.awaitUninterruptibly 75 | 76 | // Close the connection. Make sure the close operation ends because 77 | // all I/O operations are asynchronous in Netty. 78 | channel.close().awaitUninterruptibly 79 | 80 | // Shut down all thread pools to exit. 81 | bootstrap.releaseExternalResources() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/telnet/TelnetClientHandler.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.telnet 2 | 3 | import java.util.logging.Logger 4 | 5 | import org.jboss.netty.channel._ 6 | 7 | /** 8 | * Handles a client-side channel. 9 | */ 10 | class TelnetClientHandler extends SimpleChannelUpstreamHandler { 11 | 12 | private val logger = Logger.getLogger(getClass.getName) 13 | 14 | override def handleUpstream(ctx: ChannelHandlerContext, e: ChannelEvent): Unit = { 15 | e match { 16 | case _: ChannelStateEvent => logger.info(e.toString) 17 | case _ => None 18 | } 19 | super.handleUpstream(ctx, e) 20 | } 21 | 22 | override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { 23 | System.err.println(e.getMessage) 24 | } 25 | 26 | override def exceptionCaught(context: ChannelHandlerContext, e: ExceptionEvent): Unit = { 27 | // Close the connection when an exception is raised. 28 | logger.warning("Unexpected exception from downstream." + e.getCause) 29 | e.getChannel.close() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/telnet/TelnetClientPipelineFactory.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.telnet 2 | import org.jboss.netty.channel.{ChannelPipeline, ChannelPipelineFactory} 3 | import org.jboss.netty.handler.codec.frame.{DelimiterBasedFrameDecoder, Delimiters} 4 | import org.jboss.netty.handler.codec.string.{StringDecoder, StringEncoder} 5 | 6 | /** 7 | * Simplistic telnet client. 8 | */ 9 | class TelnetClientPipelineFactory extends ChannelPipelineFactory { 10 | 11 | override def getPipeline: ChannelPipeline = { 12 | // Create a default pipeline implementation. 13 | val pipeline = org.jboss.netty.channel.Channels.pipeline 14 | 15 | // Add the text line codec combination first, 16 | pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter: _*)) 17 | pipeline.addLast("decoder", new StringDecoder) 18 | pipeline.addLast("encoder", new StringEncoder) 19 | 20 | // and then business logic. 21 | pipeline.addLast("handler", new TelnetClientHandler) 22 | 23 | pipeline 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/telnet/TelnetServer.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.telnet 2 | 3 | import java.net.InetSocketAddress 4 | import java.util.concurrent.Executors 5 | import org.jboss.netty.bootstrap.ServerBootstrap 6 | import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory 7 | 8 | /** 9 | * Simplistic telnet server. 10 | */ 11 | object TelnetServer { 12 | 13 | def main(args: Array[String]): Unit = { 14 | // Configure the server. 15 | val bootstrap = new ServerBootstrap( 16 | new NioServerSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool) 17 | ) 18 | 19 | // Configure the pipeline factory. 20 | bootstrap.setPipelineFactory(new TelnetServerPipelineFactory) 21 | 22 | // Bind and start to accept incoming connections. 23 | bootstrap.bind(new InetSocketAddress(8080)) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/telnet/TelnetServerHandler.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.telnet 2 | 3 | import java.net.InetAddress 4 | import java.util.Date 5 | import java.util.logging.Logger 6 | 7 | import org.jboss.netty.channel._ 8 | 9 | /** 10 | * Handles a server-side channel. 11 | */ 12 | class TelnetServerHandler extends SimpleChannelUpstreamHandler { 13 | 14 | private val logger = Logger.getLogger(getClass.getName) 15 | 16 | override def handleUpstream(ctx: ChannelHandlerContext, e: ChannelEvent): Unit = { 17 | e match { 18 | case _: ChannelStateEvent => logger.info(e.toString) 19 | case _ => 20 | } 21 | super.handleUpstream(ctx, e) 22 | } 23 | 24 | override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { 25 | // Send greeting for a new connection. 26 | e.getChannel.write("Welcome to " + InetAddress.getLocalHost.getHostName + "!\r\n") 27 | e.getChannel.write("It is " + new Date + " now.\r\n") 28 | } 29 | 30 | override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { 31 | 32 | // Cast to a String first. 33 | // We know it is a String because we put some codec in TelnetPipelineFactory. 34 | val request = e.getMessage.toString 35 | 36 | // Generate and write a response. 37 | var response: String = "" 38 | var close: Boolean = false 39 | if (request.length == 0) { 40 | response = "Please type something.\r\n" 41 | } else if ("bye".equals(request.toLowerCase())) { 42 | response = "Have a good day!\r\n" 43 | close = true 44 | } else { 45 | response = "Did you say '" + request + "'?\r\n" 46 | } 47 | 48 | // We do not need to write a ChannelBuffer here. 49 | // We know the encoder inserted at TelnetPipelineFactory will do the conversion. 50 | val future = e.getChannel.write(response) 51 | 52 | // Close the connection after sending 'Have a good day!' 53 | // if the client has sent 'bye'. 54 | if (close) { 55 | future.addListener(ChannelFutureListener.CLOSE) 56 | } 57 | } 58 | 59 | override def exceptionCaught(context: ChannelHandlerContext, e: ExceptionEvent): Unit = { 60 | // Close the connection when an exception is raised. 61 | logger.warning("Unexpected exception from downstream." + e.getCause) 62 | e.getChannel.close() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/telnet/TelnetServerPipelineFactory.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.telnet 2 | import org.jboss.netty.channel.{ChannelPipeline, ChannelPipelineFactory} 3 | import org.jboss.netty.handler.codec.frame.{DelimiterBasedFrameDecoder, Delimiters} 4 | import org.jboss.netty.handler.codec.string.{StringDecoder, StringEncoder} 5 | 6 | /** 7 | * Creates a newly configured ChannelPipeline for a new channel. 8 | */ 9 | class TelnetServerPipelineFactory extends ChannelPipelineFactory { 10 | 11 | override def getPipeline: ChannelPipeline = { 12 | // Create a default pipeline implementation. 13 | val pipeline = org.jboss.netty.channel.Channels.pipeline 14 | 15 | // Add the text line codec combination first, 16 | pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter: _*)) 17 | pipeline.addLast("decoder", new StringDecoder) 18 | pipeline.addLast("encoder", new StringEncoder) 19 | 20 | // and then business logic. 21 | pipeline.addLast("handler", new TelnetServerHandler) 22 | 23 | pipeline 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/uptime/UptimeClient.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.uptime 2 | 3 | import java.net.InetSocketAddress 4 | import java.util.concurrent.Executors 5 | 6 | import org.jboss.netty.bootstrap.ClientBootstrap 7 | import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory 8 | import org.jboss.netty.channel.{ChannelPipeline, ChannelPipelineFactory, Channels} 9 | import org.jboss.netty.handler.timeout.ReadTimeoutHandler 10 | import org.jboss.netty.util.{HashedWheelTimer, Timer} 11 | 12 | /** 13 | * Connects to a server periodically to measure and print the uptime of the 14 | * server. This example demonstrates how to implement reliable reconnection 15 | * mechanism in Netty. 16 | */ 17 | object UptimeClient { 18 | 19 | // Sleep 5 seconds before a reconnection attempt. 20 | val RECONNECT_DELAY = 5 21 | 22 | // Reconnect when the server sends nothing for 10 seconds. 23 | private val READ_TIMEOUT = 10 24 | 25 | def main(args: Array[String]): Unit = { 26 | // Print usage if no argument is specified. 27 | if (args.length != 2) { 28 | System.err.println( 29 | "Usage: " + UptimeClient.getClass.getSimpleName + 30 | " " 31 | ) 32 | return 33 | } 34 | 35 | // Parse options. 36 | val host = args(0) 37 | val port = args(1).toInt 38 | 39 | // Initialize the timer that schedules subsequent reconnection attempts. 40 | val timer: Timer = new HashedWheelTimer 41 | 42 | // Configure the client. 43 | val bootstrap = new ClientBootstrap( 44 | new NioClientSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool) 45 | ) 46 | 47 | // Set up the pipeline factory. 48 | bootstrap.setPipelineFactory(new ChannelPipelineFactory { 49 | 50 | private val timeoutHandler = new ReadTimeoutHandler(timer, READ_TIMEOUT) 51 | private val uptimeHandler = new UptimeClientHandler(bootstrap, timer) 52 | 53 | //@throws(classOf[java.lang.Exception]) 54 | override def getPipeline: ChannelPipeline = Channels.pipeline(timeoutHandler, uptimeHandler) 55 | }) 56 | 57 | bootstrap.setOption("remoteAddress", new InetSocketAddress(host, port)) 58 | 59 | // Initiate the first connection attempt - the rest is handled by 60 | // UptimeClientHandler. 61 | bootstrap.connect 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/scala/org/edla/netty/example/uptime/UptimeClientHandler.scala: -------------------------------------------------------------------------------- 1 | package org.edla.netty.example.uptime 2 | 3 | import java.net.{ConnectException, InetSocketAddress} 4 | import java.util.concurrent.TimeUnit 5 | 6 | import org.jboss.netty.bootstrap.ClientBootstrap 7 | import org.jboss.netty.channel.{ChannelHandlerContext, ChannelStateEvent, ExceptionEvent, SimpleChannelUpstreamHandler} 8 | import org.jboss.netty.handler.timeout.ReadTimeoutException 9 | import org.jboss.netty.util.{Timeout, Timer} 10 | 11 | /** 12 | * Keep reconnecting to the server while printing out the current uptime and 13 | * connection attempt status. 14 | */ 15 | class UptimeClientHandler(bootstrap: ClientBootstrap, timer: Timer) extends SimpleChannelUpstreamHandler { 16 | 17 | var startTime: Long = -1 18 | 19 | def getRemoteAddress: InetSocketAddress = bootstrap.getOption("remoteAddress").asInstanceOf[InetSocketAddress] 20 | 21 | override def channelDisconnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { 22 | println("Disconnected from: " + getRemoteAddress) 23 | } 24 | 25 | override def channelClosed(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { 26 | println("Sleeping for: " + UptimeClient.RECONNECT_DELAY + 's') 27 | timer.newTimeout( 28 | (_: Timeout) => { 29 | println("Reconnecting to: " + getRemoteAddress) 30 | bootstrap.connect 31 | }, 32 | UptimeClient.RECONNECT_DELAY, 33 | TimeUnit.SECONDS 34 | ) 35 | } 36 | 37 | override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { 38 | if (startTime < 0) startTime = System.currentTimeMillis 39 | println("Connected to: " + getRemoteAddress) 40 | } 41 | 42 | override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent): Unit = { 43 | val cause = e.getCause 44 | cause match { 45 | case _: ConnectException => 46 | startTime = -1 47 | println("Failed to connect: " + cause.getMessage) 48 | // The connection was OK but there was no traffic for last period. 49 | case _: ReadTimeoutException => println("Disconnecting due to no inbound traffic") 50 | case _ => cause.printStackTrace() 51 | } 52 | ctx.getChannel.close() 53 | } 54 | 55 | def println(msg: String): Unit = { 56 | if (startTime < 0) System.err.format("[SERVER IS DOWN] %s%n", msg) 57 | else System.err.format("[UPTIME: %s] %s%n", ((System.currentTimeMillis - startTime) / 1000).toString, msg) 58 | } 59 | } 60 | --------------------------------------------------------------------------------