netty(一):NIO——处理消息边界

处理消息边界

为什么要处理边界

因为会存在半包和粘包的问题

1.客户端和服务端约定一个固定长度

  • 优点:简单

  • 缺点:可能造成浪费

2.客户端与服务端约定一个固定分割符

*缺点 效率低

3.先发送长度,再发送数据

TLV格式: type类型,length长度,Value数据,类型和长度已知的情况下,就可以方便获取消息大小

http1.1是TLV格式

http2.0是LTV格式

4.自动扩容解决消息边界问题

第一次read事件未能读完全部的输入,那么会产生第二个读事件,那么在第一次读的时候进行扩容,

并复制之前的内容至新的buffer中,

在第二个读事件触发以后使用扩容后的buffer,读取剩余的数据

buffer应当和各自的channel绑定,如何绑定,需要用到附件attachment,

attachment需要在注册时放到selectionKey中。

复制代码
// 绑定附件
SelectionKey scKey = channel.register(selector,0,byteBuffer);

// 获取附件
scKey.attachment();

// 指定新的附件(覆盖附件)
scKey.attach(bytebuffer);

示例代码:

复制代码
package com.ysf;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

public class BorderSelectorServer {

    /**
     * 读取到 '\n'时打印
     * @param byteBuffer 缓冲字节
     */
    public static void handle(ByteBuffer byteBuffer) {
        byteBuffer.flip();
        for (int i = 0; i < byteBuffer.limit(); i++) {
            if (byteBuffer.get(i) == '\n') {
                int length = i + 1 - byteBuffer.position();
                ByteBuffer allocate = ByteBuffer.allocate(length);
                for (int j=0;j<length;j++){
                    allocate.put(byteBuffer.get());
                }
                allocate.flip();
                System.out.println(Charset.defaultCharset().decode(allocate));
            }
        }
        byteBuffer.compact();
    }

    public static void main(String[] args) throws IOException {
        // 声明一个选择器
        Selector selector = Selector.open();

        // 声明一个server
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(11027));
        ssc.configureBlocking(false);

        // 注册这个server到selector
        SelectionKey sscKey = ssc.register(selector, 0, null);
        // 添加sscKey关心的事件,因为是serverChannel,所以应当关心accept事件
        sscKey.interestOps(SelectionKey.OP_ACCEPT);
        while (true) {
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();

                if (key.isAcceptable()) {
                    ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = serverChannel.accept();
                    socketChannel.configureBlocking(false);
                    // 声明一个buffer缓冲区和socketChannel绑定
                    ByteBuffer byteBuffer = ByteBuffer.allocate(16);
                    SelectionKey scKey = socketChannel.register(selector, 0, byteBuffer);
                    scKey.interestOps(SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    // 当客户端异常断开链接是需要处理IOException
                    try {
                        SocketChannel channel = (SocketChannel) key.channel();
                        ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
                        int read = channel.read(byteBuffer);
                        if (read == -1) {
                            // 客户端close()了
                            key.cancel();
                        }else{
                            // 调用处理逻辑
                            handle(byteBuffer);
                            if (byteBuffer.position() == byteBuffer.limit()){
                                // buffer满了,需要扩容
                                ByteBuffer bufferEx = ByteBuffer.allocate(byteBuffer.capacity() * 2);
                                byteBuffer.flip();
                                bufferEx.put(byteBuffer);
                                key.attach(bufferEx);
                            }
                        }

                    }catch (IOException e){
//                        e.printStackTrace();
                        key.cancel();
                    }
                }
            }
        }
    }
}
相关推荐
qq_251836457几秒前
基于java 汽车检修管理系统设计与实现 论文
java·开发语言·汽车
量子炒饭大师4 分钟前
【Linux系统编程】Cyberpunk在霓虹丛林中构建堡垒 ——【基础开发工具(1)】一文带你初步了解 软件包管理器 并 快速上手 yum和apt 工具
java·linux·运维·apt·yum·软件包管理器
Finger#0000FF9 分钟前
从零上手VibeCoding(ClaudeCode+DeepSeek V4.Pro)
java·人工智能·ai编程·vibe coding·claudecode
木子墨51610 分钟前
系统设计面试 | 实现一个限流器:滑动窗口 → 令牌桶 → 漏桶
java·开发语言·数据结构·数据库·面试·职场和发展
吴声子夜歌22 分钟前
Java——synchronized
java·synchronized
不知名的忻28 分钟前
交换排序:冒泡排序 vs 快速排序(Java)
java·算法·排序算法
程序员阿明29 分钟前
spring boot + vue3 实现RSA加密解密
java·spring boot·后端
wok1571 小时前
IDEA 无法识别 OkHttpClient?cannot resolve symbol问题解决
java·ide·intellij-idea
吴声子夜歌1 小时前
Java——标准序列化机制
java·序列化
hughnz1 小时前
下一代地热能的技术障碍
java·大数据·数据库