9.处理消息边界

网络编程中消息的长度是不太确定的,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();
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【pytest、playwright】多账号同时操作

目录 方案实现思路&#xff1a; 方案一&#xff1a; 方案二&#xff1a; 方案实现思路&#xff1a; 依照上图所见&#xff0c;就知道&#xff0c;一个账号是pytest-playwright默认的环境&#xff0c;一个是 账号登录的环境 方案一&#xff1a; 直接上代码&#xff1a; imp…

gtsam::Pose3的compose()函数作用

#include <gtsam/geometry/Pose3.h> #include <iostream> int main(int argc, char** argv) {// B 的旋转量为绕 x 轴旋转 180 度gtsam::Pose3 B gtsam::Pose3(gtsam::Rot3(0, 1, 0, 0), gtsam::Point3(1, 2, 0));// A 的旋转量为绕 z 轴旋转 180 度gtsam::Pose3 A…

MySQL中如何进行多表查询

目录 一、子查询 1.什么是子查询 2.注意事项 二、联结查询 1.什么是联结 2.内部联结&#xff08;等值联结&#xff09; ①WHERE语句 ②ON语句 3.自联结 4.自然联结 5.外部联结 三、组合查询 1.什么是组合查询 2.UNION规则 *本节涉及概念来源于图灵程序设计丛书&a…

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…

基于SpringBoot和Vue的学生笔记共享平台的设计与实现

今天要和大家聊的是一款基于SpringBoot和Vue的学生笔记共享平台的设计与实现 &#xff01;&#xff01;&#xff01; 有需要的小伙伴可以通过文章末尾名片咨询我哦&#xff01;&#xff01;&#xff01; &#x1f495;&#x1f495;作者&#xff1a;李同学 &#x1f495;&…

Linux中断管理:(一)中断号的映射

文章说明&#xff1a; Linux内核版本&#xff1a;5.0 架构&#xff1a;ARM64 参考资料及图片来源&#xff1a;《奔跑吧Linux内核》 Linux 5.0内核源码注释仓库地址&#xff1a; zhangzihengya/LinuxSourceCode_v5.0_study (github.com) 1. 中断控制器 Linux 内核支持众多…

TCP通信——端口转发(重点内容)

实现多人群聊 Client(客户端&#xff09;建立通信 package com.zz.tcp.case1;import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.util.Scanner;public class Client {public static void mai…

Java- maven下载jar包,提示找不到,Could not find artifact

1、执行下面命令行 mvn install:install-file -Dfile/home/quangang/桌面/isv-sdk-2.0.jar -DgroupIdcom.jd -DartifactIdisv-sdk -Dversion2.0 -Dpackangjar 2、然后这里要加上jar包

Linux(CentOS 7 )基于git、maven实现springboot自动化部署

前提 1、已安装git、maven、java环境 不清楚的可以看另一篇文章&#xff1a; https://blog.csdn.net/weixin_44646763/article/details/137041469 2、已为项目设置远程 git 仓库 origin (可以通过&#xff1a;git remote add origin https://github.com/xxx/xxx.git设置) 创…

MCRNet:用于乳腺超声成像语义分割的多级上下文细化网络

MCRNet&#xff1a;用于乳腺超声成像语义分割的多级上下文细化网络 摘要引言方法 MCRNet_ Multi-level context refinement network for semantic segmentation in breast ultrasound imaging 摘要 由于对比度差、目标边界模糊和大量阴影的不利影响&#xff0c;乳腺超声成像中…

基于ssm校园教务系统论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对校园教务信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差…

数据结构——优先级队列及多服务台模拟系统的实现

一、优先级队列的定义和存储 优先级队列定义&#xff1a;优先级高的元素在队头&#xff0c;优先级低的元素在队尾 基于普通线性表实现优先级队列&#xff0c;入队和出队中必有一个时间复杂度O(n),基于二叉树结构实现优先级队列&#xff0c;能够让入队和出队时间复杂度都为O(log…

WPF碎片

1、Style作为资源可放在控件自身资源下&#xff0c;也可以放在上级控件下如Window.Resources甚至Application.Resources下&#xff0c;但第二种方法需要加为Style添加key并通过Style"{StaticResource xxx}类似方式调用&#xff0c;而前者控件直接默认使用&#xff1b; 方法…

printf()对浮点数的四舍五入是有问题的!!!

一、问题描述 4.5四舍五入应该是5&#xff0c;8.5四舍五入应该是9 但是printf()函数以".f"和.lf打印&#xff0c;得到的却是4和8 二、问题演示 1、代码 #include<stdio.h> int main() {float f4.5;double d8.5;printf("%.f\n",f);printf("…

C语言实现猜数字游戏(有提示,限制次数版)

这次的猜数字游戏我添加了新的功能&#xff1a;为玩家添加了提示&#xff0c;以及输入数字的限制次数。 首先&#xff0c;我们的猜数字游戏需要一个菜单&#xff0c;来让玩家可以选择玩游戏还是退出游戏&#xff0c;所以我们需要开始就打印一个菜单&#xff1a; int main() {…

Linux之进程间通信

1.进程间通信的目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程 资源共享&#xff1a;多个进程之间共享同样的资源。 通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息&#xff0c;通知它&#xff08;它们&#xff09;发生了某种事件&#xff…

谷歌商店如何绑定银行卡!通过支付宝!

完整操作视频在B站&#xff1a; https://www.bilibili.com/video/BV1zt421g7pa/?spm_id_from333.337.search-card.all.click&vd_sourceb5a2563a2e562c5165936c011dcfd0a5 谷歌商店怎么支付&#xff01; 谷歌商店主要用来购买游戏和支付app的应用&#xff0c;由于都是采…

蓝桥杯省赛刷题——题目 2656:刷题统计

刷题统计OJ链接&#xff1a;蓝桥杯2022年第十三届省赛真题-刷题统计 - C语言网 (dotcpp.com) 题目描述 小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a 道题目&#xff0c;周六和周日每天做 b 道题目。请你帮小明计算&#xff0c;按照计划他将在第几…

P6学习:Oracle Primavera P6 OBS/责任人解析

前言 Primavera P6 EPPM 责任人用于管理 P6 企业项目组合管理 (EPPM) 系统中的项目所有权和权限。 Primavera P6 EPPM 中的所有项目都至少围绕三个结构进行组织&#xff1a;称为企业项目结构 (EPS) 的用于组织项目的结构、称为工作分解结构 (WBS) 的用于组织项目内活动的结构…

一篇讲明白 Hadoop 生态的三大部件

文章目录 每日一句正能量前言01 HDFS02 Yarn03 Hive04 HBase05 Spark及Spark Streaming关于作者推荐理由后记赠书活动 每日一句正能量 黎明时怀着飞扬的心醒来&#xff0c;致谢爱的又一天&#xff0c;正午时沉醉于爱的狂喜中休憩&#xff0c;黄昏时带着感恩归家&#xff0c;然后…