Netty学习篇--02

Channel、ChannelPipeline、ChannelHandlerContent发送数据的不同

// channel往回写数据
Channel channel = ctx.channel();
channel.writeAndFlush(Unpooled.copiedBuffer(new String("123").toCharArray(), CharsetUtil.UTF_8));
// pipeline写回数据
ChannelPipeline pipeline = ctx.pipeline();
pipeline.writeAndFlush(Unpooled.copiedBuffer(new String("123").toCharArray(), CharsetUtil.UTF_8));
// ctx写回数据
ctx.writeAndFlush(Unpooled.copiedBuffer(new String("123").toCharArray(), CharsetUtil.UTF_8));
三种方式的区别
  • 前2种影响channel整个通道的channelHandler的事件
  • ctx.writeAndFlush只影响当前handler

netty入站出站handler执行顺序问题

总结:
1. InboundHandler顺序执行,OutboundHandler逆序执行

2. InboundHandler通过write方法与OutboundHandler进行交互

3. InboundHandler想要传递消息到OutboundHandler,OutboundHandler需要在InboundHandler之前添加到管道中,否则在InboundHandler中write(msg)的时候OutboundHandler可能还没注册到管道中(前提是用ChannelHandlerContent进行回写数据)

4. InboundHandler和OutBoundHandler角色的不同顺序不同;针对客户端而言,客户端是先发起请求在接收数据,所以是OutboundHandler > InboundHandler;针对服务端则反之

netty异步操作

netty中所有的IO操作都是异步的,意味着任何IO调用都会立即返回,通过ChannelFuture获得IO操作的结果和状态

ChannelFuture
ChannelFuture提供了IO操作的结果和状态
它继承了io.netty.util.concurrent包下的Future
间接继承了并发包下的Future

ChannelFuture的类图

注意:
不要在IO线程内调用future对象的sync/await方法,不能再ChannelHandler中调用sync/await方法,可能导致死锁问题
ChannelPromise
继承ChannelFuture,进一步拓展用于设置IO操作结果
TCP粘包拆包
  • TCP拆包

    所谓拆包就是一个数据包拆分成多个包进行发送,就好比咱们在同一家店购买东西过多会拆成多个包裹进去发货类似,一个完成的数据包可能会被TCP拆分成多个包进行传输发送
  • TCP粘包
    粘包则和拆包相反,将多个小包封装成一个大的数据包,就好比快递站收到包裹不会立马运输派送,会等到一定的量才会运输派送;客户端发送若干的数据包,服务端接收的时候粘合成一个包接收
    
    // 客户端
    public class HtNettyClient {
    
        private String ip;
    
        private int port;
    
        HtNettyClient(String ip, int port) {
            this.ip = ip;
            this.port = port;
        }
    
        public void start() {
            EventLoopGroup workGroup = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(workGroup)
                        .channel(NioSocketChannel.class)
                        .remoteAddress(new InetSocketAddress(ip, port))
                        .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new HtNettyClientHandler());
                    }
                });
                ChannelFuture future = bootstrap.connect().sync();
                future.channel().closeFuture().sync();
            } catch (Exception e) {
    
            } finally {
                workGroup.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) {
            HtNettyClient client = new HtNettyClient("127.0.0.1", 9000);
            client.start();
        }
    }
    
    // 客户端发送数据handler
    public class HtNettyClientHandler extends ChannelInboundHandlerAdapter {
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            // 数据缓冲区
            ByteBuf byteBuf = null;
            byte[] req = ("hetangyuese" +System.getProperty("line.separator")).getBytes();
            for (int i= 0; i<10; i++) {
                byteBuf = Unpooled.buffer(req.length);
                byteBuf.writeBytes(req);
                ctx.writeAndFlush(byteBuf);
            }
     }
    
    // 服务端
    public class HtNettyServer {
    
        private int port;
    
        HtNettyServer(int port) {
            this.port = port;
        }
    
        public void start() {
            // 定义一个boss来处理请求
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(bossGroup, workGroup)
                        .option(ChannelOption.SO_BACKLOG, 1024)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ch.pipeline()
    //                                    .addLast(new LineBasedFrameDecoder(3))
                                        .addLast(new HtNettyHandler());
                            }
                        });
                // 绑定端口
                ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
                // 阻塞至直至关闭通道
                channelFuture.channel().closeFuture().sync();
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                bossGroup.shutdownGracefully();
                workGroup.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) {
            HtNettyServer server = new HtNettyServer(9000);
            server.start();
        }
    }
    
    // 服务端handler
    public class HtNettyHandler extends ChannelInboundHandlerAdapter {
    
        private int count;
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            // 如果没有定义解码器的话 默认转byteBuf
            ByteBuf byteBuf = (ByteBuf)msg;
            byte[] params = new byte[byteBuf.readableBytes()];
            byteBuf.readBytes(params);
            String body = new String(params, CharsetUtil.UTF_8);
            System.out.println("收到了请求,请求内容:" + body + ", 收到请求次数:" + ++count);
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }
    
    // 结果
    收到了请求,请求内容:hetangyuese
    hetangyuese
    hetangyuese
    hetangyuese
    hetangyuese
    hetangyuese
    hetangyuese
    hetangyuese
    hetangyuese
    hetangyuese
    , 收到请求次数:1
    
粘、拆包

为何TCP会导致粘包和拆包?

TCP每次在发送数据时都是以流的形式进行传输,底层有一个缓冲区来存储发送的字节流,

1.当发送数据小于缓冲区的饱和的大小时,会发生粘包,粘成一个包发送至服务端(每个包之间的间隔时间短,包数据很小);

2.当发送的数据大于缓冲区的阈值,则会拆分成多个包进行发送

3.服务端没及时读取缓冲区的数据,导致数据堆积,可能导致服务端粘包

4.发送方由于Nagle算法机制
Nagle算法
TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
粘包、拆包解决方法
1. 设置定长消息(根据业务设置足够长)
2. 设置消息的边界(设置分隔符)
3. 使用带消息头的协议,消息头存储消息开始标识及消息的长度信息
4. 发送端关闭Nagle算法

Netty编、解码器

Decoder解码
主要对应的是ChannelInboundHandler,主要作用将字节数组转换成对象消息
Decoder解码常用抽象类
  • ByteToMessageDecoder

    字节码转消息对象时需要检查缓冲区是否有足够的字节
    
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in,List<Object> out) throws Exception {
        // 假使消息为int字节 则需要判断bytebuf字节是否大于等于4
        if (in.readableBytes() >= 4) {
            // 符合的话就add到list对象中(解析对象)
            out.add(in.readInt());
        }
    }
    
  • ReplayingDecoder
    继承了ByteToMessageDecoder,不需要检查缓冲区是否有足够多的数据,速度略慢于ByteToMessageDecoder;
    tips: 根据项目的复杂程度合理选择,常用ByteToMessageDecoder
    
  • MessageToMessageDecoder
    用于从一种消息解码到另外一种消息
    
解码器的具体实现 (主要解决TCP底层的粘包和拆包问题)
  • DelimiterBasedFrameDecoder

    指定消息分隔符的解码器(客户端发送数据所有的数据末尾都需要增加分隔符,否则服务端接收不到)
    
    // 定义最大长度及需要的切割符
    public DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf delimiter) {
        this(maxFrameLength, true, delimiter);
    }
    // 定义切割符
    ByteBuf delimiter = Unpooled.copiedBuffer("自定义分隔符".getBytes());
    
    /**
    * maxFrameLength:解析分隔符最大长度
    * stripDelimiter:true表示解析数据隐藏分隔符,反之解析数据末尾都会带上分隔符
    * failFast: true表示超出maxLength立刻抛出异常,false则会解析完在抛出异常
    * delimiter: 分隔符
    */
    public DelimiterBasedFrameDecoder(
                int maxFrameLength, boolean stripDelimiter, boolean failFast,
                ByteBuf delimiter) {
        this(maxFrameLength, stripDelimiter, failFast, new ByteBuf[] {
            delimiter.slice(delimiter.readerIndex(), delimiter.readableBytes())});
    }
    
    // 客户端请求体
    reqStr = "hello!_" +
                    "My name is hanleilei !_" +
                    "What is your name !_" +
                    "How are you? !_"
            ;
    
    // 服务端增加解析 以!_为分隔符
    .addLast(new DelimiterBasedFrameDecoder(1024,Unpooled.copiedBuffer("!_".getBytes())))
    
    // 结果
    收到了请求,请求内容:hello, 收到请求次数:1
    收到了请求,请求内容:My name is hanleilei , 收到请求次数:2
    收到了请求,请求内容:What is your name , 收到请求次数:3
    收到了请求,请求内容:How are you? , 收到请求次数:4
    
    // 服务端设置stripDelimiter为false
    addLast(new DelimiterBasedFrameDecoder(1024, false, Unpooled.copiedBuffer("!_".getBytes())));
    
    //结果
    收到了请求,请求内容:hello!_, 收到请求次数:1
    收到了请求,请求内容:My name is hanleilei !_, 收到请求次数:2
    收到了请求,请求内容:What is your name !_, 收到请求次数:3
    收到了请求,请求内容:How are you? !_, 收到请求次数:4
    
  • LineBasedFrameDecoder
    以换行符为结束标志的解码器
    
    // 如果发送的数据超过了maxLength还未解析到换行符则抛出TooLongFrameException异常
    public LineBasedFrameDecoder(final int maxLength) {
      this(maxLength, true, false);
    }
    
    
    // 服务端定义最大长度为3的换行解码器
    public void start() {
            // 定义一个boss来处理请求
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(bossGroup, workGroup)
                        .option(ChannelOption.SO_BACKLOG, 1024)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ch.pipeline()
                                        .addLast(new LineBasedFrameDecoder(3))
                                        .addLast(new HtNettyHandler());
                            }
                        });
                // 绑定端口
                ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
                // 阻塞至直至关闭通道
                channelFuture.channel().closeFuture().sync();
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                bossGroup.shutdownGracefully();
                workGroup.shutdownGracefully();
            }
        }
    
    // 客户端发送
    public class HtNettyClientHandler extends ChannelInboundHandlerAdapter {
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            // 客户端注册是发送数据至服务端
            ByteBuf byteBuf = null;
            byte[] req = ("hetangyuese" +System.getProperty("line.separator")).getBytes();
            for (int i= 0; i<10; i++) {
                byteBuf = Unpooled.buffer(req.length);
                byteBuf.writeBytes(req);
                ctx.writeAndFlush(byteBuf);
            }
        }
    }
    
    // 异常
    io.netty.handler.codec.TooLongFrameException: frame length (11) exceeds the allowed maximum (3)
      at io.netty.handler.codec.LineBasedFrameDecoder.fail(LineBasedFrameDecoder.java:146)
      at io.netty.handler.codec.LineBasedFrameDecoder.fail(LineBasedFrameDecoder.java:142)
      at io.netty.handler.codec.LineBasedFrameDecoder.decode(LineBasedFrameDecoder.java:99)
      at io.netty.handler.codec.LineBasedFrameDecoder.decode(LineBasedFrameDecoder.java:75)
      at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489)
      at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428)
      at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265)
      at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
      at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
      at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
      at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1359)
      at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
      at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
      at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:935)
      at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:134)
      at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:645)
      at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:580)
      at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:497)
      at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:459)
      at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
      at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:138)
      at java.lang.Thread.run(Thread.java:748)
    
    // 修改长度
    addLast(new LineBasedFrameDecoder(1024))
    
    // 结果
    收到了请求,请求内容:hetangyuese, 收到请求次数:1
    收到了请求,请求内容:hetangyuese, 收到请求次数:2
    收到了请求,请求内容:hetangyuese, 收到请求次数:3
    收到了请求,请求内容:hetangyuese, 收到请求次数:4
    收到了请求,请求内容:hetangyuese, 收到请求次数:5
    收到了请求,请求内容:hetangyuese, 收到请求次数:6
    收到了请求,请求内容:hetangyuese, 收到请求次数:7
    收到了请求,请求内容:hetangyuese, 收到请求次数:8
    收到了请求,请求内容:hetangyuese, 收到请求次数:9
    收到了请求,请求内容:hetangyuese, 收到请求次数:10
    
  • FixedLengthFrameDecoder
    固定长度的解码器
    
    /**
    * frameLength:解析的消息体长度,每次直解析frameLength长度字节消息
    */
    public FixedLengthFrameDecoder(int frameLength) {
            if (frameLength <= 0) {
                throw new IllegalArgumentException(
                        "frameLength must be a positive integer: " + frameLength);
            }
            this.frameLength = frameLength;
        }
    
    // 客户端
    package com.hetangyuese.netty.client.handler;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    
    /**
     * @program: netty-root
     * @description: 客户端逻辑类
     * @author: hewen
     * @create: 2019-10-22 17:15
     **/
    public class HtNettyClientHandler extends ChannelInboundHandlerAdapter {
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            String reqStr = "";
            // 数据缓冲区
            ByteBuf byteBuf = null;
    //        reqStr = "hetangyuese" + System.getProperty("line.separator");
            reqStr = "hello!_" +
                    "My name is hanleilei !_" +
                    "What is your name !_" +
                    "How are you? !_"
            ;
            byte[] req = (reqStr).getBytes();
    //        for (int i= 0; i<10; i++) {
            byteBuf = Unpooled.buffer(req.length);
            byteBuf.writeBytes(req);
            ctx.writeAndFlush(byteBuf);
    //        }
        }
    }
    
    // 服务端解析(直接解析3个字节)
    ch.pipeline().addLast(new FixedLengthFrameDecoder(3))
    .addLast(new HtNettyHandler());
    
    // 结果
    收到了请求,请求内容:hel, 收到请求次数:1
    收到了请求,请求内容:lo!, 收到请求次数:2
    收到了请求,请求内容:_My, 收到请求次数:3
    收到了请求,请求内容: na, 收到请求次数:4
    收到了请求,请求内容:me , 收到请求次数:5
    收到了请求,请求内容:is , 收到请求次数:6
    收到了请求,请求内容:han, 收到请求次数:7
    收到了请求,请求内容:lei, 收到请求次数:8
    收到了请求,请求内容:lei, 收到请求次数:9
    收到了请求,请求内容: !_, 收到请求次数:10
    收到了请求,请求内容:Wha, 收到请求次数:11
    收到了请求,请求内容:t i, 收到请求次数:12
    收到了请求,请求内容:s y, 收到请求次数:13
    收到了请求,请求内容:our, 收到请求次数:14
    收到了请求,请求内容: na, 收到请求次数:15
    收到了请求,请求内容:me , 收到请求次数:16
    收到了请求,请求内容:!_H, 收到请求次数:17
    收到了请求,请求内容:ow , 收到请求次数:18
    收到了请求,请求内容:are, 收到请求次数:19
    收到了请求,请求内容: yo, 收到请求次数:20
    收到了请求,请求内容:u? , 收到请求次数:21
    
    
  • LengthFieldBasedFrameDecoder
    消息包括 :消息头 + 消息体,基于长度通用的解码器
    
    /**
    * maxFrameLength: 消息数据最大长度
    *
    * lengthFieldOffset:长度字段偏移位,长度字段开始的地方,跳过指定长度字节之后的消 * 息才是消息体字段,一般设置为0,从头部开始
    *
    * lengthFieldLength:消息头长度字段占的字节数,如果设为2则表示在
    * lengthFieldOffset开始往后的2个字节存储消息体长度
    *
    * lengthAdjustment:调整消息体长度字段,如果消息包括消息头(即长度字段),如果需要* 去掉消息头则需要对应设置为负数(长度字段的字节长度),netty解析要减去对应的数值获取* 消息体
    *
    * initialBytesToStrip:是否需要剔除消息头,在获取到一个完整的数据包之后,去除长度 * 字节,直接拿到消息体的数据,为0代表不去除消息头
    *
    * failFast:是否快速失败,true代表在数据长度超出最大长度则立刻抛出异常
    */
    public LengthFieldBasedFrameDecoder(
                int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,
                int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
            this(
                    ByteOrder.BIG_ENDIAN, maxFrameLength, lengthFieldOffset, lengthFieldLength,
                    lengthAdjustment, initialBytesToStrip, failFast);
        }
    
  • StringDecoder
    文本解码器,将接收到的消息解码成字符串,一般与上述搭配使用,然后在后面加业务的handler
    
Encoder编码器
主要对应的是ChannelOutboundHandler,将消息对象转换为字节数组
Encoder解码常用的抽象解码类
  • MessageToByteEncoder

    消息转为字节数组,write方法会判断是否支持消息类型,如果不支持则通过context传递到下一个ChannelOutboundHandler,自定义需要重写encode方法
    
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
            ByteBuf buf = null;
            try {
                // 判断是否支持的消息类型
                if (acceptOutboundMessage(msg)) {
                    @SuppressWarnings("unchecked")
                    I cast = (I) msg;
                    buf = allocateBuffer(ctx, cast, preferDirect);
                    try {
                        encode(ctx, cast, buf);
                    } finally {
                        ReferenceCountUtil.release(cast);
                    }
    
                    if (buf.isReadable()) {
                        ctx.write(buf, promise);
                    } else {
                        buf.release();
                        ctx.write(Unpooled.EMPTY_BUFFER, promise);
                    }
                    buf = null;
                } else {
                    // 如果不支持则通过context传递到下一个ChannelOutboundHandler
                    ctx.write(msg, promise);
                }
            } catch (EncoderException e) {
                throw e;
            } catch (Throwable e) {
                throw new EncoderException(e);
            } finally {
                if (buf != null) {
                    buf.release();
                }
            }
        }
    
    
  • MessageToMessageEncoder:将一种消息编码为另外一种消息
    

原文地址:https://www.cnblogs.com/hetangyuese/p/11742052.html

时间: 2024-08-29 00:54:46

Netty学习篇--02的相关文章

Netty学习篇--整合springboot

经过前面的netty学习,大概了解了netty各个组件的概念和作用,开始自己瞎鼓捣netty和我们常用的项目的整合(很简单的整合) 项目准备 工具:IDEA2017 jar包导入:maven 项目框架:springboot+netty 项目操作 右键创建一个maven项目,项目名称: hetangyuese-netty-03(项目已上传github) 项目完整结构 ? maven导包 <!-- netty start --> <dependency> <groupId>

pythonchallenge之C++学习篇-02

字符处理时每个语言都具备的一种功能,其中还有一些语言因此出名,比如perl,shell,还有一些函数式的编程语言 C语言中的字符串与数组和指针联系的比较紧密,因此可以这样生命字符串*p="hello wybret" C++还定义了一个内容丰富的抽象数据类型标准库,其中重要的类型就是string和vector以及bitset 如果你通过C++学习数据结构与算法之类的东西,估计会对抽象数据类型(ADT)相当了解 一个语言数据类型的多少以及以什么程度的方便表达应用,都会影响这门语言的使用,这

Netty学习篇④-心跳机制及断线重连

心跳检测 前言 客户端和服务端的连接属于socket连接,也属于长连接,往往会存在客户端在连接了服务端之后就没有任何操作了,但还是占用了一个连接:当越来越多类似的客户端出现就会浪费很多连接,netty中可以通过心跳检测来找出一定程度(自定义规则判断哪些连接是无效链接)的无效链接并断开连接,保存真正活跃的连接. 什么叫心跳检测 我理解的心跳检测应该是客户端/服务端定时发送一个数据包给服务端/客户端,检测对方是否有响应: 如果是存活的连接,在一定的时间内应该会收到响应回来的数据包: 如果在一定时间内

Objective-C学习篇02—封装

面向对象的三大特性:封装.继承和多态 封装目的就是将数据隐藏起来,外界只能通过这个类的方法(接口)才能访问或者设置里面的数据,不可以在外部直接修改或者访问里面的数据,通常使用方法来达到封装一个类的目的,最终使这个类达到高内聚低耦合的目的. 耦合说的是一个软件结构宏各个模块之间的相互关联相互紧密的程度,模块之间的联系越精密,其耦合性也就越强,模块间耦合的高低取决于模块之间接口的复杂性,调用信息的方式及传递的信息. 内聚说的是单个模块内各个元素之间彼此结合的紧密程度,所谓高内聚就是一个软件模块是由相

java基础学习篇02(数据类型转换和基本面试题)

JAVA基本数据类型转换 java中的数据类型,分为,引用数据类型和基本数据类型.基本数据类型有8中 整型:byte 8位 . short 16位 .int 32位 .long 64位. (8位=1个字节) 浮点型:float 32位 .double64位 字符类型:char Boolean型:boolean 自动类型转换 转换图 正方向是自动转换  反方向强制转换(char--->double的转换  和byte到 double的转换) char -->int-->long-->

从入门到实战,Netty多线程篇案例集锦

从入门到实战,Netty多线程篇案例集锦 原创 2015-09-10 李林峰 InfoQ Netty案例集锦系列文章介绍 1|Netty的特点 Netty入门比较简单,主要原因有如下几点: Netty的API封装比较简单,将复杂的网络通信通过BootStrap等工具类做了二次封装,用户使用起来比较简单: Netty源码自带的Demo比较多,通过Demo可以很快入门: Netty社区资料.相关学习书籍也比较多,学习资料比较丰富. 但是很多入门之后的Netty学习者遇到了很多困惑,例如不知道在实际项

Android自定义view学习笔记02

Android自定义view学习笔记02 本文代码来自于张鸿洋老师的博客之Android 自定义View (二) 进阶 学习笔记,对代码进行些许修改,并补充一些在coding过程中遇到的问题.学习的新东西. 相关代码 //CustomImageView.java package mmrx.com.myuserdefinedview.textview; import android.content.Context; import android.content.res.TypedArray; im

netty 学习资料

最近在做一个网页遥控器的项目,用到了netty,但还未发现比较系统完整的netty博客教程,所以打算自己写一个netty学习教程,会每天更新一篇,欢迎交流. 先给大家提供一些资料: 1. 比较简短易懂的有实例的系列教程,涉及到了netty关键特性:但个人觉得比较速成,不系统,不深入 http://www.coderli.com/netty-course-hello-world http://www.coderli.com/netty-two-concepts http://www.coderli

Netty学习——通过websocket编程实现基于长连接的双攻的通信

Netty学习(一)基于长连接的双攻的通信,通过websocket编程实现 效果图,客户端和服务器端建立起长连接,客户端发送请求,服务器端响应 但是目前缺少心跳,如果两个建立起来的连接,一个断网之后,另外一个是感知不到对方已经断掉的.以后使用心跳技术来进行连接检测 须知: 状态码101,代表 协议转换,从HTTP协议升级为WebSocket协议 HTTP协议,一般访问的时候:是 Http://localhost:8080/ws WebSocket协议,访问的时候,需要是:ws://localho