大数据学习之路61-Netty的基本使用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37050372/article/details/82454114

之前我们写了Akka的RPC通信,那么为什么Spark底层要这样设计呢?

因为以后我们要玩的是分布式并行计算,我们需要将任务提交到集群上执行。那么这个任务在Master上执行还是在Worker上执行?答案是在Worker上执行,严格的来说是在Worker的子进程上执行,就好比我们之前使用的yarn,yarn上有nodemanager,而任务并不在nodemanager上执行,而是在nodemanager启动的yarnchild上执行。Worker一开始就将自己的资源注册到Master上,然后Master就知道哪些Worker上有资源。然后Master就将计算任务分配到某台或某几台Worker上。

在spark2.0已经将Akka移除,因为Akka的扩展性并不是很好,于是又用Netty重新写了一套。

接下来我们来看看Netty的思想:

什么是NIO,我们之前写的Socket就是传统的IO,传统的IO是有问题的。NIO就是new IO(新IO)它的异步性要比传统IO强大。

接下来我们开始写Netty的RPC:

我们要写NIO的话就要调用Netty的类的方法。我们首先配置服务端的线程池组,为什么Netty的性能高呢?因为它首先就创建线程池,以后这个线程池可以供Socket连接,Socket通信使用。

我们的服务端是有两个线程池的。客户端有一个线程池就够了。

先说服务端,第一个线程池是用来接收客户端的连接的,作为一个服务端,一定会有很多的客户端要和他进行连接。而建立连接是要启动线程的,如果没有线程池,我们还需要自己创建一个线程。有线程池的话,我们只需要从线程池中取一个跟他建立连接就好了。

第二个线程池是服务端与客户端进行Socket通信的时候使用的。用于用户进行SocketChannel的网络读写。

如果让我们自己去写线程池的话,一定会写很多代码,而现在我们只需要使用Netty提供的工具类就行。

接下来我们要new 一个ServerBootstrap它也是一个工具类。它是Netty用户启动NIO服务端的辅助启动类,可以降低服务端的开发复杂度。

这个工具类首先需要传入线程池,然后创建NioServerSocketCannel,要创建的话我们需要传进去一个实现类。这里的classOf相当于.class,传进去之后通过这个类的字节码进行反射。然后再绑定I/O事件处理类。这个I/O事件处理类就可以处理我们的读写请求。这里的pipeline的意思是管道,或者流水线。再这里可以添加很多的处理器,而处理器是按照先后顺序执行的。

那么我们自己写的处理器类只要添加进去Netty就能认识吗?肯定是不行的,他一定要实现一个接口或继承一个类才能被识别。那么我们这里继承的是ChannelInboundHandlerAdapter,继承这个类之后还要重写几个关键的方法。

channelActive:客户端与服务端建立连接之后,这个方法会被调用。

channelRead:客户端如果给服务端发消息,这个方法就会被调用。

channelReadComplete:服务端接收完来自客户端的消息之后,再发回消息给客户端。

exceptionCaught:当出现异常的时候,这个方法会被调用。

那么也就是说,如果我们使用了Netty之后,我们只需要写处理器中的逻辑就行了,其他的都是固定的套路。

前面的操作只是给ServerBootstrap绑定了一些必要的条件,接下来的

bootstrap.bind(host,port).sync()才是真正的启动

我们还需要使用

channelFuture.channel().closeFuture().sync()让服务等待关闭命令。

结束之后我们需要释放进程池资源。

服务端说完了,接下来说客户端:

客户端只有一个线程池,因为她以后只负责和服务端发消息即可。而连接请求交给服务端即可。

客户端的辅助启动类为Bootstrap这个工具类传入线程池之后,需要创建NioSocketChannel,接着同样需要绑定I/O事件处理类。

ctx.writeAndFlush(Unpooled.copiedBuffer(content.getBytes("utf-8")))

这句话是在客户端连接到服务端之后,客户端给服务端发消息用的Unpooled.copiedBuffer将字节数组转换为ByteBuf类型。这里我们不仅可以写字节缓冲,我们还可以写case class

服务端接收到消息之后:在ChannelRead方法中得到的是一个Any类型的消息,我们需要使用asInstanceOf将他强转为ByteBuf

ByteBuf中放的是字节数组,所以我们要想办法将他拿出来。

byteBuf.readableBytes()

这个方法是读出ByteBuf中字节数组的大小。然后我们创建出同样大小的空Byte数组

byteBuf.readBytes(b)

这样就可以将ByteBuf中的字节数组中的值全部交给我们创建的字节数组

转为字符串输出。

接着在这个方法中服务端也可以以同样的方式发送消息给客户端。

channelRead方法执行完之后ChannelReadComplete方法会被调用

至此我们就可以利用Netty实现简单的字符串通信了。通信的时候主机名和端口号保持一致

以下贴出代码:

服务端:

package com.test.server

import com.test.handler.ServerHandler
import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.{ChannelInitializer, nio}
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioServerSocketChannel

class NettyServer {
    def bind(host:String,port:Int): Unit ={
       //配置服务端线程池组
      //用于服务端接收客户端的连接。
      val bossGroup = new NioEventLoopGroup()
      //用户进行SocketChannel的网络读写
      val workerGroup = new nio.NioEventLoopGroup()
         //是netty用户启动NIO服务端的辅助启动类,降低服务端的开发复杂度
      try {
        val bootstrap = new ServerBootstrap()
        //将两个线程池组作为参数传入ServerBootstrap
        bootstrap.group(bossGroup, workerGroup)
          //创建NioServerSocketChannel
          .channel(classOf[NioServerSocketChannel])
          //绑定I/O事件处理类
          .childHandler(new ChannelInitializer[SocketChannel] {
          override def initChannel(ch: SocketChannel): Unit = {
            ch.pipeline().addLast(

              new ServerHandler
            )
          }
        })
        //绑定端口,调用sync方法等待绑定操作完成
        val channelFuture = bootstrap.bind(host, port).sync()
        //等待服务关闭
        channelFuture.channel().closeFuture().sync()
      }finally{
        bossGroup.shutdownGracefully()
        workerGroup.shutdownGracefully()
      }
    }
}

object NettyServer{
  def main(args: Array[String]): Unit = {
          val host = args(0)
          val port = args(1).toInt
          val server = new NettyServer

          server.bind(host,port)
  }
}

服务端的事件处理类:

package com.test.handler

import java.nio.ByteBuffer

import io.netty.buffer.{ByteBuf, Unpooled}
import io.netty.channel.{ChannelHandlerContext, ChannelInboundHandler, ChannelInboundHandlerAdapter}

class ServerHandler extends ChannelInboundHandlerAdapter{
  //客户端与服务端建立连接之后会被调用
  override def channelActive(ctx: ChannelHandlerContext): Unit = {
        println("一个客户端连接上了")
  }
  //客户端给服务端发消息,这个方法就会被调用
  override def channelRead(ctx: ChannelHandlerContext, msg: scala.Any): Unit = {
      println("收到客户端发来的消息了")
      val byteBuf = msg.asInstanceOf[ByteBuf]
    val b = new Array[Byte](byteBuf.readableBytes())
     byteBuf.readBytes(b)
    val str: String = new String(b,"utf-8")
    println(str)
     val back = "在ChannelRead中写回数据给客户端"
      ctx.write(Unpooled.copiedBuffer(back.getBytes()))
  }
  //服务端发回消息给客户端,将消息队列中的数据写入到SocketChannel并发送给对方
  override def channelReadComplete(ctx: ChannelHandlerContext): Unit = {
       println("channelReadComplete invoke")
       ctx.flush()
  }

  override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable): Unit = {
      println("exceptionCaught invoked")
      ctx.close()
  }


}

客户端:

package com.test.client

import com.test.handler.ClientHandler
import com.test.server.NettyServer
import io.netty.bootstrap.Bootstrap
import io.netty.channel.ChannelInitializer
import io.netty.channel.nio.{NioEventLoop, NioEventLoopGroup}
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioSocketChannel

class NettyClient {
   def connect(host:String,port:Int): Unit ={
     //创建客户端NIO线程池
       val eventGroup =  new NioEventLoopGroup()
     //创建客户端辅助启动类
        val bootstrap = new Bootstrap
       try{
         println("进入try")
         bootstrap.group(eventGroup)
           .channel(classOf[NioSocketChannel])
           .handler(new ChannelInitializer[SocketChannel] {
             override def initChannel(ch: SocketChannel): Unit = {
               ch.pipeline().addLast(
                 new ClientHandler
               )
             }
           })
         val channelFuture =  bootstrap.connect(host,port).sync()
         channelFuture.channel().closeFuture().sync()
       }finally {
              eventGroup.shutdownGracefully()
       }
   }
}
object NettyClient{
  def main(args: Array[String]): Unit = {
    val host = args(0)
    val port = args(1).toInt
    val client = new NettyClient

    client.connect(host,port)
  }
}

客户端的事件处理类:

package com.test.handler

import io.netty.buffer.{ByteBuf, Unpooled}
import io.netty.channel.{ChannelHandlerContext, ChannelInboundHandlerAdapter}

class ClientHandler extends ChannelInboundHandlerAdapter{
  //客户端与服务端连接上之后会被调用
  override def channelActive(ctx: ChannelHandlerContext): Unit = {
       println("已经跟服务端连接上了")
    val content = "客户端在ChannelActive发消息给服务端"
       ctx.writeAndFlush(Unpooled.copiedBuffer(content.getBytes("utf-8")))
  }
  //服务端发来消息之后会被调用
  override def channelRead(ctx: ChannelHandlerContext, msg: scala.Any): Unit = {
       println("channelRead")
    val buf: ByteBuf = msg.asInstanceOf[ByteBuf]
    val bytes: Array[Byte] = new Array[Byte](buf.readableBytes())
    buf.readBytes(bytes)
    val str = new String(bytes,"utf-8")
    println(str)
  }

  //客户端回复消息给客户端
  override def channelReadComplete(ctx: ChannelHandlerContext): Unit = {
        println("channelReadComplete")
  }

  override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable): Unit = {

  }
}

猜你喜欢

转载自blog.csdn.net/qq_37050372/article/details/82454114
今日推荐