最近在读李林峰写的<<Netty权威指南(第2版)>>,所以本系列有部分内容是参考书籍来写的,感谢作者提供的学习资料。
----------------------------------------------------------------------华丽的分割线----------------------------------------------------------------------------
IDE:eclipse-jee-indigo-SR2-win32-x86_64
JDK:JDK1.7
官网地址:http://netty.io/,从【Downloads】选择下载netty-all-4.1.1.Final-sources。如图:
创建一个java工程,将netty-all-4.1.1.Final.jar包导入到项目中
package com.lll.game; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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 HttpServer { private static Log log = LogFactory.getLog(HttpServer.class); public static void main(String[] args) throws Exception { HttpServer server = new HttpServer(); log.info("服务已启动..."); server.start(8844); } public void start(int port) throws Exception { //配置服务端的NIO线程组 EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ServerHandler()); } }).option(ChannelOption.SO_BACKLOG, 128) //最大客户端连接数为128 .childOption(ChannelOption.SO_KEEPALIVE, true); //绑定端口,同步等待成功 ChannelFuture f = b.bind(port).sync(); //等待服务端监听端口关闭 f.channel().closeFuture().sync(); } finally { //优雅退出,释放线程池资源 workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } }
5.x代码
package com.lll.game; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; public class ServerHandler extends ChannelHandlerAdapter{ @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { super.handlerAdded(ctx); System.out.println(ctx.channel().id()+"进来了"); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { super.handlerRemoved(ctx); System.out.println(ctx.channel().id()+"离开了"); } }
ServerHandler继承于ChannelHandlerAdapter,而ChannelHandlerAdapter实现了ChannelHandler接口,在ChannelHandler里申明了2个生命周期的监听方法,你可以完成任意初始化任务只要他不会被阻塞很长的时间。代码如下:
/* * Copyright 2012 The Netty Project * / public interface ChannelHandler { /** * Gets called after the {@link ChannelHandler} was added to the actual context and it's ready to handle events. */ void handlerAdded(ChannelHandlerContext ctx) throws Exception; /** * Gets called after the {@link ChannelHandler} was removed from the actual context and it doesn't handle events * anymore. */ void handlerRemoved(ChannelHandlerContext ctx) throws Exception; /** * Gets called if a {@link Throwable} was thrown. * * @deprecated is part of {@link ChannelInboundHandler} */ @Deprecated void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception; /** * Indicates that the same instance of the annotated {@link ChannelHandler} * can be added to one or more {@link ChannelPipeline}s multiple times * without a race condition. * <p> * If this annotation is not specified, you have to create a new handler * instance every time you add it to a pipeline because it has unshared * state such as member variables. * <p> * This annotation is provided for documentation purpose, just like * <a href="http://www.javaconcurrencyinpractice.com/annotations/doc/">the JCIP annotations</a>. */ @Inherited @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Sharable { // no value } }
package com.game.lll.net; import java.util.Date; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; public class ServerHandler extends ChannelInboundHandlerAdapter{ private static Log log = LogFactory.getLog(ServerHandler.class); @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { super.handlerAdded(ctx); System.out.println(ctx.channel().id()+"进来了"); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { super.handlerRemoved(ctx); System.out.println(ctx.channel().id()+"离开了"); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf)msg; byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req,"UTF-8"); log.info(req); String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)? new Date(System.currentTimeMillis()).toString():"BAD ORDER"; ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); ctx.write(resp); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // TODO Auto-generated method stub ctx.close(); } }
ServerHandler继承自ChannelInboundHandlerAdapter,它用于对网络事件进行读写操作,通常我们只需要关注channelRead和exceptionCaught方法。下面对这两个方法进行简单说明。
第30行做类型转换,将msg转换成Netty的ByteBuf对象。通过ByteBuf的readableBytes方法可以获取缓冲区可读的字节数,根据可读的字节数创建byte数组,通过ByteBuf的readBytes方法将缓冲区中的字节数组复制到新建的byte数组中,最后通过new String构造函数获取请求信息。这是对请求消息进行判断,如果是“QUERY TIME ORDER”则创建应答信息,通过ChannelHandlerContext的write方法异步发送应答消息给客户端。
第43行,当发生异常时,关闭ChannelHandlerContext,释放和ChannelHandlerContext相关联的句柄等资源。
using System.Net.Sockets; using System.Text; using System.Threading; using UnityEngine; using UnityEngine.UI; using System.Collections.Generic; public class HttpClient : MonoBehaviour { private const string IP = "127.0.0.1"; private const int PORT = 8844; public UILabel userName; public UILabel password; private Socket client; private string msg,ip; public void start() { try { client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); client.Connect(IP, PORT); Debug.Log ("连接服务器成功\r\n"); Thread threadReceive = new Thread(ReceiveMsg); threadReceive.IsBackground = true; threadReceive.Start(); }catch { Debug.Log ("连接服务器失败\r\n"); } } public void Send() { if(client == null) { start (); } byte[] buffer = Encoding.UTF8.GetBytes("userName:"+userName.text+" password"+password.text); client.Send(buffer); } private void ReceiveMsg() { byte[] buffer = new byte[1024 * 1024]; int len = 0; while (true) { len = client.Receive(buffer); //区分是客户端来了,还是消息来了 if (buffer[0] == 1)//客户端 { ip=Encoding.UTF8.GetString(buffer, 1, len - 1); }else//文本消息 { msg = Encoding.UTF8.GetString(buffer, 1, len-1); } } print (msg); } void Update() { if (!string.IsNullOrEmpty(msg)) { Debug.Log ("服务器说:" + msg + "\r\n"); msg = ""; } if (!string.IsNullOrEmpty(ip)) { ip = ""; } } void OnApplicationQuit() { client.Shutdown(SocketShutdown.Both); client.Close(); } }
客户端控制台