9.处理消息边界

news2024/11/25 4:18:57

网络编程中消息的长度是不太确定的,read方法读取字节数据到ByteBuffer中,ByteBuffer会有一个固定容量,单次超出容量的部分字节数据将会在下一次的ByteBuffer中,这样消息就会按照字节截断,出现消息边界问题。

Http 2.0 是LTV格式

Type类型、Length长度、Value数据。在类型和长度已知的情况下,就可以方便的获取消息大小,分配合适的buffer,缺点是buffer需要提前分配,如果内容过大,则影响server的吞吐量。

 

/**
     * 服务端
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        //##创建一个Selector,管理多个Channel(比如:ServerSocketChannel,SocketChannel)
        Selector selector = Selector.open();
        //创建一个服务器
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //****ServerSocketChannel配置为非阻塞-默认是阻塞true,可以让accept方法变成非阻塞
        serverSocketChannel.configureBlocking(false);
        //## 将ServerSocketChannel注册到Selector
        //## 返回值selectionKey,将来事件发生后,可以知道事件和发生事件的channel
        //## 事件有:accept,connect, read, write,
        //## ServerSocketChannel -> accept事件 在服务端,会在客户端发起连接请求时触发
        //## connect事件,在客户端,当客户端与服务端建立连接以后,客户端触发
        //## SocketChannel -> read事件,可读事件,当客户端发送数据到Channel,服务端可以从channel读取数据
        //## write事件,可写事件
        //## 第二个参数0表示,不关注任何事件
        SelectionKey serverSocketChannelSelectionKey = serverSocketChannel.register(selector, 0, null);
        log.info("register key={}", serverSocketChannelSelectionKey);
        //## 只关注accept事件
        serverSocketChannelSelectionKey.interestOps(SelectionKey.OP_ACCEPT);
        //给服务器绑定一个端口8000,让客户端来连接
        serverSocketChannel.bind(new InetSocketAddress(8000));

        while(true) { //保证可以多个客户端连接
            //## select方法,没有事件发生时线程阻塞,有事件时候线程才能恢复运行
            // select在事件未处理时,它不会阻塞。(下面不调用SocketChannel的accept方法,线程就不会阻塞)
            // select在事件处理了(accept)或者取消(cancel)了才会阻塞。不能对事件置之不理,就会一直空转循环
            selector.select();
            //## 处理事件
            //## 获取到内部包含了所有发生的事件
            //## 这里的selectionKeys就包含了上面accept事件注册到Selector上返回的selectKey
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //因为要在遍历的时候会删除元素,所以采用迭代器遍历iterator
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            log.info("开始遍历所有的selectionKey");
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                //拿到的selectionKey一定从selectionKeys集合中删除,否则就可能会出问题
                //遍历的selectionKey处理完一定要删除
                iterator.remove();
                log.info("事件selectKey={}", selectionKey);
                //判断事件类型
                if(selectionKey.isAcceptable()) {//服务端accept事件
                    ServerSocketChannel channel = (ServerSocketChannel)selectionKey.channel();
                    log.info("事件关联的channel={}", channel);
                    //接受客户端的连接,事件处理accept
                    //如果上面不删除selectionKeys集合中的selectionKey,下次循环并没有客户端发起连接,accept方法返回null
                    SocketChannel accept = channel.accept();
                    log.info("accept={}", accept);
                    //将SocketChannel配置为非阻塞模式
                    accept.configureBlocking(false);
                    //将socketChannel注册到selector
                    //第三个参数attachment附件,专属于每个channel的ByteBuffer
                    //将ByteBuffer关联到selectionKey上
                    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(16);
                    SelectionKey acceptSelectKey = accept.register(selector, 0, byteBuffer);
                    //只关注read事件
                    acceptSelectKey.interestOps(SelectionKey.OP_READ);
                }else if(selectionKey.isReadable()) {//read事件
                    try{
                        SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
                        //获取channel专属的attachment->ByteBuffer,根据SelectionKey
                        ByteBuffer byteBuffer = (ByteBuffer)selectionKey.attachment();
                        int read = socketChannel.read(byteBuffer);
                        //-1表示客户端正常断开,客户端SocketChannel调用了close方法
                        if(read == -1) {
                            selectionKey.cancel();
                        }else {
//                            byteBuffer.flip();//读模式
//                            String resStr = StandardCharsets.UTF_8.decode(byteBuffer).toString();
//                            log.info("resStr={}", resStr);
                            split(byteBuffer);//这里会将原byteBuffer变成写模式
                            //需要扩容
                            if(byteBuffer.position() == byteBuffer.limit()) {
                                //创建一个新的byteBuffer其容量是原来的2倍
                                ByteBuffer increaseByteBuffer = ByteBuffer.allocateDirect(byteBuffer.capacity() * 2);
                                byteBuffer.flip();//切换为读模式,才能将byteBuffer中的数据读到
                                //将原来的byteBuffer的数据拷贝到新的ByteBuffer中
                                increaseByteBuffer.put(byteBuffer);
                                //用新的ByteBuffer替换原来的ByteBuffer
                                selectionKey.attach(increaseByteBuffer);
                            }
                        }
                    }catch (Exception e) {
                        e.printStackTrace();
                        //因为客户端断开了,会抛出Exception in thread "main" java.io.IOException: 远程主机强迫关闭了一个现有的连接。
                        //因此需要将key取消
                        //从selector的key集合中真正的删除key
                        selectionKey.cancel();
                    }
                }
            }
        }
    }

 private static void split(ByteBuffer source) {
        source.flip();//切换读模式
        for (int i = 0; i < source.limit(); i++) {

            if(source.get(i) == '\n') {
                int length = i + 1 - source.position();
                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(length);
                for (int j = 0; j < length; j++) {
                    byte b = source.get();
                    byteBuffer.put(b);
                }
                byteBuffer.flip();
                String str = StandardCharsets.UTF_8.decode(byteBuffer).toString();
                log.info("str={}", str);
            }
        }
        source.compact();//切换写模式,接着上次读的位置接着写

    }
/**
     * 客户端
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        //连接服务端,地址localhost:8000
        socketChannel.connect(new InetSocketAddress("localhost", 8000));
        //将hello字符串->byte[]->ByteBuffer->socketChannel
        socketChannel.write(StandardCharsets.UTF_8.encode("Hello world\nI'm zhangsan\nHow are you?\n"));
        System.out.println("waiting...");
        System.in.read();
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1559297.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UE4_碰撞_碰撞蓝图节点——Line Trace For Objects(对象的线条检测)

一、Line Trace For Objects&#xff08;对象的线条检测&#xff09;&#xff1a;沿给定线条执行碰撞检测并返回遭遇的首个命中&#xff0c;这只会找到由Object types指定类型的对象。注意他与Line Trace By Channel(由通道检测线条&#xff09;的区别&#xff0c;一个通过Obje…

解决AD使用交互式BOM插件时,插入make点导致显示异常的问题

记得上次写了一篇关于使用这个插件时出现这个问题的解决方法&#xff0c;具体可查看&#xff1a;AD使用交互式BOM插件时应该注意到的一个问题_ad的bom插件-CSDN博客 当时的解决办法就是删除后再运行脚本生成&#xff0c;这些天经过多次实验&#xff0c;发现是当时那个封装有问…

ES的RestClient相关操作

ES的RestClient相关操作 Elasticsearch使用Java操作。 本文仅介绍CURD索引库和文档&#xff01;&#xff01;&#xff01; Elasticsearch基础&#xff1a;https://blog.csdn.net/weixin_46533577/article/details/137207222 Elasticsearch Clients官网&#xff1a;https://ww…

实例、构造函数、原型、原型对象、prototype、__proto__、原型链……

学习原型链和原型对象&#xff0c;不需要说太多话&#xff0c;只需要给你看看几张图&#xff0c;你自然就懂了。 prototype 表示原型对象__proto__ 表示原型 实例、构造函数和原型对象 以 error 举例 图中的 error 表示 axios 抛出的一个错误对象&#xff08;实例&#xff0…

Makefile:动态库的编译链接与使用(六)

1、动态链接库 动态链接库&#xff1a;不会把代码编译到二进制文件中&#xff0c;而是运行时才去加载&#xff0c;所以只需要维护一个地址 动态&#xff1a;运行时才去加载&#xff0c;即所谓的动态加载连接&#xff1a;指库文件和二进制程序分离&#xff0c;用某种特殊的手段…

字符串的遍历,统计,反转.java

题目&#xff1a;键盘输入字符串&#xff0c;统计字符串所包含的大小写字母个数&#xff0c;及数字个数 分析&#xff1a;键盘输入字符串需next&#xff08;&#xff09;方法&#xff0c;利用fot循环遍历每个字符&#xff0c;返回字符串上的字符用charAt&#xff08;index&…

图论-最短路

一、不存在负权边-dijkstra算法 dijkstra算法适用于这样一类问题&#xff1a; 从起点 start 到所有其他节点的最短路径。 其实求解最短路径最暴力的方法就是使用bfs广搜一下&#xff0c;但是要一次求得所有点的最短距离我们不可能循环n次&#xff0c;这样复杂度太高&#xf…

Mac air 个人免费版VMWare Fusion安装及配置教程

Mac air 安装免费版VMWare Fusion教程及问题解决 1、下载VMWare Fusion2、下载wins镜像文件3、开始配置4、出现的问题及解决方法4.1 如何跳过启动时的网络连接4.2 启动后&#xff0c;无法连接网络怎么办4.3 怎么实现将文件拖拽到虚拟机中 当你手上是一台Mac电脑&#xff0c;却需…

薪酬、人数上不封顶,这家互联网大厂正在疯抢超级毕业生

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了人工智能中文站https://ai.weoknow.com 每天给大家更新可用的国内可用chatGPT资源 发布在https://it.weoknow.com 更多资源欢迎关注 又是一年一度校园春招季。在生成式 AI 一路狂飙的时代浪潮下&#xff0c;人工…

Linux: 进程地址空间究竟是什么?进程地址空间存在意义何在?

Linux: 进程地址空间究竟是什么&#xff1f; 一、内存究竟是什么&#xff1f;分为哪些&#xff1f;二、内存是真实物理空间&#xff1f;三、进程地址空间&#xff08;虚拟地址&#xff09;3.1 为何同一个变量地址相同&#xff0c;保存的数据却不同&#xff1f; 四、为什么需要地…

ssm012医院住院管理系统+vue

医院住院管理关系 摘 要 随着时代的发展&#xff0c;医疗设备愈来愈完善&#xff0c;医院也变成人们生活中必不可少的场所。如今&#xff0c;已经2021年了&#xff0c;虽然医院的数量和设备愈加完善&#xff0c;但是老龄人口也越来越多。在如此大的人口压力下&#xff0c;医院…

3.31总结

这两天对于java知识的学习又收获了一些新的东西&#xff0c;如内部类、抽象类、接口、权限修饰符、代码块、final. final final多用于方法、类、变量的修饰 方法&#xff1a;表示该方法是最终方法&#xff0c;不能被重写 类&#xff1a;表明该类是最终类&#xff0c;不能被…

latex伪代码一些记录

参考一 参考二 参考三 使用minipage 最终调整好的效果&#xff1a; $ \begin{document} \begin{center} \begin{minipage}{15.92cm} \renewcommand{\thealgorithm}{1} \begin{CJK}{GBK}{song} \begin{algorithm}[H]\caption{ \text{算法1&#xff1a;xxx}}\begin{algorith…

Linux 基础IO [缓冲区文件系统]

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux知识分享⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d; 目录 前言 一.Linux下一切皆文件 二.缓冲…

调试技巧安全预编译头文件(C++基础)

调试 调试可以选择条件调试和操作调试&#xff1a; 条件调试来选择条件进入断点设置&#xff0c;操作调试来使达到断点条件后完成某些操作&#xff08;一般是output窗口输出&#xff09;。 在这里就只输出了小于6的条件。 安全 降低崩溃、内存泄露、非法访问等问题。 应该转…

vue基础教程(5)——构建项目级登录页

同学们可以私信我加入学习群&#xff01; 正文开始 前言一、创建首页二、登录页代码讲解三、对应的vue知识点&#xff1a;四、附件-各文件代码总结 前言 前面我们已经把vue自带的页面删除&#xff0c;也搭建了最简单的router路由&#xff0c;下面就可以真正开发我们自己的项目…

蓝桥杯-python-常用库归纳

目录 日期和时间 datetime模块 date日期类&#xff0c;time时间类&#xff0c;datetime日期时间类 定义date&#xff08;年&#xff0c;月&#xff0c;日&#xff09; data之间的减法 定义时间&#xff08;时&#xff0c;分&#xff0c;秒&#xff09; 定义datetime&#xf…

文献学习-23-MRM:用于遗传学医学图像预训练的掩码关系建模

MRM: Masked Relation Modeling for Medical Image Pre-Training with Genetics Authors: Qiushi Yang, Wuyang Li, Baopu Li, Yixuan Yuan Source: ICCV 2023 Abstract: 关于自动多模态医疗诊断的 ODERN 深度学习技术依赖于大量的专家注释&#xff0c;这既耗时又令人望而却…

DeepL Pro3.1 下载地址及安装教程

DeepL Pro是DeepL公司推出的专业翻译服务。DeepL是一家专注于机器翻译和自然语言处理技术的公司&#xff0c;其翻译引擎被认为在质量和准确性方面表现优秀.DeepL Pro提供了一系列高级功能和服务&#xff0c;以满足专业用户的翻译需求。其中包括&#xff1a; 高质量翻译&#xf…

Python 常用内置库 time库、random库、turtle库

文章目录 一、time库二、random库三、turtle库1. 绘制正方形2. 使用海龟对象绘制六边形3. 绘制多个起点相同大小不同起点的五角星4. 绘制多个图形和添加文字 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、time库 time是最基础的时间处理库&#…