Netty多协议开发
HTTP协议开发
post与get的区别
1)get用于信息获取,post用于更新资源。
2)get数据放在请求行中,post数据放在请求体内。
3)get对数据长度有限制(2083字节),post没有限制。
4)post比get安全性高。
Netty Http+Xml协议栈开发
高效的XML绑定JiBx JOPO <=> XML
package com.netty.ch10; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import org.jibx.runtime.BindingDirectory; import org.jibx.runtime.IBindingFactory; import org.jibx.runtime.IMarshallingContext; import org.jibx.runtime.IUnmarshallingContext; import org.jibx.runtime.JiBXException; import com.netty.ch10.httpXML.pojo.Order; import com.netty.ch10.httpXML.pojo.OrderFactory; /** * jiBx类库的使用 * @author Administrator * ANT 脚本生成XML与对象的绑定关系 *1)下载JiBx包 *2)JiBx包 的example下找到一个build.xml 并运行target ==> BindGen 生成 binding.xml 和 pojo.xsd * <target name="bindgen"> <echo message="Running BindGen tool"/> <java classpathref="classpath" fork="true" failonerror="true" classname="org.jibx.binding.generator.BindGen"> <arg value="-s"/> <arg value="${basedir}/src/com/netty/ch10/httpXML/pojo"/> <arg value="com.netty.ch10.httpXML.pojo.Order"/> </java> </target> 3)jibx编译命令 动态修改POJO类 ==> bind <!-- bind as a separate step --> <target name="bind" depends="check-binding"> <echo message="Running JiBX binding compiler"/> <taskdef name="bind" classname="org.jibx.binding.ant.CompileTask"> <classpath> <fileset dir="${jibx-home}/lib" includes="*.jar"/> </classpath> </taskdef> <bind binding="${basedir}/binding.xml"> <classpath refid="classpath"/> </bind> </target> 4)运行测试类 */ public class TestOrder { private IBindingFactory factory = null; private StringWriter write = null; private StringReader reader = null; private final static String CHARSET_NAME = "UTF-8"; /** * pojo to xml * @param order * @return * @throws JiBXException * @throws IOException */ private String encode2Xml(Order order) throws JiBXException, IOException{ factory = BindingDirectory.getFactory(Order.class); write = new StringWriter(); IMarshallingContext mctx = factory.createMarshallingContext(); mctx.setIndent(2); mctx.marshalDocument(order,CHARSET_NAME,null,write); String xmlStr = write.toString(); write.close(); System.out.println(xmlStr); return xmlStr; } private Order decode2Order(String xmlBody) throws JiBXException{ reader = new StringReader(xmlBody); IUnmarshallingContext uctx = factory.createUnmarshallingContext(); Order order = (Order)uctx.unmarshalDocument(reader); return order; } public static void main(String[] args) throws JiBXException, IOException { TestOrder test = new TestOrder(); Order order = OrderFactory.create(); String body = test.encode2Xml(order); Order order2 = test.decode2Order(body); System.out.println(order2); } }
Http请求类
package com.netty.ch10.httpXML.request; import io.netty.handler.codec.http.FullHttpRequest; public class HttpXmlRequest { private FullHttpRequest request; private Object body; /** * @return the body */ public Object getBody() { return body; } /** * @param body the body to set */ public void setBody(Object body) { this.body = body; } public HttpXmlRequest(FullHttpRequest request, Object body) { this.request = request; this.body = body; } public final FullHttpRequest getRequest() { return request; } public final void setRequest(FullHttpRequest request) { this.request = request; } }
Http响应类
package com.netty.ch10.httpXML.response; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; public class HttpXmlResponse { private FullHttpResponse httpResponse; private Object result; public HttpXmlResponse(FullHttpResponse httpResponse, Object result) { super(); this.httpResponse = httpResponse; this.result = result; } public HttpXmlResponse() { super(); // TODO Auto-generated constructor stub } /** * @return the httpResponse */ public FullHttpResponse getHttpResponse() { return httpResponse; } /** * @param httpResponse the httpResponse to set */ public void setHttpResponse(FullHttpResponse httpResponse) { this.httpResponse = httpResponse; } /** * @return the result */ public Object getResult() { return result; } /** * @param result the result to set */ public void setResult(Object result) { this.result = result; } }
Encoder编码基类
package com.netty.ch10.httpXML.common; import java.io.StringWriter; import java.nio.charset.Charset; import java.util.List; import org.jibx.runtime.BindingDirectory; import org.jibx.runtime.IBindingFactory; import org.jibx.runtime.IMarshallingContext; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageCodec; import io.netty.handler.codec.MessageToMessageEncoder; /** * HTTP+XML 请求消息编码基类 * @author Administrator * * @param <T> */ public abstract class AbstractHttpXmlEncoder<T> extends MessageToMessageEncoder<T>{ IBindingFactory factory = null; StringWriter writer = null; final static String CHARSET_NAME ="UTF-8"; final static Charset UTF_8 = Charset.forName(CHARSET_NAME); protected ByteBuf encode0(ChannelHandlerContext ctx,Object body) throws Exception{ factory = BindingDirectory.getFactory(body.getClass()); writer= new StringWriter(); IMarshallingContext mctx = factory.createMarshallingContext(); mctx.setIndent(2); mctx.marshalDocument(body,CHARSET_NAME,null,writer); String xmlStr = writer.toString(); writer.close(); writer = null; ByteBuf encodeBuf = Unpooled.copiedBuffer(xmlStr,UTF_8); return encodeBuf; } /* (non-Javadoc) * @see io.netty.channel.ChannelHandlerAdapter#exceptionCaught(io.netty.channel.ChannelHandlerContext, java.lang.Throwable) */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // TODO Auto-generated method stub super.exceptionCaught(ctx, cause); if(writer != null){ writer.close(); writer = null; } } }
HTTP请求编码类
package com.netty.ch10.httpXML.request; import java.net.InetAddress; import java.util.List; import com.netty.ch10.httpXML.common.AbstractHttpXmlEncoder; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpHeaders; //import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpVersion; /** * HTTP+XML Http 请求消息编码类 * @author Administrator * */ public class HttpXmlRequestEncoder extends AbstractHttpXmlEncoder<HttpXmlRequest>{ @Override protected void encode(ChannelHandlerContext ctx, HttpXmlRequest msg, List<Object> out) throws Exception { // TODO Auto-generated method stub //将业务需要发送的pojo对象Order实例通过JiBx序列化成字符串,随后封装成Netty的ByteBuf。 ByteBuf body = encode0(ctx, msg.getBody()) ; FullHttpRequest request = msg.getRequest(); //如果业务侧没有设置消息头 则构造新的http请求头 if(request == null){ request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,HttpMethod.GET,"/do",body); //从request中获取请求头 HttpHeaders header = request.headers(); //设置一系列的请求头 这边以硬编码的方式 产品化可以做成xml配置文件 header.set(HttpHeaderNames.HOST,InetAddress.getLocalHost().getHostAddress()); header.set(HttpHeaderNames.CONNECTION,HttpHeaderValues.CLOSE); header.set(HttpHeaderNames.ACCEPT_ENCODING,HttpHeaderValues.GZIP.toString() +","+HttpHeaderValues.DEFLATE.toString()); header.set(HttpHeaderNames.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.7"); header.set(HttpHeaderNames.USER_AGENT,"Netty xml Http Client side"); header.set(HttpHeaderNames.ACCEPT,"text/html.application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); header.set(HttpHeaderNames.CONTENT_LENGTH,Integer.toString(body.readableBytes())); } //完成消息体xml序列化后重新构造http请求消息加入到out中,由http请求编码器继续对http请求消息进行编码 out.add(request); } }
Http响应编码类
package com.netty.ch10.httpXML.response; import java.util.List; import com.netty.ch10.httpXML.common.AbstractHttpXmlEncoder; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.AsciiString; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderUtil; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; /** * Http+xml 应答消息编码类 * @author Administrator * */ public class HttpXmlResponseEncoder extends AbstractHttpXmlEncoder<HttpXmlResponse> { private static final HttpVersion HTTP_1_1 = HttpVersion.HTTP_1_1; private static final HttpResponseStatus OK = HttpResponseStatus.OK; private static final AsciiString CONTENT_TYPE = HttpHeaderNames.CONTENT_TYPE; @Override protected void encode(ChannelHandlerContext ctx, HttpXmlResponse msg, List<Object> out) throws Exception { ByteBuf body = encode0(ctx,msg.getResult()); FullHttpResponse response = msg.getHttpResponse(); if(response == null){ response = new DefaultFullHttpResponse(HTTP_1_1,OK,body); }else{ new DefaultFullHttpResponse(msg.getHttpResponse().protocolVersion(), msg.getHttpResponse().status(), body); } response.headers().set(CONTENT_TYPE, "text/xml"); response.headers().set(HttpHeaderNames.CONTENT_LENGTH,Integer.toString(body.readableBytes())); //将编码后的response对象添加到编码结果链表中,由后续的Http编码类进行二次编码 out.add(response); } }
Decoder编码基类
package com.netty.ch10.httpXML.common; import java.io.StringReader; import java.net.SocketAddress; import java.nio.charset.Charset; import java.util.List; import org.jibx.runtime.BindingDirectory; import org.jibx.runtime.IBindingFactory; import org.jibx.runtime.IMarshallingContext; import org.jibx.runtime.IUnmarshallingContext; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.handler.codec.MessageToMessageDecoder; public abstract class AbstractHttpXmlDecoder<T> extends MessageToMessageDecoder<T> { private IBindingFactory factory; private StringReader reader; private Class<?> clazz; private boolean isPrint; private final static String CHARSET_NAME = "UTF-8"; private final static Charset UTF_8 = Charset.forName(CHARSET_NAME); protected AbstractHttpXmlDecoder(Class<?> clazz){ this(clazz, false); } protected AbstractHttpXmlDecoder(Class<?> clazz,boolean isPrint){ this.clazz = clazz; this.isPrint = isPrint; } protected Object decode0(ChannelHandlerContext arg0,ByteBuf body) throws Exception{ factory = BindingDirectory.getFactory(clazz); String context = body.toString(UTF_8); if(isPrint) System.out.println("The body is"+context); reader =new StringReader(context); IUnmarshallingContext uctx = factory.createUnmarshallingContext(); Object result = uctx.unmarshalDocument(reader); reader.close(); reader = null; return result; } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { // TODO Auto-generated method stub super.write(ctx, msg, promise); if(reader != null){ reader.close(); reader = null; } } }
Http请求解码类
package com.netty.ch10.httpXML.request; import java.util.List; import com.netty.ch10.httpXML.common.AbstractHttpXmlDecoder; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.util.CharsetUtil; /** * HTTP+XML Http请求消息解码类 * @author Administrator * */ public class HttpXmlRequestDecoder extends AbstractHttpXmlDecoder<FullHttpRequest> { public HttpXmlRequestDecoder(Class<?> clazz) { this( clazz,false); } public HttpXmlRequestDecoder(Class<?> clazz,boolean isPrint) { super(clazz,isPrint); } /* (non-Javadoc) * @see com.netty.ch10.httpXML.response.AbstractHttpXmlDecoder#decode(io.netty.channel.ChannelHandlerContext, java.lang.Object, java.util.List) */ @Override protected void decode(ChannelHandlerContext arg0, FullHttpRequest arg1, List<Object> arg2) throws Exception { if(!arg1.decoderResult().isSuccess()){ sendError(arg0, HttpResponseStatus.BAD_REQUEST); return; } //通过FullHttpRequest 和经过decode0将byteBuf反序列化的order对象 构造HttpXmlRequest HttpXmlRequest request = new HttpXmlRequest(arg1, decode0(arg0,arg1.content())); arg2.add(request); } private static void sendError(ChannelHandlerContext ctx,HttpResponseStatus status){ FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,status ,Unpooled.copiedBuffer("Failure:"+status.toString()+"\r\n", CharsetUtil.UTF_8)); response.headers().set("contextType","text/plain;charset=UTF-8"); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } }
Http响应解码类
package com.netty.ch10.httpXML.response; import java.util.List; import com.netty.ch10.httpXML.common.AbstractHttpXmlDecoder; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultFullHttpResponse; public class HttpXmlResponseDecoder extends AbstractHttpXmlDecoder<DefaultFullHttpResponse> { public HttpXmlResponseDecoder(Class<?> clazz) { this(clazz,false); // TODO Auto-generated constructor stub } public HttpXmlResponseDecoder(Class<?> clazz,boolean isPrintLog) { super(clazz,isPrintLog); // TODO Auto-generated constructor stub } @Override protected void decode(ChannelHandlerContext ctx, DefaultFullHttpResponse msg, List<Object> out) throws Exception { //将反序列化的POJO对象构造成HttpXmlResponse 并添加到解码结果列表中 HttpXmlResponse resHttpXmlResponse = new HttpXmlResponse(msg,decode0(ctx,msg.content())); out.add(resHttpXmlResponse); } }
客户端及客户端请求handler
package com.netty.ch10.httpXML; import java.net.InetSocketAddress; import com.netty.ch10.httpXML.pojo.Order; import com.netty.ch10.httpXML.request.HttpXmlRequestEncoder; import com.netty.ch10.httpXML.response.HttpXmlResponseDecoder; import io.netty.bootstrap.Bootstrap; 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.NioSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpRequestEncoder; /** * Http+xml客户端 * @author Administrator *1)发起Http连接请求 *2)构造订购请求消息,将其编码成xml,通过http协议发给服务端 *3)构造http服务端的应答消息,将xml应答消息反序列化为订购消息POJO对象 *4)关闭HTTP连接 * *Handler的执行顺序 :https://my.oschina.net/jamaly/blog/272385 *https://my.oschina.net/freegarden/blog/300348 * * 执行顺序是:Encoder 先注册的后执行,与OutboundHandler一致;Decoder是先注册的先执行,与InboundHandler一致。 * */ public class HttpXmlClient { public void connect(int port) throws Exception{ //配置客户端NIO线程组 EventLoopGroup group = new NioEventLoopGroup(); try{ Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { //负责将二进制码流解码成HTTP的应答消息 ch.pipeline().addLast("http-decoder",new HttpRequestDecoder()); //负责将1个http请求消息的多个部分合并成一个完整的http消息 ch.pipeline().addLast("http-aggregator",new HttpObjectAggregator(65535)); //XML解码器 ch.pipeline().addLast("xml-decoder",new HttpXmlResponseDecoder(Order.class, true)); //【注意】:顺序,编码的时候按照从尾到头的顺序 HttpXmlClientHandler-->HttpXmlRequestEncoder-->HttpRequestEncoder ch.pipeline().addLast("http-encoder",new HttpRequestEncoder()); //2)封装成完整的httprequest ch.pipeline().addLast("xml-encoder",new HttpXmlRequestEncoder()); //1)pojo -->bytebuf //write POJO receive POJO ch.pipeline().addLast("xml-client-handler",new HttpXmlClientHandler()); } }); //发起异步连接请求 ChannelFuture f = b.connect(new InetSocketAddress(port)).sync(); //等待客户端链路关闭 f.channel().closeFuture().sync(); }finally{ group.shutdownGracefully(); } } public static void main(String[] args) throws Exception{ int port = 8080; if(args != null && args.length > 0){ try { port = Integer.valueOf(args[0]); } catch (Exception e) { // TODO: handle exception } } new HttpXmlClient().connect(port); } }
package com.netty.ch10.httpXML; import com.netty.ch10.httpXML.request.HttpXmlRequest; import com.netty.ch10.httpXML.response.HttpXmlResponse; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; public class HttpXmlClientHandler extends SimpleChannelInboundHandler<HttpXmlResponse> { @Override protected void messageReceived(ChannelHandlerContext ctx, HttpXmlResponse msg) throws Exception { System.out.println("the client receive response of http header is :" + msg.getHttpResponse().headers().names()); System.out.println("the client receive response of http body is :"+ msg.getResult()); } /* (non-Javadoc) * @see io.netty.channel.ChannelHandlerAdapter#channelActive(io.netty.channel.ChannelHandlerContext) */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { HttpXmlRequest request = new HttpXmlRequest(null, OrderFactory.create(124)); ctx.writeAndFlush(request); } /* (non-Javadoc) * @see io.netty.channel.ChannelHandlerAdapter#exceptionCaught(io.netty.channel.ChannelHandlerContext, java.lang.Throwable) */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
服务端及服务端响应handler
package com.netty.ch10.httpXML; import java.net.InetSocketAddress; import com.netty.ch10.httpXML.pojo.Order; import com.netty.ch10.httpXML.request.HttpXmlRequestDecoder; import com.netty.ch10.httpXML.response.HttpXmlResponseEncoder; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponseDecoder; import io.netty.handler.codec.http.HttpResponseEncoder; /** * Http 服务端的功能如下 * @author Administrator *1)接收Http客户端的连接 *2)接收http客户端的xml请求消息,并将其解码成POJO对象 *3)对pojo对象进行业务处理 *4)通过http+xml的格式返回应答消息 *5)主动关闭http连接 */ public class HttpXmlServer { public void run(final int port) throws Exception{ NioEventLoopGroup workerGroup = new NioEventLoopGroup(); NioEventLoopGroup bossGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup,workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast("http-decoder",new HttpRequestDecoder()); ch.pipeline().addLast("http-aggregator",new HttpObjectAggregator(65536)); ch.pipeline().addLast("xml-decoder",new HttpXmlRequestDecoder(Order.class,true)); //下面顺序相反 ch.pipeline().addLast("http-encoder",new HttpResponseEncoder()); ch.pipeline().addLast("xml-encoder",new HttpXmlResponseEncoder()); ch.pipeline().addLast("xmlServerHandler",new HttpXmlServerHandler()); } }); ChannelFuture future = b.bind(new InetSocketAddress(port)).sync(); System.out.println("Http 订购服务器启动,网址是:"+"http://localhost:"+port); // Wait until the connection is closed. future.channel().closeFuture().sync(); } catch (Exception e) { // TODO: handle exception } } public static void main(String[] args) throws Exception { int port = 8080; if(args.length>0){ try{ port = Integer.parseInt(args[0]); }catch(Exception e){ e.printStackTrace(); } } new HttpXmlServer().run(port); } }
package com.netty.ch10.httpXML; import java.util.ArrayList; import java.util.List; import com.netty.ch10.httpXML.pojo.Address; import com.netty.ch10.httpXML.pojo.Order; import com.netty.ch10.httpXML.request.HttpXmlRequest; import com.netty.ch10.httpXML.response.HttpXmlResponse; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.util.CharsetUtil; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; public class HttpXmlServerHandler extends SimpleChannelInboundHandler<HttpXmlRequest> { @Override public void messageReceived(final ChannelHandlerContext ctx, HttpXmlRequest xmlRequest) throws Exception { FullHttpRequest request = xmlRequest.getRequest(); Order order = (Order)xmlRequest.getBody(); System.out.println("Http server receive request :"+ order); dobusiness(order); ChannelFuture future = ctx.writeAndFlush(new HttpXmlResponse(null,order)); if(!ctx.channel().isActive()){ future.addListener(new GenericFutureListener<Future<? super Void>>() { @Override public void operationComplete(Future future) throws Exception { // TODO Auto-generated method stub ctx.close(); } }); } } private void dobusiness(Order order) { order.getCustomer().setFirstName("狄"); order.getCustomer().setLastName("仁杰"); List<String> midNames = new ArrayList<String>(); midNames.add("李元芳"); order.getCustomer().setMiddleNames(midNames); Address address = order.getBillTo(); address.setCity("洛阳"); address.setCountry("大唐"); address.setState("河南道"); address.setPostCode("123456"); order.setBillTo(address); // order.setShiping(address); } /* * (non-Javadoc) * * @see * io.netty.channel.ChannelHandlerAdapter#exceptionCaught(io.netty.channel. * ChannelHandlerContext, java.lang.Throwable) */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // TODO Auto-generated method stub cause.printStackTrace(); if(ctx.channel().isActive()){ sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR); } } private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) { FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer("失败:" + status.toString() + "\r\n", CharsetUtil.UTF_8)); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=UTF-8"); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } }
WebSocket协议开发
webSocket将网络套接字引入到客户端和服务端,浏览器和服务端之间可以通过套接字建立持久连接,双方随时都可以互发数据给对方,而不是之前由客户端控制的请求-应答模式。
http协议的弊端:
1)同一时刻只能在一个方向上传输数据(半双工)
2)HTTP消息冗余而繁琐
3)针对服务器推送的黑客攻击,如长时间轮询。
webSocket特点:
1)单一的TCP连接,采用全双工模式通信
2)对代理,防火墙和路由器透明
3)无头部信息,Cookie和身份验证
4)无安全开销
5)通过“ping/pong”帧保持链路激活
6)服务器可以主动传递消息给客户端,不再需要客户端轮询。
server
package com.netty.ch11; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.stream.ChunkedWriteHandler; /** * webSocketServer 启动端 * * @author Administrator * */ public class WebSocketServer { public void run(int port) throws Exception { NioEventLoopGroup bossGroup = new NioEventLoopGroup(); NioEventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); //将请求和应答消息编码和解码为http消息 pipeline.addLast("http-codec", new HttpServerCodec()); //将Http消息的多个部分组合成一条完整的http消息 pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); //来向客户端发送html5文件 ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler()); ch.pipeline().addLast("handler", new WebSocketServerHandler()); } }); Channel ch = b.bind(port).sync().channel(); System.out.println("Web socket server started at port " + port + '.'); System.out.println("Open your browser and navigate to http://localhost:" + port + '/'); ch.closeFuture().sync(); } finally { System.out.println("stop server"); bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port = 8080; if (args.length > 0) { try { port = Integer.parseInt(args[0]); } catch (NumberFormatException e) { e.printStackTrace(); } } new WebSocketServer().run(port); } }
websocket处理类
package com.netty.ch11; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.websocketx.*; import io.netty.util.CharsetUtil; import java.util.logging.Level; import java.util.logging.Logger; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; /** * @version 1.0 * @date 2014年2月14日 */ public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> { private static final Logger logger = Logger .getLogger(WebSocketServerHandler.class.getName()); //握手处理类 private WebSocketServerHandshaker handshaker; @Override public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { // 传统的HTTP接入 if (msg instanceof FullHttpRequest) { handleHttpRequest(ctx, (FullHttpRequest) msg); } // WebSocket接入 前端请求websocket socket.onmessage else if (msg instanceof WebSocketFrame) { handleWebSocketFrame(ctx, (WebSocketFrame) msg); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception { // 如果HTTP解码失败,返回HHTP异常 if (!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) { sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST)); return; } // 构造握手响应返回,本机测试 WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory( "ws://localhost:8080/websocket", null, false); //创建握手消息处理类 handshaker = wsFactory.newHandshaker(req); //无法处理的websocket if (handshaker == null) { WebSocketServerHandshakerFactory .sendUnsupportedVersionResponse(ctx.channel()); } else { handshaker.handshake(ctx.channel(), req); } } private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { // 判断是否是关闭链路的指令 if (frame instanceof CloseWebSocketFrame) { handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain()); return; } // 判断是否是Ping消息 if (frame instanceof PingWebSocketFrame) { ctx.channel().write( new PongWebSocketFrame(frame.content().retain())); return; } // 本例程仅支持文本消息,不支持二进制消息 if (!(frame instanceof TextWebSocketFrame)) { throw new UnsupportedOperationException(String.format( "%s frame types not supported", frame.getClass().getName())); } // 返回应答消息 String request = ((TextWebSocketFrame) frame).text(); if (logger.isLoggable(Level.FINE)) { logger.fine(String.format("%s received %s", ctx.channel(), request)); } ctx.channel().write( new TextWebSocketFrame(request + " , 欢迎使用Netty WebSocket服务,现在时刻:" + new java.util.Date().toString())); } private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) { // 返回应答给客户端 if (res.status().code() != 200) { ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8); res.content().writeBytes(buf); buf.release(); res.headers().set(HttpHeaderNames.CONTENT_LENGTH ,Integer.toString(res.content().readableBytes())); } // 如果是非Keep-Alive,关闭连接 ChannelFuture f = ctx.channel().writeAndFlush(res); if (!ctx.channel().isActive() || res.status().code() != 200) { f.addListener(ChannelFutureListener.CLOSE); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
client html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> Netty WebSocket 时间服务器 </head> <br> <body> <br> <script type="text/javascript"> var socket; if (!window.WebSocket) { window.WebSocket = window.MozWebSocket; } if (window.WebSocket) { socket = new WebSocket("ws://localhost:8080/websocket"); socket.onmessage = function (event) { var ta = document.getElementById('responseText'); ta.value = ""; ta.value = event.data }; socket.onopen = function (event) { var ta = document.getElementById('responseText'); ta.value = "打开WebSocket服务正常,浏览器支持WebSocket!"; }; socket.onclose = function (event) { var ta = document.getElementById('responseText'); ta.value = ""; ta.value = "WebSocket 关闭!"; }; } else { alert("抱歉,您的浏览器不支持WebSocket协议!"); } function send(message) { if (!window.WebSocket) { return; } if (socket.readyState == WebSocket.OPEN) { socket.send(message); } else { alert("WebSocket连接没有建立成功!"); } } </script> <form onsubmit="return false;"> <input type="text" name="message" value="Netty最佳实践"/> <br><br> <input type="button" value="发送WebSocket请求消息" onclick="send(this.form.message.value)"/> <hr color="blue"/> <h3>服务端返回的应答消息</h3> <textarea id="responseText" style="width:500px;height:300px;"></textarea> </form> </body> </html>
私有协议栈开发
绝大多数的私有协议传输层都是基于TCP/IP,所以利用Netty的NIO TCP协议栈可以很方便的进行私有协议的定制和开发。
Netty协议栈功能设计
通信模型:
1)Netty协议栈client发送握手消息,携带节点IP等有效身份认证信息。
2)Netty协议栈服务端对握手请求消息进行合法性的认证,包括节点ID有效性检验,节点重复登录校验和IP地址合法性校验,校验通过后,返回登录成功的握手应答消息。
3)链路建立成功后,client发送业务消息。
4)链路成功后,server发送心跳消息。
5)链路建立成功之后,client发送心跳消息。
6)链路建立成功后,server发送业务消息。
7)server退出后,server关闭连接,client感知对方关闭连接后,被动关闭连接。
注意:双方采用全双工的通信方式,并不区分server与client。双方之间采用ping-pong心跳机制。链路断开client主动关闭连接,间隔T周期发起重连,直至成功。
消息定义:消息头,消息体。
链路的建立:创建握手请求消息,同时server基于IP地址进行黑白名单安全认证机制,或者通过密钥+数字证书进行接入认证。
链路的关闭:双方通过心跳和业务消息维持链路的长连接,任何一方都不需要主动关闭连接,但是以下情况需要关闭连接,
1)对方宕机或重启。
2)消息读写发生IO异常。
3)心跳发生IO异常或者超市。
4)发生编码异常等不可恢复。
心跳机制(ping-pong):在网络空闲时采用心跳机制检验链路的互通性,一旦发现网络故障,立即关闭链路,主动连接。
1)当网络处于空闲状态,连续T周期没有读写,则client主动发送ping心跳消息给服务端。
2)如果在下个周期T到来时client没有收到对方发送的Pong心跳应答消息或者读取到服务端发送的其他业务消息,则心跳计数器+1.
3)一旦收到业务消息或者Pong消息,则心跳计数器=0,N次没有收到服务端应答,则关闭链路,间隔INTERVAL时间后发起重连。
4)server网络空闲状态时间T后,心跳失败计数器+1,N次没有接受到client请求则关闭链路等待client重连,一旦接受到ping或业务消息,计数器清零。
注意:通过Ping-Pong双向心跳机制,保证无论哪一方出现问题,都被及时检测出来。N次检测防止对方短时间繁忙没有及时应答而造成的误判。
重连机制: 间隔INTERVAL时间不断重连,保证服务端有足够的时间释放句柄资源,再发起重新连接,直至成功。
重复登录保护:在缓存地址表中查看client是否已经登录,如果已经登录,则拒绝重复登录,返回错误码-1,并关闭链路。如心跳失败同时也清空该client的地址缓存表信息。
消息缓存重发:链路中断后,待发送的消息缓存在消息队列不能丢失,等恢复链路后消息重新发送,同时设置队列上线,防止OOM,超过上限聚集添加新的消息。
安全性:IP黑白名单,SSL/TSL会话。
私有栈协议开发
数据结构定义:
package com.netty.ch12.msg; import java.util.HashMap; import java.util.Map; public class Header { private int crcCode = 0xabef0101; private int length ;//消息长度 private long sessionID;//会话ID private byte type;//消息类型 private byte priority;//消息优先级 private Map<String,Object> attachment = new HashMap<String,Object>();//附件 同时便于扩展 /** * @return the crcCode */ public int getCrcCode() { return crcCode; } /** * @param crcCode the crcCode to set */ public void setCrcCode(int crcCode) { this.crcCode = crcCode; } /** * @return the length */ public int getLength() { return length; } /** * @param length the length to set */ public void setLength(int length) { this.length = length; } /** * @return the sessionID */ public long getSessionID() { return sessionID; } /** * @param sessionID the sessionID to set */ public void setSessionID(long sessionID) { this.sessionID = sessionID; } /** * @return the type */ public byte getType() { return type; } /** * @param type the type to set */ public void setType(byte type) { this.type = type; } /** * @return the priority */ public byte getPriority() { return priority; } /** * @param priority the priority to set */ public void setPriority(byte priority) { this.priority = priority; } /** * @return the attachment */ public Map<String, Object> getAttachment() { return attachment; } /** * @param attachment the attachment to set */ public void setAttachment(Map<String, Object> attachment) { this.attachment = attachment; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return "Header [crcCode=" + crcCode + ", length=" + length + ", sessionID=" + sessionID + ", type=" + type + ", priority=" + priority + ", attachment=" + attachment + "]"; } }
package com.netty.ch12.msg; /** * 对netty协议栈使用到的数据结构进行定义 * @author Administrator * */ public final class NettyMessage { private Header header; //消息头 private Object body;//消息体 /** * @return the header */ public final Header getHeader() { return header; } /** * @param header the header to set */ public final void setHeader(Header header) { this.header = header; } /** * @return the body */ public final Object getBody() { return body; } /** * @param body the body to set */ public final void setBody(Object body) { this.body = body; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return "NettyMessage [header=" + header + ", body=" + body + "]"; } }
Netty消息编码工具类:
package com.netty.ch12.util; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.marshalling.MarshallingDecoder; import io.netty.handler.codec.marshalling.UnmarshallerProvider; public class NettyMarshallingDecoder extends MarshallingDecoder{ public NettyMarshallingDecoder(UnmarshallerProvider provider) { super(provider); } public NettyMarshallingDecoder(UnmarshallerProvider provider, int maxObjectSize){ super(provider, maxObjectSize); } public Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { return super.decode(ctx, in); } }
package com.netty.ch12.util; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.marshalling.MarshallerProvider; import io.netty.handler.codec.marshalling.MarshallingDecoder; import io.netty.handler.codec.marshalling.MarshallingEncoder; import io.netty.handler.codec.marshalling.UnmarshallerProvider; public class NettyMarshallingEncoder extends MarshallingEncoder{ public NettyMarshallingEncoder(MarshallerProvider provider) { super(provider); } public void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception{ super.encode(ctx, msg, out); } }
package com.netty.ch12.util; import io.netty.handler.codec.marshalling.DefaultMarshallerProvider; import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider; import io.netty.handler.codec.marshalling.MarshallerProvider; import io.netty.handler.codec.marshalling.MarshallingDecoder; import io.netty.handler.codec.marshalling.UnmarshallerProvider; import org.jboss.marshalling.MarshallerFactory; import org.jboss.marshalling.Marshalling; import org.jboss.marshalling.MarshallingConfiguration; public class MarshallingCodeCFactory { public static NettyMarshallingDecoder buildMarshallingDecoder(){ MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial"); MarshallingConfiguration configuration = new MarshallingConfiguration(); configuration.setVersion(5); UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration); NettyMarshallingDecoder decoder = new NettyMarshallingDecoder(provider, 1024); return decoder; } public static NettyMarshallingEncoder buildMarshallingEncoder(){ MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial"); MarshallingConfiguration configuration = new MarshallingConfiguration(); configuration.setVersion(5); MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration); NettyMarshallingEncoder encoder = new NettyMarshallingEncoder(provider); return encoder; } }
消息编解码:
package com.netty.ch12.handler; import java.io.IOException; import java.util.List; import java.util.Map; import com.netty.ch12.msg.NettyMessage; import com.netty.ch12.util.MarshallingCodeCFactory; import com.netty.ch12.util.NettyMarshallingEncoder; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageEncoder; /** * nettty 消息编码类 * @author Administrator * */ public class NettyMessageEncoder extends MessageToMessageEncoder<NettyMessage> { private NettyMarshallingEncoder marshallingEncoder; public NettyMessageEncoder(){ marshallingEncoder = MarshallingCodeCFactory.buildMarshallingEncoder(); } @Override protected void encode(ChannelHandlerContext ctx, NettyMessage msg, List<Object> out) throws Exception { if(msg == null || msg.getHeader() == null) throw new Exception("The encode message is null"); ByteBuf sendBuf = Unpooled.buffer(); sendBuf.writeInt(msg.getHeader().getCrcCode()); sendBuf.writeInt(msg.getHeader().getLength()); sendBuf.writeLong(msg.getHeader().getSessionID()); sendBuf.writeByte(msg.getHeader().getType()); sendBuf.writeByte(msg.getHeader().getPriority()); sendBuf.writeInt(msg.getHeader().getAttachment().size()); String key = null; byte[] keyArray = null; Object value = null; for(Map.Entry<String, Object> param : msg.getHeader().getAttachment().entrySet()){ key = param.getKey(); keyArray = key.getBytes("utf-8"); sendBuf.writeInt(keyArray.length); sendBuf.writeBytes(keyArray); value = param.getValue(); marshallingEncoder.encode(ctx,value, sendBuf); } key = null; keyArray = null; value = null; if(msg.getBody() != null){ marshallingEncoder.encode(ctx,msg.getBody(), sendBuf); }//else //sendBuf.writeInt(0); // 在第4个字节处写入Buffer的长度 sendBuf.setInt(4, sendBuf.readableBytes()); // 把Message添加到List传递到下一个Handler out.add(sendBuf); } }
package com.netty.ch12.handler; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import java.util.HashMap; import java.util.Map; import com.netty.ch12.msg.Header; import com.netty.ch12.msg.NettyMessage; import com.netty.ch12.util.MarshallingCodeCFactory; import com.netty.ch12.util.NettyMarshallingDecoder; /** * netty 消息解码类 * @author Administrator * */ public class NettyMessageDecoder extends LengthFieldBasedFrameDecoder{ private NettyMarshallingDecoder marshallingDecoder; public NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) { super(maxFrameLength, lengthFieldOffset, lengthFieldLength); marshallingDecoder = MarshallingCodeCFactory.buildMarshallingDecoder(); } public NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,int lengthAdjustment, int initialBytesToStrip) { super(maxFrameLength, lengthFieldOffset, lengthFieldLength,lengthAdjustment, initialBytesToStrip); marshallingDecoder = MarshallingCodeCFactory.buildMarshallingDecoder(); } public Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception{ ByteBuf frame = (ByteBuf)super.decode(ctx, in); //为空说明是半包消息,直接返回由i/o线程读取后续的码流 if(frame == null){ return null; } NettyMessage message = new NettyMessage(); Header header = new Header(); header.setCrcCode(frame.readInt()); header.setLength(frame.readInt()); header.setSessionID(frame.readLong()); header.setType(frame.readByte()); header.setPriority(frame.readByte()); int size = frame.readInt(); if(size > 0){ Map<String, Object> attach = new HashMap<String, Object>(size); int keySize = 0; byte[] keyArray = null; String key = null; for(int i=0; i<size; i++){ keySize = frame.readInt(); keyArray = new byte[keySize]; in.readBytes(keyArray); key = new String(keyArray, "UTF-8"); attach.put(key, marshallingDecoder.decode(ctx, frame)); } key = null; keyArray = null; header.setAttachment(attach); } if(frame.readableBytes() > 0){ message.setBody(marshallingDecoder.decode(ctx, frame)); } message.setHeader(header); return message; } }
common类
package com.netty.ch12.comon; public enum MessageType { LOGIN_REQ{ public byte value(){ return (byte)1; } },LOGIN_RESP{ public byte value(){ return (byte)2; } },HEARTBEAT_REQ{ @Override public byte value() { // TODO Auto-generated method stub return (byte)3; } },HEARTBEAT_RESP{ @Override public byte value() { // TODO Auto-generated method stub return (byte)4; } }; public abstract byte value(); }
package com.netty.ch12.comon; public class NettyConstant { public static final String LOCALIP = "127.0.0.1"; public static final String REMOTEIP = "127.0.0.1"; public static final int LOCAL_PORT = 12088; public static final int PORT = 8080; }
握手和安全认证:
握手的发起是在客户端和服务端TCP链路建立成功通道激活时,握手消息的接入和安全认证在服务端处理。
package com.netty.ch12.handler; import com.netty.ch12.comon.MessageType; import com.netty.ch12.msg.Header; import com.netty.ch12.msg.NettyMessage; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; public class LoginAuthReqHandler extends ChannelHandlerAdapter { public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(buildLoginReq()); } public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { NettyMessage message = (NettyMessage) msg; if (message.getHeader() != null && message.getHeader().getType() == MessageType.LOGIN_RESP.value()) { System.out.println("Received from server response"); byte loginResult = (byte) message.getBody(); if (loginResult != (byte) 0) { // 握手失败 关闭连接 ctx.close(); } else { System.out.println("Login is ok :" + message); ctx.fireChannelRead(msg); } } else { ctx.fireChannelRead(msg); } } private NettyMessage buildLoginReq() { NettyMessage message = new NettyMessage(); Header header = new Header(); header.setType(MessageType.LOGIN_REQ.value()); message.setHeader(header); message.setBody("It is request"); return message; } public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { ctx.close(); } }
package com.netty.ch12.handler; import java.net.InetSocketAddress; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.netty.ch12.comon.MessageType; import com.netty.ch12.msg.Header; import com.netty.ch12.msg.NettyMessage; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; public class LoginAuthRespHandler extends ChannelHandlerAdapter { //地址缓存 private Map<String,Boolean> nodeCheck = new ConcurrentHashMap<String,Boolean>(); private String[] whiteList = {"127.0.0.1","192.168.1.104"}; public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { NettyMessage message = (NettyMessage)msg; //如果是握手请求消息则处理 其他消息透传 if(message.getHeader() != null && message.getHeader().getType() == MessageType.LOGIN_REQ.value()){ String nodeIndex = ctx.channel().remoteAddress().toString(); NettyMessage loginResp = null; //重复登录 拒绝 if(nodeCheck.containsKey(nodeIndex)){ loginResp = buildLoginResponse((byte)-1); }else{ InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress(); String ip = address.getAddress().getHostAddress(); System.out.println("获取client连接:"+ip); boolean isOk = false; for(String WIP : whiteList){ if(WIP.equals(ip)){ isOk = true; break; } } loginResp = isOk ? buildLoginResponse((byte)0) : buildLoginResponse((byte)-1); if(isOk){ nodeCheck.put(nodeIndex, true); } System.out.println("The login response is :" + loginResp + "body [" + loginResp.getBody() + "]"); ctx.writeAndFlush(loginResp); } }else{ ctx.fireChannelRead(msg); } } private NettyMessage buildLoginResponse(byte result) { NettyMessage message = new NettyMessage(); Header header = new Header(); header.setType(MessageType.LOGIN_RESP.value()); message.setHeader(header); message.setBody(result); return message; } public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { nodeCheck.remove(ctx.channel().remoteAddress().toString());//删除缓存 ctx.close(); ctx.fireExceptionCaught(cause); } }
心跳检测机制
package com.netty.ch12.handler; import java.util.concurrent.TimeUnit; import com.netty.ch12.comon.MessageType; import com.netty.ch12.msg.Header; import com.netty.ch12.msg.NettyMessage; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.util.concurrent.ScheduledFuture; public class HeartBeatReqHandler extends ChannelHandlerAdapter{ private volatile ScheduledFuture<?> heartBeat; /* (non-Javadoc) * @see io.netty.channel.ChannelHandlerAdapter#channelRead(io.netty.channel.ChannelHandlerContext, java.lang.Object) */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // TODO Auto-generated method stub NettyMessage message = (NettyMessage) msg; //握手成功(如果握手不成功,则已经关闭连接,不会执行至此) 主动发送心跳消息 if(message.getHeader() != null && message.getHeader().getType() == MessageType.LOGIN_RESP.value()){ heartBeat = ctx.executor().scheduleAtFixedRate(new HeartBeatTask(ctx), 0, 5000, TimeUnit.MILLISECONDS); }else if(message.getHeader().getType() == MessageType.HEARTBEAT_RESP.value()){ System.out.println("Client receive server heart beat message : -- >" + message); }else{ ctx.fireChannelRead(msg); } } //心跳探测 private class HeartBeatTask implements Runnable{ private final ChannelHandlerContext ctx; public HeartBeatTask(final ChannelHandlerContext ctx){ this.ctx = ctx; } @Override public void run() { NettyMessage heatBeat = buildHeatBeat(); System.out.println("client send heart beat message to server : --> " + heartBeat); ctx.writeAndFlush(heatBeat); } private NettyMessage buildHeatBeat(){ NettyMessage message = new NettyMessage(); Header header = new Header(); header.setType(MessageType.HEARTBEAT_REQ.value()); message.setHeader(header); return message; } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // TODO Auto-generated method stub if(heartBeat != null){ heartBeat.cancel(true); heartBeat = null; } ctx.fireExceptionCaught(cause); } }
package com.netty.ch12.handler; import com.netty.ch12.comon.MessageType; import com.netty.ch12.msg.Header; import com.netty.ch12.msg.NettyMessage; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; public class HeartBeatRespHandler extends ChannelHandlerAdapter{ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { NettyMessage message = (NettyMessage) msg; //返回心跳应答消息 if(message.getHeader() != null && message.getHeader().getType() == MessageType.HEARTBEAT_REQ.value()){ System.out.println("Receive client heart beat message : ---> " + message); NettyMessage heartBeat = buildHeatBeat(); System.out.println("Send heart beat response message to client : --->" +heartBeat); ctx.writeAndFlush(heartBeat); }else{ ctx.fireChannelRead(message); } } private NettyMessage buildHeatBeat(){ NettyMessage message = new NettyMessage(); Header header = new Header(); header.setType(MessageType.HEARTBEAT_RESP.value()); message.setHeader(header); return message; } }
客户端
package com.netty.ch12; import java.net.InetSocketAddress; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import com.netty.ch12.comon.NettyConstant; import com.netty.ch12.handler.HeartBeatReqHandler; import com.netty.ch12.handler.LoginAuthReqHandler; import com.netty.ch12.handler.NettyMessageDecoder; import com.netty.ch12.handler.NettyMessageEncoder; import io.netty.bootstrap.Bootstrap; 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.NioSocketChannel; import io.netty.handler.timeout.ReadTimeoutHandler; /** * 客户端主要用于初始化系统资源,根据配置信息发起连接 * @author Administrator * */ public class NettyClient { private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); EventLoopGroup group = new NioEventLoopGroup(); public void connect(int port ,String host) throws Exception{ try{ Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new NettyMessageDecoder(1024 * 1024, 4, 4, -8, 0)); ch.pipeline().addLast("MessageEncoder",new NettyMessageEncoder()); //心跳超时的实现非常简单,利用ReadTimeoutHandler机制,当一定周期内(50s)没有读取到对方任何消息,需要主动关闭连接。 //如果是client,发起重新连接,如果是server,释放资源,清除客户端登录缓存信息,等待服务端重连。 ch.pipeline().addLast("readTimeoutHandler",new ReadTimeoutHandler(50)); ch.pipeline().addLast("LoginAuthHandler",new LoginAuthReqHandler()); ch.pipeline().addLast("HeartBeatHandler",new HeartBeatReqHandler()); } }); //发起异步连接操作 ChannelFuture future = b.connect(new InetSocketAddress(host,port) ,new InetSocketAddress(NettyConstant.LOCALIP ,NettyConstant.LOCAL_PORT)); System.out.println("client to server "+host+":"+port); future.channel().closeFuture().sync(); }catch(Exception e){ e.printStackTrace(); }finally{ //所有资源释放完成之后,清空资源,再次发起重新连接 executor.execute(new Runnable(){ @Override public void run() { try { //等待5s TimeUnit.SECONDS.sleep(5); //发起重新连接 connect(NettyConstant.PORT, NettyConstant.REMOTEIP); } catch (Exception e) { e.printStackTrace(); } } }); } } public static void main(String[] args) throws Exception{ new NettyClient().connect(NettyConstant.PORT, NettyConstant.REMOTEIP); } }
服务端
package com.netty.ch12; import com.netty.ch12.comon.NettyConstant; import com.netty.ch12.handler.HeartBeatRespHandler; import com.netty.ch12.handler.LoginAuthReqHandler; import com.netty.ch12.handler.LoginAuthRespHandler; import com.netty.ch12.handler.NettyMessageDecoder; import com.netty.ch12.handler.NettyMessageEncoder; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.timeout.ReadTimeoutHandler; /** * 服务端 : 主要的工作就是握手的接入认证 不关心断连重连的问题 * @author Administrator * netstat -ano|findstr "8080" */ public class NettyServer { public void bind() throws Exception{ //配置服务端的NIO线程组 NioEventLoopGroup bossGroup = new NioEventLoopGroup(); NioEventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup,workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new NettyMessageDecoder(1024 * 1024, 4, 4, -8, 0)); ch.pipeline().addLast(new NettyMessageEncoder()); //当一定周期内没有读取对方任何消息 则主动关闭链路 ch.pipeline().addLast("readTimeoutHandler",new ReadTimeoutHandler(50)); ch.pipeline().addLast(new LoginAuthRespHandler()); ch.pipeline().addLast("HeartBeatHandler",new HeartBeatRespHandler()); } }); ChannelFuture f = b.bind(NettyConstant.REMOTEIP,NettyConstant.PORT).sync(); System.out.println("Netty server start ok : " +(NettyConstant.REMOTEIP + ":" + NettyConstant.PORT)); f.channel().closeFuture().sync(); } public static void main(String[] args) throws Exception { new NettyServer().bind(); } }