在上节中我们说到了环境的配置,在配置了 jdk 和 netty 的情况下,我们就可以开始着手写一个简单的 C/S 框架了。由于 Netty 对 Java 原生的 NIO 进行了封装,所以很容易就可以搭建一个 C/S 框架,下面我们不免俗套地来搭建一个回声服务器来初步了解一下 Netty, 为后面的学习做准备。
首先,先上服务端的代码
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; public class DiscardServer { private int port; public DiscardServer(int port) { this.port = port; } EventLoopGroup bossGroup = new NioEventLoopGroup();//用于处理I/O操作的多线程循环器,该条为接收连接 EventLoopGroup workGroup = new NioEventLoopGroup();//用于处理连接,一旦有连接,boss会将连接信息注册到work上 public void run() { try { ServerBootstrap bootstrap = new ServerBootstrap();//启动NIO的辅助启动类 bootstrap.group(bossGroup,workGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() {//用于配置channel @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new DiscardServerHandler());//实现channel的具体配置 } }); //绑定端口,接受进来的连接 ChannelFuture f = bootstrap.bind(port).sync(); //等待服务器关闭,在这个例子中,服务器不会关闭 f.channel().close().sync(); }catch(Exception e) { e.printStackTrace(); } } public void shutdown(){ bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } public static void main(String[] args)throws Exception { int port = 4700; new DiscardServer(port).run(); } }
写到这,相信有将这段代码放到编译器里的朋友会发现在下面一行中会报错
ch.pipeline().addLast(new DiscardServerHandler());
事实也的确如此,因为我们还没有实现这个类,这个类是用于自定义的消息处理的类,接下来就让我们完成这个类
import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.ReferenceCountUtil; public class DiscardServerHandler extends ChannelInboundHandlerAdapter{//继承ChannelInboundHandlerAdapter方法 public void channelRead(ChannelHandlerContext ctx, Object msg) {//重写了channelRead方法,当从客户端收到数据后,就会被自动调用 ByteBuf in = (ByteBuf)msg; try { while(in.isReadable()) {//在服务器控制台打印收到的消息 System.out.println((char)in.readByte()); System.out.flush(); } ctx.writeAndFlush(msg);//收到什么,就给客户端返回什么 }finally { ReferenceCountUtil.release(msg); } } public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {//重写了exceptionCaught方法,处理异常 cause.printStackTrace();ctx.close(); } }
首先对ChannelInboundHandlerAdapter做一些介绍,这个类实现了ChannelInboundHandler的接口,直接继承这个类然后对我们要使用的方法进行重写显然是要比自己继承接口然后实现接口中的所有方法要更加简单。
到这里,一个简单的回声服务器就完成了,这个服务器实现的功能是收到什么就在服务器的控制太打印什么消息并发送给客户端一个同样的消息。基本上,Netty 服务器的框架就是这样,一个类负责启动初始化服务器,一个类来负责对消息进行处理。当然,在应用变得更加复杂的情况下这样肯动是不够用的,但是我们现在暂且认为他就是这样的。
服务端先到这里,接下来让我们实现客户端。完成了服务端之后,客户端就显得很简单了,同服务器一样,Netty 的客户端也需要两个类,一个用于初始化连接,一个用于处理数据。首先还是初始化连接的类
import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; public class Client { public static Properties p; public static int port = 8700; public static String host = "****";//如果为服务器的话改为你的服务器ip //public static String host = "localhost"; private static NioEventLoopGroup workGroup = new NioEventLoopGroup(); private Client() {} public void start() throws Exception { Bootstrap b = new Bootstrap(); b.group(workGroup).channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>(){ protected void initChannel(SocketChannel ch)throws Exception{ ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new ClientHandler());//注意要跟服务器保持一致 } }); Channel ch = b.connect(host,port).sync().channel();//访问固定ip的固定端口 ch.closeFuture().sync(); } public void shut() { workGroup.shutdownGracefully(); } public static void main(String[] args) throws Exception { new Client().start(); } }
接下来依旧ClientHandler类的编写,在回声服务器中,ClientHandler只需要实现一连接就给服务器发送一条消息并将服务端的消息打印出来然后再发送给服务器,这样就可以一直实现客户端服务器的互发消
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; public class ClientHandler extends ChannelInboundHandlerAdapter{ String cmsg = "this is client"; @Override public void channelActive(ChannelHandlerContext ctx) {//一连接就自动调用该函数给服务器发消息 ctx.writeAndFlush(cmsg.getBytes()); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf in = (ByteBuf)msg; try { while(in.isReadable()) {//在服务器控制台打印收到的消息 System.out.println((char)in.readByte()); System.out.flush(); } ctx.writeAndFlush(msg);//收到什么,就给服务端返回什么 }finally { ReferenceCountUtil.release(msg);} } @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } }
到这里,对 Netty 就已经差不多有一掉了解了,从下一节开始,我们开始在回声服务器的基础上开始改写,逐渐形成一个简单的游戏服务器。