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);
    }
    
}
View Code

 

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;
    }
}
View Code

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;
    }
    
    
    
}
View Code

 

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;
        }
    } 
    
    
}
View Code

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);
    }

}
View Code

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);
    }

    

}
View Code

 

 

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;
        }
    }
    

    
}
View Code

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);
    }
    
}
View Code

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);
        
    }

}
View Code

 

客户端及客户端请求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);
    }
}
View Code
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();
    }

}
View Code

 

服务端及服务端响应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);
    }
}
View Code
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);
    }

}
View Code

 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);
    }
}
View Code

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();
    }


}
View Code

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>
View Code

 私有协议栈开发

 绝大多数的私有协议传输层都是基于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 + "]";
    }
    
    
}
View Code
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 + "]";
    }    
    
}
View Code

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);  
    }  
      
}  
View Code
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);  
    }  
}
View Code
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;  
    }  
}  
View Code

消息编解码:

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);  
    }

}
View Code
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;  
    }  
} 
View Code

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();
}
View Code
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;

}
View Code

 

握手和安全认证:

握手的发起是在客户端和服务端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();
    }
}
View Code
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);
    }  
}  
View Code

 

心跳检测机制

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);
    }

    
}
View Code
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;
    }
}
View Code

 

客户端

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);
    }
}
View Code

 

服务端

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();
    }
    
}
View Code

 

posted @ 2017-07-20 17:40  gaojy  阅读(1477)  评论(0编辑  收藏  举报