您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

netty入门到弹幕实战

时间:2022-10-02 19:36:51  来源:今日头条  作者:青杠山上

一、IO模型

  • 阻塞式IO模型
  • 非阻塞式IO模型
  • IO复用
  • 信号驱动式IO
  • 异步IO
  1. linux IO流程

 


2. 各个IO模型的比较

 

NIO的优势

  • 事件驱动模型 避免多线程 单线程处理多任务
  • 非阻塞,IO读写不再阻塞,而是返回0
  • 基于通道的传输,比基于流更有效率
  • 更高级的IO函数,零拷贝
  • IO多路复用大大提高了JAVA网络应用的可伸缩性和实用性

NIO的缺点

3.TCP粘包,拆包问题

问题原因:TCP协议发送时造成

 

.NETty解决方案:

 


4.Netty的零拷贝

传统拷贝方式

  1. 数据从磁盘读取到内核的read buffer
  2. 数据从内核缓冲区拷贝到用户缓冲区
  3. 数据从用户缓冲区拷贝到内核的socket buffer
  4. 数据从内核的socket buffer拷贝到网卡接口(硬件)的缓冲区

零拷贝

  1. 调用transferTo,数据从文件由DMA引擎拷贝到内核read buffer
  2. 接着DMA从内核read buffer将数据拷贝到网卡接口buffer

Netty中的零拷贝体现在这三个方面:

1.bytebuffer

Netty发送和接收消息主要使用bytebuffer,bytebuffer使用对外内存(DirectMemory)直接进行Socket读写。

2.Composite Buffers

传统的ByteBuffer,如果需要将两个ByteBuffer中的数据组合到一起,我们需要首先创建一个size=size1+size2大小的新的数组,然后将两个数组中的数据拷贝到新的数组中。但是使用Netty提供的组合ByteBuf,就可以避免这样的操作,因为CompositeByteBuf并没有真正将多个Buffer组合起来,而是保存了它们的引用,从而避免了数据的拷贝,实现了零拷贝。

3.对于FileChannel.transferTo的使用

Netty中使用了FileChannel的transferTo方法,该方法依赖于操作系统实现零拷贝。

二、Netty组件

  1. Channel – 对应NIO中的channel
  2. EventLoop --对应NIO中的while循环
  3. ChannelHandler和ChannelPipline --对应NIO客户逻辑实现handleRead和handleWrite
  4. ByteBuf --对应Nio中的ByteBuffer
  5. BootStrap和BootServerStrap --对应NIO中的Selecter、ServerSocketChannel等的创建、配置、启动

Reactor线程模型

Rector线程模型有三种形式:

1.单线程模型:

 

2.多线程模型:

 

3.mutiple模型

 

Netty对这三种模式都有支持

三、简单的例子

1.引入pom包

 <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.42.Final</version>
        </dependency>

2.服务器端

public class TimeServer {

    public void bind(int port) throws InterruptedException {
        EventLoopGroup boosGroup=new NioEventLoopGroup();
        EventLoopGroup workerGroup=new NioEventLoopGroup();

        try{
            ServerBootstrap b=new ServerBootstrap();
            b.group(boosGroup,workerGroup)
                    .channel(NIOServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,1024)
                    .childHandler(new ChildChannelHandler());

            //绑定端口,同步等待成功
            ChannelFuture future = b.bind(port).sync();
            //等待服务端监听关闭
            future.channel().closeFuture().sync();
        }finally {
            //优雅退出,释放线程池资源
            boosGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void mAIn(String[] args) throws InterruptedException {
        new TimeServer().bind(8888);
    }


    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            socketChannel.pipeline().addLast(new TimeServerHandler());
        }
    }
}

public class TimeServerHandler extends ChannelInboundHandlerAdapter {


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf=(ByteBuf)msg;
        byte[] req=new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body=new String(req,"UTF-8");
        System.out.println(body);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String currentTime=simpleDateFormat.format(new Date());
        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
        ctx.write(resp);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
}

实战弹幕系统

java:


public class WebSocketDanmuServer {

    private int port;

    public WebSocketDanmuServer(int port) {
        this.port = port;
    }

    public void run(){
        EventLoopGroup bossGroup=new NioEventLoopGroup(1);
        EventLoopGroup workGroup=new NioEventLoopGroup(8);
        try {
        ServerBootstrap b=new ServerBootstrap();
        b.group(bossGroup,workGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new WebsocketDanmuServerInitializer())
                .option(ChannelOption.SO_BACKLOG,128)
                .childOption(ChannelOption.SO_KEEPALIVE,true);

        System.out.println("弹幕系统启动了 "+port);


            ChannelFuture future = b.bind(port).sync();

            future.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            workGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new WebSocketDanmuServer(8080).run();
    }
}

public class WebsocketDanmuServerInitializer extends ChannelInitializer<SocketChannel> {


    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast("http-decodec",new HttpRequestDecoder());
        pipeline.addLast("http-aggregator",new HttpObjectAggregator(65536));
        pipeline.addLast("http-encodec",new HttpResponseEncoder());
        pipeline.addLast("http-chunked",new ChunkedWriteHandler());
       /*
			pipeline.addLast(new HttpServerCodec());
			pipeline.addLast(new HttpObjectAggregator(64*1024));
			pipeline.addLast(new ChunkedWriteHandler());
		*/
        pipeline.addLast("http-request",new HttpRequestHandler("/ws"));
        pipeline.addLast("WebSocket-protocol",new WebSocketServerProtocolHandler("/ws"));
        pipeline.addLast("WebSocket-request",new TextWebSocketFrameHandler());
    }
}
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private final String wsUri;
    private static final File INDEX;


    static {
        URL location=HttpRequestHandler.class.getProtectionDomain().getCodeSource().getLocation();
        try {
            String path = location.toURI() + "WebsocketDanMu.html";
            path = !path.contains("file:") ? path : path.substring(5);
            INDEX = new File(path);
        } catch (URISyntaxException e) {
            throw new IllegalStateException("Unable to locate WebsocketChatClient.html", e);
        }
    }

    public HttpRequestHandler(String wsUri) {
        this.wsUri = wsUri;
    }

    private static void send100Continue(ChannelHandlerContext ctx) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
        ctx.writeAndFlush(response);
    }


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        if(wsUri.equalsIgnoreCase(request.getUri())){
            ctx.fireChannelRead(request.retain());
        }else {
            if(HttpHeaders.is100ContinueExpected(request)){
                send100Continue(ctx);
            }
            RandomaccessFile file = new RandomAccessFile(INDEX, "r");//4

            HttpResponse response = new DefaultHttpResponse(request.getProtocolVersion(), HttpResponseStatus.OK);
            response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=UTF-8");

            boolean keepAlive = HttpHeaders.isKeepAlive(request);

            if (keepAlive) {                                        //5
                response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, file.length());
                response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
            }
            ctx.write(response);                    //6

            if (ctx.pipeline().get(SslHandler.class) == null) {     //7
                ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));
            } else {
                ctx.write(new ChunkedNioFile(file.getChannel()));
            }
            ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);           //8
            if (!keepAlive) {
                future.addListener(ChannelFutureListener.CLOSE);        //9
            }

            file.close();
        }
        }


}

public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {


    public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        Channel incoming=ctx.channel();
        for (Channel channel:channels){
            if(channel!=incoming){
                channel.writeAndFlush(new TextWebSocketFrame(msg.text()));
            }else {
                channel.writeAndFlush(new TextWebSocketFrame("我发送的 "+msg.text()));
            }
        }
    }


    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  // (2)
        Channel incoming = ctx.channel();

        // Broadcast a message to multiple Channels
        channels.writeAndFlush(new TextWebSocketFrame("[SERVER] - " + incoming.remoteAddress() + " 加入"));

        channels.add(incoming);
        System.out.println("Client:"+incoming.remoteAddress() +"加入");
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  // (3)
        Channel incoming = ctx.channel();

        // Broadcast a message to multiple Channels
        channels.writeAndFlush(new TextWebSocketFrame("[SERVER] - " + incoming.remoteAddress() + " 离开"));

        System.err.println("Client:"+incoming.remoteAddress() +"离开");

        // A closed Channel is automatically removed from ChannelGroup,
        // so there is no need to do "channels.remove(ctx.channel());"
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception { // (5)
        Channel incoming = ctx.channel();
        System.out.println("Client:"+incoming.remoteAddress()+"在线");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception { // (6)
        Channel incoming = ctx.channel();
        System.err.println("Client:"+incoming.remoteAddress()+"掉线");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)	// (7)
            throws Exception {
        Channel incoming = ctx.channel();
        System.err.println("Client:"+incoming.remoteAddress()+"异常");
        // 当出现异常就关闭连接
        cause.printStackTrace();
        ctx.close();
    }


}

html:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta name="Keywords" content="danmu">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>弹幕网站</title>
    <style type="text/css">
        body {
            background: url(http://ot0ak6eri.bkt.clouddn.com/01.jpg); no-repeat:top center;
            font-size: 12px;
            font-family: "微软雅黑";
        }


        * {
            margin: 0;
            padding: 0;
        }
        /* screen start*/
        .screen {
            width: 300px;
            height: 100px;
            background: #669900;
        }


        .dm {
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            display: none;
        }


        .dm .d_screen .d_del {
            width: 38px;
            height: 38px;
            background: #600;
            display: block;
            text-align: center;
            line-height: 38px;
            text-decoration: none;
            font-size: 20px;
            color: #fff;
            border-radius: 19px;
            border: 1px solid #fff;
            z-index: 2;
            position: absolute;
            right: 20px;
            top: 20px;
            outline: none;
        }


        .dm .d_screen .d_del:hover {
            background: #F00;
        }


        .dm .d_screen .d_mask {
            width: 100%;
            height: 100%;
            background: #000;
            position: absolute;
            top: 0;
            left: 0;
            opacity: 0.6;
            filter: alpha(opacity = 60);
            z-index: 1;
        }


        .dm .d_screen .d_show {
            position: relative;
            z-index: 2;
        }


        .dm .d_screen .d_show div {
            font-size: 26px;
            line-height: 36px;
            font-weight: 500;
            position: absolute;
            top: 76px;
            left: 10;
            color: #fff;
        }
        /*end screen*/


        /*send start*/
        .send {
            width: 100%;
            height: 76px;
            position: absolute;
            bottom: 0;
            left: 0;
            border: 1px solid red;
        }


        .send .s_filter {
            width: 100%;
            height: 76px;
            background: #000;
            position: absolute;
            bottom: 0;
            left: 0;
            opacity: 0.6;
            filter: alpha(opacity = 60);
        }


        .send  .s_con {
            width: 100%;
            height: 76px;
            position: absolute;
            top: 0;
            left: 0;
            z-index: 2;
            text-align: center;
            line-height: 76px;
        }


        .send .s_con .s_text {
            width: 800px;
            height: 36px;
            border: 0;
            border-radius: 6px 0 0 6px;
            outline: none;
        }


        .send .s_con .s_submit {
            width: 100px;
            height: 36px;
            border-radius: 0 6px 6px 0;
            outline: none;
            font-size: 14px;
            color: #fff;
            background: #65c33d;
            font-family: "微软雅黑";
            cursor: pointer;
            border: 1px solid #5bba32;
        }


        .send .s_con .s_submit:hover {
            background: #3eaf0e;
        }
        /*end send*/
    </style>


</head>
<body>
<a href="#" id="startDm">开启弹幕</a>
<!-- dm start -->
<div class="dm">
    <!-- d_screen start -->
    <div class="d_screen">
        <a href="#" class="d_del">X</a>
        <div class="d_mask"></div>
        <div class="d_show">
        </div>
    </div>
    <!-- end d_screen -->


    <!-- send start -->
    <div class="send">
        <div class="s_filter"></div>
        <div class="s_con">
            <input type="text" class="s_text" /> <input type="button"
                                                        value="发表评论" class="s_submit" id="btn"/>
        </div>
    </div>
    <!-- end send -->
</div>
<!-- end dm-->
<script type="text/JavaScript"
        src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.0.js"></script>

<script type="text/javascript" >

    String.prototype.endWith=function(str){
        if(str==null||str==""||this.length==0||str.length>this.length)
            return false;
        if(this.substring(this.length-str.length)==str)
            return true;
        else
            return false;
        return true;
    }

    String.prototype.startWith=function(str){
        if(str==null||str==""||this.length==0||str.length>this.length)
            return false;
        if(this.substr(0,str.length)==str)
            return true;
        else
            return false;
        return true;
    }
</script>
<!--<script type="text/javascript" src="websocket.js"></script>-->
<script type="text/javascript">
    $(function() {

        $("#startDm,.d_del").click(function() {
            $("#startDm,.dm").toggle(1000);
//init_screen();
        });
        $("#btn").click(function(){
            send();
        });
        $(".s_text").keydown(function() {
            var code = window.event.keyCode;





            if (code == 13)//回车键按下时,输出到弹幕


            {
                send();
            }
        });


    });


    function launch()
    {

        var _height = $(window).height();
        var _left = $(window).width() - $("#"+index).width();
        var time=10000;
        if(index%2==0)
            time=20000;
        _top+=80;
        if(_top>_height-100)
            _top=80;
        $("#"+index).css({
            left:_left,
            top:_top,
            color:getRandomColor()

        });
        $("#"+index).animate({
                left:"-"+_left+"px"},
            time,
            function(){});
        index++;
    }



    /* //初始化弹幕
     function init_screen() {
     var _top = 0;
     var _height = $(window).height();
     $(".d_show").find("div").show().each(function() {
     var _left = $(window).width() - $(this).width();
     var time=10000;
     if($(this).index()%2==0)
     time=20000;
     _top+=80;
     if(_top>_height-100)
     _top=80;
     $(this).css({
     left:_left,
     top:_top,
     color:getRandomColor()

     });
     $(this).animate({
     left:"-"+_left+"px"},
     time,
     function(){});


     });
     } */

    //随机获取颜色值
    function getRandomColor() {
        return '#' + (function(h) {
            return new Array(7 - h.length).join("0") + h
        })((Math.random() * 0x1000000 << 0).toString(16))
    }
</script>

<script type="text/javascript">

    var websocket=null;
    var _top=80;
    var index=0;

    var host=window.location.host;
    //判断当前浏览器是否支持WebSocket
    if('WebSocket' in window){
        websocket=new WebSocket("ws://"+host+"/ws");
    }
    else{
        alert("Not Support WebSocket!");
    }


    //连接发生错误的回调方法
    websocket.onerror = function(){
        setMessageInnerHTML("error");
    };

    //连接成功建立的回调方法
    websocket.onopen = function(event){
        setMessageInnerHTML("open");
    }

    //接收到消息的回调方法
    // 收到服务器发送的消息
    websocket.onmessage = function(){
        setMessageInnerHTML(event.data);
    }

    //连接关闭的回调方法
    websocket.onclose = function(){
        setMessageInnerHTML("close");
    }


    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function(){
        websocket.close();
    }


    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML){
        //修改背景图
        var imgurl;
        if (innerHTML.startWith("~background,")) {
            var cmd = innerHTML;
            imgurl = cmd.split(",")[1];
            document.body.style.background = "url("+imgurl+")";
        }else{
            $(".d_show").Append("<div id='"+index+"'>"+ innerHTML + "</div>");
        }

        launch();
    }


    //发送消息
    function send(){
        //var message = document.getElementById('text').value;
        var message = $(".s_text").val();
        $(".s_text").val("");
        websocket.send(message);
    }
</script>

</body>
</html>


Tags:netty   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Netty入门实践:模拟IM聊天
我们使用的框架几乎都有网络通信的模块,比如常见的Dubbo、RocketMQ、ElasticSearch等。它们的网络通信模块使用Netty实现,之所以选择Netty,有两个主要原因: Netty封装了复杂的JD...【详细内容】
2023-12-08  Search: netty  点击:(241)  评论:(0)  加入收藏
Netty入门实践-模拟IM聊天
我们使用的框架几乎都有网络通信的模块,比如常见的Dubbo、RocketMQ、ElasticSearch等。它们的网络通信模块使用Netty实现,之所以选择Netty,有2个主要原因: Netty封装了复杂的JDK...【详细内容】
2023-12-05  Search: netty  点击:(108)  评论:(0)  加入收藏
如何使用Netty模拟一个Web服务端
Netty作为Web服务端具有以下好处:高性能Netty是一个基于事件驱动和异步非阻塞的网络编程框架,它使用了高效的NIO(非阻塞输入输出)模型。这使得Netty在处理大量并发连接时表现出...【详细内容】
2023-09-11  Search: netty  点击:(272)  评论:(0)  加入收藏
玩转Netty,从“Hello World”开始!
为什么要用Netty?首先当然是NIO的使用,本身比较复杂,而且还存在一些问题。除此之外,如果在项目的开发中,要实现稳定的网络通信,就得考虑网络的闪断、客户端的重复接入、客户端的...【详细内容】
2023-05-16  Search: netty  点击:(417)  评论:(0)  加入收藏
Netty和原生Java的性能比较
Netty是一种基于异步事件循环的网络应用编程方法。本文对比Netty与Java的本地服务器。虽然目前本地服务器使用的人不多,但我仍要找出netty比本地服务器的好处有多少。让我们...【详细内容】
2023-05-16  Search: netty  点击:(88)  评论:(0)  加入收藏
基于Netty搭建WebSocket,模仿微信聊天页面
作者:小傅哥 博客:https://bugstack.cn 沉淀、分享、成长,让自己和他人都能有所收获!前言介绍 本章节我们模仿微信聊天页面,开发一个基于Netty搭建WebSocket通信案例。Netty的应...【详细内容】
2023-03-17  Search: netty  点击:(237)  评论:(0)  加入收藏
Netty:遇到TCP发送缓冲区满了 写半包操作该如何处理
什么是写半包写半包:一份数据,一次发送没有把他全部发送,需要循环发送,那么第一次的操作称为写半包什么情况下会出现写半包:发送方发送200byte,但是接收方只能接受100byte,因此发送...【详细内容】
2023-03-13  Search: netty  点击:(209)  评论:(0)  加入收藏
Java使用Netty框架自建DNS代理服务器教程
前言DNS协议作为着互联网客户端-服务器通信模式得第一关,在当下每天都有成千上亿上网记录产生得当今社会,其重要性自然不可言喻。在国内比较有名得DNS服务器有电信得114.114.1...【详细内容】
2023-02-09  Search: netty  点击:(149)  评论:(0)  加入收藏
Netty基础介绍(使用场景、组件、模型、代码示例等)
Netty简介Netty 对 JDK 自带的 NIO 的 API 进行了良好的封装,解决了如客户端面临断线重连、 网络闪断、心跳处理、半包读写、 网络拥塞和异常流的处理等等问题。且Netty拥有...【详细内容】
2023-01-10  Search: netty  点击:(146)  评论:(0)  加入收藏
从Redis、HTTP协议,看Nett协议设计,我发现了个惊天大秘密
1. 协议的作用TCP/IP 中消息传输基于流的方式,没有边界协议的目的就是划定消息的边界,制定通信双方要共同遵守的通信规则2. Redis 协议如果我们要向 Redis 服务器发送一条 set...【详细内容】
2023-01-03  Search: netty  点击:(308)  评论:(0)  加入收藏
▌简易百科推荐
Qt与Flutter:在跨平台UI框架中哪个更受欢迎?
在跨平台UI框架领域,Qt和Flutter是两个备受瞩目的选择。它们各自具有独特的优势,也各自有着广泛的应用场景。本文将对Qt和Flutter进行详细的比较,以探讨在跨平台UI框架中哪个更...【详细内容】
2024-04-12  刘长伟    Tags:UI框架   点击:(1)  评论:(0)  加入收藏
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  京东云开发者    Tags:Web Components   点击:(8)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  InfoQ    Tags:Kubernetes   点击:(19)  评论:(0)  加入收藏
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  风舞凋零叶    Tags:Spring Security   点击:(55)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  贝格前端工场    Tags:框架   点击:(47)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  程序员wayn  微信公众号  Tags:Spring   点击:(39)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  云云众生s  微信公众号  Tags:Kubernetes   点击:(51)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  大噬元兽  微信公众号  Tags:框架   点击:(68)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  HELLO程序员  微信公众号  Tags:Spring   点击:(88)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19   Java中文社群  微信公众号  Tags:SpringBoot   点击:(86)  评论:(0)  加入收藏
站内最新
站内热门
站内头条