netty socket超时设置_基于Netty实现Web容器Netty版Tomcat(三)

一首《感谢你曾经来过》,谢谢支持。。。。。

本次所涉及代码已上传至GitHub: https://github.com/lgli/lgli-netty-tomcat/tree/master 前面已经对于IO的发展历程和基本原理,及其一些小例子已经说得差不多了,今天需要进入主题了,在这之前,再次回忆下各种版本的聊天小程序小例子,因为今天的Netty入门级也是从聊天室开始的。 传送门: 传统IO方式 NIO方式 AIO方式 从最开始的同步阻塞,同步非阻塞,到最后的异步非阻塞 这里简单总结下几种方式: 传统IO《BIO》: 当客户端发起一个TCP连接,客户端通过java.net.ServerSocket#accept获取到一个java.net.Socket对象,于是在服务端启动一个独立的线程来处理这个java.net.Socket的所有内容,包括读写数据;当启动多个客户端连接的时候,服务端的线程数量等同客户端连接数量,这种Thread Per Request模式带来的问题就是大量的线程将给服务器带来巨大的线程开销,为了避免这个问题,可以采用线程池的方式来解决,但是线程池也会带来一个问题,假定线程池线程数量是500,此时刚好有500个客户端连接好服务端并在进行数据交互,此时第501个客户端的请求也是无法处理的,即这种方式不能较好的解决高并发的问题,模式的方式如下图所示:

cbffa22eeb73bc6617593dab3fadea32.png

NIO

NIO启用了一个多路复用的概念,即将所有连接通道都注册在一个

java.nio.channels.Selector上,然后通过轮询

java.nio.channels.Selector中的状态来进行对应的操作

如下图所示:

fb4f978c0fcbc76de2aee31626f4e829.png

java.nio.channels.Selector的底层模型是一个基于Reactor反应堆的一种实现方式,在这里先提醒这么一个概念,后面会详述这个模型

AIO

AIO则是主要针对于在同步阻塞的等待数据读写方式上的一种改变,即不在同步阻塞的等待底层操作系统对数据的操作结果。而是通过传入回调函数来执行成功或者失败之后应该做的事情,其底层主要基于Proactor模型。

基于相对较复杂的NIO操作,Netty诞生了,除了对NIO进行优化和封装之外,Netty还可以随意切换多种不同的网络协议,提供很多标准的协议、安全、编码解码的支持,解决了很多 NIO 不易用的问题,同时应用于多种大型框架内,比如Dubbo、RocketMQ等。

先来一个基于Netty的聊天室改造

服务端

package com.lgli.netty.chart;import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.*;import io.netty.channel.group.ChannelGroup;import io.netty.channel.group.DefaultChannelGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioServerSocketChannel;import io.netty.handler.codec.DelimiterBasedFrameDecoder;import io.netty.handler.codec.Delimiters;import io.netty.handler.codec.string.StringDecoder;import io.netty.handler.codec.string.StringEncoder;import io.netty.util.concurrent.GlobalEventExecutor;import java.net.InetSocketAddress;/** * ChartNettyServer * @author lgli */public class ChartNettyServer {    public static void main(String[] args){        //配置服务端的NIO线程组        EventLoopGroup bossGroup = new NioEventLoopGroup();        EventLoopGroup workerGroup = new NioEventLoopGroup();        ServerBootstrap bootstrap = new ServerBootstrap();        try{            bootstrap.group(bossGroup,workerGroup)                    .channel(NioServerSocketChannel.class)                    //针对主线程的配置,分配最大线程数                    .option(ChannelOption.SO_BACKLOG,128)                    //针对子线程的配置 保持长连接                    .option(ChannelOption.SO_KEEPALIVE,true)                    .childHandler(new MyChannelInitial());            //绑定端口,同步等待成功            ChannelFuture future = bootstrap.bind(new InetSocketAddress("localhost", 8080)).sync();            //等待服务端监听端口关闭            future.channel().closeFuture().sync();        } catch (Exception e) {            e.printStackTrace();        } finally {            //出现异常,则释放资源            bossGroup.shutdownGracefully();            workerGroup.shutdownGracefully();        }    }    static class MyChannelInitial extends ChannelInitializer{        protected void initChannel(SocketChannel socketChannel) throws Exception {            System.out.println("client connection :" + socketChannel.remoteAddress());            socketChannel.pipeline()                    .addLast("frame",new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()))                    .addLast("decode",new StringDecoder())                    .addLast("encode",new StringEncoder())                    .addLast("handler",new MyHandler());        }    }    static class MyHandler extends SimpleChannelInboundHandler{        /**         * 保存所有的连接         */        private static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);        /**         * 接收到数据,发送给其他服务端         * @param channelHandlerContext 发送数据的通道         * @param s 发送的数据         * @throws Exception         */        protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {            Channel inputChannel = channelHandlerContext.channel();            for(Channel channel : channels){                if(channel == inputChannel){                    continue;                }                channel.writeAndFlush(inputChannel.remoteAddress()+":"+s+"\n");            }        }        /**         * 有新的连接连接进来         * @param ctx         * @throws Exception         */        @Override        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {            Channel inChannel = ctx.channel();            //通知其他服务器有新的上线了            for(Channel channel : channels){                channel.writeAndFlush("欢迎"+inChannel.remoteAddress()+"进入聊天室! \n");            }            inChannel.writeAndFlush("欢迎您进入聊天室!\n");            channels.add(inChannel);        }        @Override        public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {            //通知其他服务器,下线了            Channel outChannel = ctx.channel();            for(Channel channel : channels){                if(outChannel == channel){                    continue;                }                channel.writeAndFlush(outChannel.remoteAddress()+"下线了 \n");            }            channels.remove(outChannel);        }    }}

这里对代码做一点解释:

Main方法入口

27-28行:配置服务端主线程和工作线程组

29行:创建启动类

31-37行:方法需要传入配置的2个线程组,设置通道类型为

io.netty.channel.socket.nio.NioServerSocketChannel

根据主线程配置,设置最大线程数

针对子线程配置,保持长连接

传入事件分发Handler

b395987de7859e843678805139ff1ff5.png

39-40行:绑定服务,端口,等待服务端监听端口关闭

99ca5f2bd92125edfc152abf2f79337a.png

46-47行:出现异常,关闭线程组,释放资源

66df8a6cc6fd151625ba9c1c3d82fcbb.png

下面看下事件分发Handler:

需要是io.netty.channel.ChannelHandler类型的对象,

这里选择自定一个对象,com.lgli.netty.chart.ChartNettyServer.MyChannelInitial

继承于io.netty.channel.ChannelInitializer

e9ce0c216840d267c5b40b4bb40c3f0e.png

上图显示了自定义对象和两者之间的关系

56-60行:

TCP数据处理方式

    这个处理TCP粘包粘包的类,在后续Netty正式篇中会详细说到

加解密方式

自定义Handler

bfcc779938e2eccb741ea528ae6eefee.png

自定义Handler主要做了3件事:接收数据时候的处理,有新的连接以及客户端下线的处理

com.lgli.netty.chart.ChartNettyServer.MyHandler#channelRead0

78-86行:接收数据的时候,将接收到的数据,发送给除数据来源的所有其他客户端

c47a1e11a2c33549f6d57518592e3438.png

com.lgli.netty.chart.ChartNettyServer.MyHandler#handlerAdded

95-103行:有新的客户端进入,则通知除新的客户端之外的其他客户端,同时,添加新的客户端保存待用

com.lgli.netty.chart.ChartNettyServer.MyHandler#handlerRemoved

106-116行:有客户端下线,则通知其他还在线的所有客户端

c073bf2e5cbfea41c765a9538d313d9f.png

看客户端代码:

package com.lgli.netty.chart;import io.netty.bootstrap.Bootstrap;import io.netty.channel.*;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioSocketChannel;import io.netty.handler.codec.DelimiterBasedFrameDecoder;import io.netty.handler.codec.Delimiters;import io.netty.handler.codec.string.StringDecoder;import io.netty.handler.codec.string.StringEncoder;import java.net.InetSocketAddress;import java.util.Scanner;/** * ChartNettyClient * @author lgli */public class ChartNettyClient {    public static void main(String[] args) {        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();        Bootstrap bootstrap = new Bootstrap();        try{            bootstrap.group(eventLoopGroup)                    .channel(NioSocketChannel.class)                    .handler(new MyChannelInitial());            Channel channel = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync().channel();            Scanner scanner = new Scanner(System.in);            while(scanner.hasNextLine()){                String s = scanner.nextLine();                if("".equals(s)){                    continue;                }                channel.writeAndFlush(s+"\n");            }        }catch (Exception e){            e.printStackTrace();        }finally {            eventLoopGroup.shutdownGracefully();        }    }    static class MyChannelInitial extends ChannelInitializer{        protected void initChannel(SocketChannel socketChannel) throws Exception {            socketChannel.pipeline()                    .addLast("frame",new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()))                    .addLast("decode",new StringDecoder())                    .addLast("encode",new StringEncoder())                    .addLast("handler",new MyChannelHandler());        }    }    static class MyChannelHandler extends SimpleChannelInboundHandler{        protected void channelRead0(ChannelHandlerContext channelHandlerContext, String o) throws Exception {            System.out.println(o);        }    }}

同样的,Main方法开始

23-24行:配置客户端线程组和客户端启动类

26-28行:配置相关参数,具体见服务端说明

31-38行:监听客户端键盘数据,向服务端发送数据

44行:出现异常则退出线程组

300475240fa1bedcfc4b5b7d53e4ff14.png

自定义事件分发Handler和服务端类似

TCP数据处理方式

加解密方式

自定义Handler

8518d5723a44b62b791c5c6fab63e168.png

自定义Handler,这里选择直接打印输出:

13057d1667f7de5f3cc67a989dd4c904.png

运行服务端和客户端程序,可以有以下效果:

基于Netty的简易Tomcat实现

这里实现了一个很简单的Tomcat功能,即监听端口,然后接受请求,根据请求分发请求对应的处理器

基本思路:

df08a4a54ffee20ff9ba51c568a3a2d2.png

启动类:

package com.lgli.netty.tomcat;import com.lgli.netty.tomcat.http.NettyRequest;import com.lgli.netty.tomcat.http.NettyResponse;import com.lgli.netty.tomcat.servlet.NettyServlet;import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.*;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioServerSocketChannel;import io.netty.handler.codec.http.HttpRequest;import io.netty.handler.codec.http.HttpRequestDecoder;import io.netty.handler.codec.http.HttpResponseEncoder;import java.io.InputStream;import java.net.InetSocketAddress;import java.util.Enumeration;import java.util.HashMap;import java.util.Map;import java.util.Properties;/** * NettyTomcat * @author lgli */public class NettyTomcat {    private static Map maps = new HashMap<>(16);    public NettyTomcat() {        try{            //初始化资源信息            Properties properties = new Properties();            InputStream in = NettyTomcat.class.getClassLoader().getResourceAsStream("netty-tomcat.properties");            properties.load(in);            Enumeration> enumeration = properties.propertyNames();            while(enumeration.hasMoreElements()){                Object o = enumeration.nextElement();                if(!(o instanceof String)){                    continue;                }                String url = (String)o;                if(!url.endsWith("-url")){                    continue;                }                Object urlClass = Class.forName(properties.getProperty(url.replace("url", "class"))).newInstance();                if(!(urlClass instanceof NettyServlet)){                    continue;                }                maps.put(properties.getProperty(url),(NettyServlet) urlClass);            }        }catch (Exception e){            e.printStackTrace();        }    }    private void monitor() {        EventLoopGroup bossGroup = new NioEventLoopGroup();        EventLoopGroup workerGroup = new NioEventLoopGroup();        ServerBootstrap serverBootstrap = new ServerBootstrap();        try{            serverBootstrap.group(bossGroup,workerGroup)                    .channel(NioServerSocketChannel.class)                    .option(ChannelOption.SO_BACKLOG,128)                    .childOption(ChannelOption.SO_KEEPALIVE,true)                    .childHandler(new NettyTomcatChannelInitial());            Channel channel = serverBootstrap.bind(new InetSocketAddress("localhost", 8080)).sync().channel();            channel.closeFuture().sync();        }catch (Exception e){            e.printStackTrace();        }finally {            bossGroup.shutdownGracefully();            workerGroup.shutdownGracefully();        }    }    static class NettyTomcatChannelInitial extends ChannelInitializer{        @Override        protected void initChannel(SocketChannel socketChannel) throws Exception {            socketChannel.pipeline().addLast("encode",new HttpResponseEncoder())                    .addLast("decode",new HttpRequestDecoder())                    .addLast("handler",new NettyTomcatHandler());        }    }    static class NettyTomcatHandler extends SimpleChannelInboundHandler{        @Override        protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {            if(!(o instanceof HttpRequest)){                return;            }            HttpRequest httpRequest = (HttpRequest) o;            NettyRequest request = new NettyRequest(channelHandlerContext,httpRequest);            NettyResponse response = new NettyResponse(channelHandlerContext,httpRequest);            String url = request.getUrl();            if(maps.containsKey(url)){                maps.get(url).service(request,response);            }else{                response.write("404 Not Found,找不到资源");            }        }    }    public static void main(String[] args) {        new NettyTomcat().monitor();    }}

com.lgli.netty.tomcat.NettyTomcat构造方法中,主要是初始化资源信息:

9cc8df8790584a95f18f75b68a25d8e0.png

即读取配置资源文件,同时将请求和对应的类的实例保存在一个Map中

367b50fe5433ffb262a42f07b77d5854.png

初始化线程组和初始化启动对象,前面已经提到,这里也不再过多解释:

a9abda60226a32a7ad22635aabb1c773.png

需要对这个Handler做一点说明:

3ea27da007e087546c69ca3da0315d39.png

当有客户端发来请求的时候,

90-93行:这里直接根据Netty中的

io.netty.handler.codec.http.HttpRequest

接收

94-95行:用两个类似平常用的比较多的

HttpServletRequest和HttpServletResponse

实现,即

NettyRequest和NettyResponse

96-101行:根据初始化的请求路径和处理对象类,对比这里接收到的请求路径,获取到处理对象类的实例,执行其service方法

最后主类方法则执行完毕

下面看下自定义的NettyRequest和NettyResponse

NettyRequest:

package com.lgli.netty.tomcat.http;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.http.HttpRequest;/** * NettyRequest * @author lgli */public class NettyRequest {    private ChannelHandlerContext ctx;    private HttpRequest request;    public NettyRequest(ChannelHandlerContext channelHandlerContext, HttpRequest httpRequest) {        this.ctx = channelHandlerContext;        this.request = httpRequest;    }    public String getRequestMethod() {        return request.method().name();    }    public String getUrl() {        return request.uri();    }}

NettyRequest主要调用

io.netty.handler.codec.http.HttpRequest

的API操作获取请求名和请求uri

NettyResponse

package com.lgli.netty.tomcat.http;import io.netty.buffer.Unpooled;import io.netty.buffer.UnpooledDirectByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.http.*;import java.io.UnsupportedEncodingException;import java.nio.charset.StandardCharsets;/** * NettyResponse * @author lgli */public class NettyResponse {    private ChannelHandlerContext ctx;    public NettyResponse(ChannelHandlerContext channelHandlerContext) {        this.ctx = channelHandlerContext;    }    public void write(String result) {        if(null == result || "".equalsIgnoreCase(result)                || "".equalsIgnoreCase(result.trim())){            return;        }        try{            FullHttpResponse response = new DefaultFullHttpResponse(                    HttpVersion.HTTP_1_1,                    HttpResponseStatus.OK,                    Unpooled.wrappedBuffer(result.getBytes("GBK"))            );            response.headers().set("Content-Type","text/html");            ctx.write(response);        }catch (Exception e){            e.printStackTrace();        }finally {            ctx.flush();            ctx.close();        }    }}

看write方法

32-37行:NettyResponse调用了

io.netty.handler.codec.http.FullHttpResponse

封装返回结果

38行:

io.netty.channel.ChannelHandlerContext

输出数据到客户端《即浏览器》

剩下用到的3个Servlet:

总的父类NettyServlet

4d90825f067ff8611b6a4f211278df69.png

第一个OneServlet

945b389ebf9a31e3b1e567b3467a30c8.png

第二个TwoServlet

ccf4240d6912e05368ed578d9c81dd71.png

执行NettyTomcat主类,开启基于Netty简易版的Tomcat:

后续,将深入了解Netty底层及其应用,还有比较关注的Netty性能调优!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值