第3章_Java仿微信全栈高性能后台+移动客户端

   

 

当服务器构建完毕而且启动以后,咱们经过网页URL地址就能够访问这台服务器,而且服务器会向网页输出Hello Netty这样几个字。前端

Netty有三种线程模型:单线程、多线程、主从线程。Netty官方推荐使用主从线程组,由于主从线程组比较高效。由于任何的服务器,不论是tomcat仍是Jetty,都会有一个启动的类bootstrap。这样的一个Server类咱们也会经过Netty在咱们的服务器里面去进行一个设置,去打开去定义。设置完成Sevrer类以后去设置Channel。讲NIO的时候讲过,当客户端和服务端创建链接以后,那么它就会有一个双向的通道。这个通道就是channel。因此咱们须要在服务器里面定义channel的类型,这样的channel的类型就是NIO的类型。channel是会有一堆的助手类和handler去对它进行处理,比方说编解码处理,或者说写数据读数据等等这样的操做。这些全部的操做都是要归类到一个助手类的一个初始化器里面。它就是一个类,在这个类里面会添加不少不少的助手类,你能够把它理解为拦截器,你能够配置多个拦截器去拦截咱们的channel。当一些相应的内容在Server里面去写完设置以后,就针对咱们的Server须要去启动。咱们就须要去启动和监听Server。监听要设置某一个端口,启动完了以后咱们也是要针对咱们的服务器去作一个关闭的监听。由于你能够去关闭服务器,关闭服务器以后你须要去进行一个优雅的关闭。java


项目名称Artifact Id:imooc-netty-hellolinux

使用netty先把相应的依赖加入到工程里面来。编程

https://mvnrepository.com/artifact/io.netty/netty-all/5.0.0.Alpha1bootstrap

 

<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>5.0.0.Alpha1</version>
</dependency>

/**  

 * Copyright © 2018Nathan.Lee.Salvatore. All rights reserved.

 *

 * @Title: HelloServer.java

 * @Prject: imooc-netty-hello

 * @Package: com.hello.server

 * @Description: TODO

 * @author: ZHONGZHENHUA  

 * @date: 2018年11月8日 上午1:53:01

 * @version: V1.0  

 */
package com.hello.server;

/**
 * @author ZHONGZHENHUA
 *
 */
public class HelloServer {

    /**
    
     * @Title:HelloServer
    
     * @Description:TODO
    
     * @param args
     
     * @author: ZHONGZHENHUA
    
     * @date: 2018年11月8日 上午1:53:01
    
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

    }

}

建立一对线程组,那么它是由两个线程池构建的。一对线程组就是两个线程池。EventLoop是一个线程,Group是一个组。EventLoopGroup的解释:windows

 

Special EventExecutorGroup which allows to register Channel's that getprocessed for later selection during the event loop.

 

它能够容许让channel去进行注册。当有客户端链接到咱们服务端以后,咱们会经过这样的一个线程组去注册。注册完了以后它会得到它的一个相应的一些客户端的channels而后再直接丢给咱们下面的一个线程组去处理。后端

 

服务端的启动类叫作ServerBootStrap,它是专门用于去启动的。浏览器

Bootstrap sub-class which allows easy bootstrap of ServerChannel

它可让咱们简单地去启动咱们的ServerChannel。tomcat

前端有一个CSS框架,它也叫作BootStrap,它是彻底不同的。咱们的线程模型是一个主从的线程模型,咱们的server里面要设置两个线程组,而且它们的任务分配会由Server自动处理,咱们开发者不须要额外地关注。安全

当客户端和Server创建连接以后,咱们会有相应的通道的产生。这通道是什么类型呢?咱们也是要进行相应的设置。咱们使用的是Nio,因此咱们会使用NioServerSocketChannel.

通道有了以后,当客户端和从线程组Server创建连接以后,咱们的从线程池将一组线程组会对咱们相应的通道作处理。作处理的时候针对channel其实它会有一个一个的管道,这个在下节讲初始化器的时候会去说。这个其实就是一个初始化器,这个初始化器的话针对每个channel都会有。初始化器里面会有不少不少的助手类,不少不少的助手类是针对咱们的每个channel去作不一样的处理的,至关因而一个拦截器。这里咱们先暂时这样理解,下一节咱们会写一个具体的图例来编写初始化器。

<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.25.Final</version>
</dependency>

 

childHandler,针对从线程组去作一个相应的操做。让netty它本身的助手类,netty的类很丰富,它有不少不少的助手类,而且助手类可让咱们开发者去自定义去重写,能够去写成咱们本身所须要的一个样式。

 

serverBootStrap.bind(8088).sync();

 

绑定一个端口8088,绑定是须要耗时须要等待,因此它有一个方法叫作sync()。绑定完一个端口以后设置为一个同步的启动方式。设置完以后netty它会一直在这里等待,等待8088启动完毕。

启动完毕以后须要设置关闭的监听。监听是针对咱们当前某一个通道。每个客户端都会有一个channel。监听这样的channel是否关闭的话,那么咱们只须要.channel()就能够了。.channel()就是获取当前某个客户端对应的一个管道。

代码实际上是OK了,可是总体的线程组尚未被关闭。当服务器启动完以后,咱们要去关闭服务器,关闭完服务器以后那么针对如今的两个线程组咱们要去优雅地关闭。netty也提供给咱们一个如何去优雅关闭的方式。

这样的一个Server的启动类实际上是写完了。下一节咱们会针对childHandler设置一个子处理器(初始化器)。

/imooc-netty-hello/src/main/java/com/hello/server/HelloServer.java

/**  

 * Copyright © 2018Nathan.Lee.Salvatore. All rights reserved.

 *

 * @Title: HelloServer.java

 * @Prject: imooc-netty-hello

 * @Package: com.hello.server

 * @Description: 实现客户端发送一个请求,服务器会返回hello netty

 * @author: ZHONGZHENHUA  

 * @date: 2018年11月8日 上午1:53:01

 * @version: V1.0  

 */
package com.hello.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * @author ZHONGZHENHUA
 *
 */
public class HelloServer {

    /**
    
     * @Title:HelloServer
    
     * @Description:TODO
    
     * @param args
     
     * @author: ZHONGZHENHUA
    
     * @date: 2018年11月8日 上午1:53:01
    
     */
    public static void main(String[] args) throws Exception{
        // TODO Auto-generated method stub
        // 定义一对线程组
        // 主线程组,用于接受客户端的链接,可是不作任何处理,跟老板同样,不作事
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        // 从线程组,老板线程组会把任务丢给他,让手下线程组去作任务
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {

            
            // netty服务器的建立, ServerBootstrap 是一个启动类
            ServerBootstrap serverBootStrap = new ServerBootstrap();
            serverBootStrap.group(bossGroup, workerGroup)//设置主从线程组
            .channel(NioServerSocketChannel.class)//设置nio的双向通道
            .childHandler(null);// 子处理器,用于处理workerGroup
            // 启动server,而且设置8088为启动的端口号,同时启动方式为同步
            ChannelFuture channelFuture = serverBootStrap.bind(8088).sync();
            // 监听关闭的channel,设置为同步方式
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    

}

 


 

上一节留下一个子处理器没有讲。如何设置子处理器(channel的初始化器)。每个client和server链接完以后都会有一个channel,每个channel有一个管道。管道会由不少个handler共同组成。当channel注册完以后,就会有一个管道。管道须要开发者编写,其实就是一个初始化器。管道须要咱们设置不少的助手类handler。助手类是针对channel去作一些相应的处理。设置的handler都会在管道里面。当客户端和服务端交互的时候,相应的助手类会针对咱们的请求去作相应的处理。你能够把这块内容当作拦截器去理解。管道能够被当作一个大的拦截器,大拦截器里面会有不少的小拦截器。当请求过来的时候咱们会逐个逐个地进行拦截。

HelloServerInitializer其实就是把咱们的handler逐个去添加。既然是针对channel去初始化,这里咱们会使用channel的初始化器。咱们的通讯是socket通讯,使用的是socket类型的channel。Channelnitializer就是对咱们的channel进行初始化。

channel里面有管道,咱们须要在客户端里面去作一些相应的处理。实际上是为咱们客户端所对应的channel作一层层的处理,因此channel须要添加相应的handler助手类。

在pipeline里面会有不少的助手类,或者称之为拦截器。咱们把它理解为拦截器的话可能会更加的便于理解。

不论是开发者自定义的handler仍是netty它所提供的handler,咱们均可以一个一个地添加到pipeline管道里面去。比方说咱们添加的第一个handler是由netty提供的。在HTTP网络上打开咱们的连接,访问咱们的服务器以后会返回一个相应的字符串hello netty。既然是HTTP,咱们就会使用到HTTP Server的一些相应的编解码器。

用户请求咱们的服务端以后,咱们要返回一个hello netty这样的一个字符串,因此咱们要添加一个自定义的handler。

/imooc-netty-hello/src/main/java/com/hello/server/HelloServerInitializer.java

 

/**  

 * Copyright © 2018Nathan.Lee.Salvatore. All rights reserved.

 *

 * @Title: HelloServerInitializer.java

 * @Prject: imooc-netty-hello

 * @Package: com.hello.server

 * @Description: 初始化器,channel注册后,会执行里面的相应的初始化方法

 * @author: ZHONGZHENHUA  

 * @date: 2018年11月8日 下午3:05:54

 * @version: V1.0  

 */
package com.hello.server;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;

/**
 * @author ZHONGZHENHUA
 *
 */
public class HelloServerInitializer extends ChannelInitializer<SocketChannel>{

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // TODO Auto-generated method stub
        // 经过SocketChannel去得到对应的管道
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 经过管道,添加handler
        // HttpServerCodec是由netty本身提供的助手类,能够理解为拦截器
        // 当请求到服务端,咱们须要作解码,响应到客户端作编码
        pipeline.addLast("HttpServerCodec", new HttpServerCodec());
        
        // 添加自定义的助手类,返回“hello netty~”
        pipeline.addLast("customHandler", null);
    }

}

 

/imooc-netty-hello/src/main/java/com/hello/server/HelloServer.java

/**  

 * Copyright © 2018Nathan.Lee.Salvatore. All rights reserved.

 *

 * @Title: HelloServer.java

 * @Prject: imooc-netty-hello

 * @Package: com.hello.server

 * @Description: 实现客户端发送一个请求,服务器会返回hello netty

 * @author: ZHONGZHENHUA  

 * @date: 2018年11月8日 上午1:53:01

 * @version: V1.0  

 */
package com.hello.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * @author ZHONGZHENHUA
 *
 */
public class HelloServer {

    /**
    
     * @Title:HelloServer
    
     * @Description:TODO
    
     * @param args
     
     * @author: ZHONGZHENHUA
    
     * @date: 2018年11月8日 上午1:53:01
    
     */
    public static void main(String[] args) throws Exception{
        // TODO Auto-generated method stub
        // 定义一对线程组
        // 主线程组,用于接受客户端的链接,可是不作任何处理,跟老板同样,不作事
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        // 从线程组,老板线程组会把任务丢给他,让手下线程组去作任务
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {

            
            // netty服务器的建立, ServerBootstrap 是一个启动类
            ServerBootstrap serverBootStrap = new ServerBootstrap();
            serverBootStrap.group(bossGroup, workerGroup)//设置主从线程组
            .channel(NioServerSocketChannel.class)//设置nio的双向通道
            .childHandler(new HelloServerInitializer());// 子处理器,用于处理workerGroup
            // 启动server,而且设置8088为启动的端口号,同时启动方式为同步
            ChannelFuture channelFuture = serverBootStrap.bind(8088).sync();
            // 监听关闭的channel,设置为同步方式
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    

}

当一个Server启动完毕以后,它就会针对咱们的childHandler,其实就是针对咱们的workerGroup作一个初始化,把咱们的相应的一些channel进行注册。管道里面放一些编解码器、自定义的处理器等等,这些东西所有都归在一块儿,造成了咱们的一个服务端。这一节现这样,下一节咱们把自定义的handler进行编写。

 


 

编写属于开发者本身的一个自定义的一个助手类,而且在这里返回一个hello netty这样的一个字符串。针对客户端向服务端发送起请求以后,NIO的原理是,请求过来数据过来,它是把首先数据放在缓冲区,而后服务端再从这个缓冲区里面去读,客户端向服务端写数据是请求的话,其实就是一个入站或者说是一个入境。若是有朋友作过云服务器的配置的话,那么其实针对网关安全组或者是防火墙的话,那么就会有一个入站的概念。那么在这个地方其实也是一个相似的概念,它是入站。咱们如今是一个HTTP的请求,过来的话咱们会写上HttpObject这样的类型。CustomHandler的channelRead0方法是从缓冲区里面读数据。既然是在handler里面,其实它是一个管道。ChannelHandlerContext是上下文对象,上下文对象能够获取channel。接下来咱们要把相应的数据刷到客户端去,在这里咱们先打印一下客户端的地址。接下来咱们要发送内容消息,发消息咱们并非直接去发,咱们要经过缓冲区。咱们须要把数据拷贝到缓冲区ByteBuf。Unpooled能够深拷贝ByteBuf。

 

charset是一个字符值,字符值咱们通常都会使用UTF-8。可使用netty提供的CharsetUtil的UTF-8。copiedBuffer是建立一个新的buf(缓冲区),在NIO的模型里面提到过不论是读数据仍是写数据咱们都是经过一个缓冲区来进行一个数据的交换/交互。

要把内容content刷到客户端,刷到客户端其实就是一个HTTP的response,它是一个响应。咱们可使用一个新的接口FullHttpResponse,DefaultFullHttpResponse是默认的专门用于处理HTTP的响应。version是HTTP的版本号,HttpResponseStatus.OK其实就是200,

validateHeaders是内容,响应首先是版本号和状态码,而后就是内容,validateHeaders就是content。response的一个基本设置有了。针对数据的类型、长度也是要设置。它是一个HTTP Header。这种编程方式其实就是相似于函数式的编程。第一个须要设置数据的类型。数据类型返回出去是一个文本/字符串,图片和JSON对象也行。

数据类型设置好了就进行一个长度的设置。content.readableBytes(),ByteBuf可读的长度。它是一个可读的长度,它会把整个长度给取出来返回。

咱们拿到这样的长度再返回到客户端,先响应出去就能够了。当如今准备就绪以后,咱们须要把相应的内容/消息给刷出去,就是咱们的response。ctx.write是把response写到缓冲区,可是并不会把消息刷到客户端。ctx.writeAndFlush不只仅是进行一个写,它还会进行一个刷。它会先把数据写到缓冲区,而后再刷到客户端。

这个就是一个自定义的处理类。

/imooc-netty-hello/src/main/java/com/hello/server/CustomHandler.java

 

/**  

 * Copyright © 2018Nathan.Lee.Salvatore. All rights reserved.

 *

 * @Title: CustomHandler.java

 * @Prject: imooc-netty-hello

 * @Package: com.hello.server

 * @Description: 建立自定义助手类

 * @author: ZHONGZHENHUA  

 * @date: 2018年11月8日 下午9:55:36

 * @version: V1.0  

 */
package com.hello.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;

/**
 * @author ZHONGZHENHUA
 *
 */
//SimpleChannelInboundHandler: 对于请求来说, 其实至关于[入站,入境]
public class CustomHandler extends SimpleChannelInboundHandler<HttpObject>{

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        // TODO Auto-generated method stub
        // 获取channel
        Channel channel = ctx.channel();
        
        // 显示客户端的远程地址
        System.out.println(channel.remoteAddress());
        
        // 定义发送的数据消息
        ByteBuf content = Unpooled.copiedBuffer("Hello netty~", CharsetUtil.UTF_8);
    
        // 构建一个http response
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
        
        // 为响应增长数据类型和长度
        response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
        
        // 把响应刷到客户端
        ctx.writeAndFlush(response);
    }

}

 

 

 

编写完毕以后咱们须要把Handler放到初始化器里面去,让channel一开始注册的时候就要添加到咱们的pipeline里面去,至关于从新在这里面注册了一个拦截器。

/imooc-netty-hello/src/main/java/com/hello/server/HelloServerInitializer.java

 

/**  

 * Copyright © 2018Nathan.Lee.Salvatore. All rights reserved.

 *

 * @Title: HelloServerInitializer.java

 * @Prject: imooc-netty-hello

 * @Package: com.hello.server

 * @Description: 初始化器,channel注册后,会执行里面的相应的初始化方法

 * @author: ZHONGZHENHUA  

 * @date: 2018年11月8日 下午3:05:54

 * @version: V1.0  

 */
package com.hello.server;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;

/**
 * @author ZHONGZHENHUA
 *
 */
public class HelloServerInitializer extends ChannelInitializer<SocketChannel>{

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // TODO Auto-generated method stub
        // 经过SocketChannel去得到对应的管道
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 经过管道,添加handler
        // HttpServerCodec是由netty本身提供的助手类,能够理解为拦截器
        // 当请求到服务端,咱们须要作解码,响应到客户端作编码
        pipeline.addLast("HttpServerCodec", new HttpServerCodec());
        
        // 添加自定义的助手类,返回“hello netty~”
        //pipeline.addLast("customHandler", null);
        pipeline.addLast("customHandler", new CustomHandler());
    }

}

 

 

 

如今服务端和初始化器以及自定义的一个助手类所有都建立完毕。

咱们监听的端口是8088。

// 显示客户端的远程地址
  System.out.println(channel.remoteAddress());

在自定义的CustomHandler这一块打印客户端的远程地址的时候打印了不少,由于咱们的msg接收的时候没有对它作一个类型的判断。

判断msg是否是一个HTTP Request的请求类型,在打印客户端的远程地址以前先判断msg的类型

/imooc-netty-hello/src/main/java/com/hello/server/CustomHandler.java

 

/**  

 * Copyright © 2018Nathan.Lee.Salvatore. All rights reserved.

 *

 * @Title: CustomHandler.java

 * @Prject: imooc-netty-hello

 * @Package: com.hello.server

 * @Description: 建立自定义助手类

 * @author: ZHONGZHENHUA  

 * @date: 2018年11月8日 下午9:55:36

 * @version: V1.0  

 */
package com.hello.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;

/**
 * @author ZHONGZHENHUA
 *
 */
//SimpleChannelInboundHandler: 对于请求来说, 其实至关于[入站,入境]
public class CustomHandler extends SimpleChannelInboundHandler<HttpObject>{

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        // TODO Auto-generated method stub
        // 获取channel
        Channel channel = ctx.channel();
        
        if(msg instanceof HttpRequest) {
        
        // 显示客户端的远程地址
        System.out.println(channel.remoteAddress());
        
        // 定义发送的数据消息
        ByteBuf content = Unpooled.copiedBuffer("Hello netty~", CharsetUtil.UTF_8);
    
        // 构建一个http response
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
        
        // 为响应增长数据类型和长度
        response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
        
        // 把响应刷到客户端
        ctx.writeAndFlush(response);
    }
 }

}

 

刷新一下以后,刷新页面的时候谷歌浏览器会针对服务端发送两次请求, 第一次请求localhost是咱们所须要的,

favicon.ico,你请求每个网站的时候,它其实默认会有这样的一个图标的请求,这个和咱们其实没有任何的关系咱们不须要去管。咱们在请求后端的时候其实咱们并无加相应的路由,就至关因而一个Spring Boot或者说Spring MVC里面的一个RequestMapping。咱们没有在后边去加相应的请求的路径,因此它统一了,只要你去访问咱们的8088这样的一个端口,那么它就会直接到咱们的Handler里面去。

仍是会返回Hello netty~,由于它没有去作相应的捕获。若是要屏蔽favicon.ico能够去设置能够去获取请求的路径,而后再去截取再去判断,若是是这样的一个ico那么就直接return不要去作额外的处理。

 

 content-type是后端设置的文本类型text/plain,content-length是content.readableBytes(),也就是这个字符串Hello netty~的长度。

     // 构建一个http response
  FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);

HTTP 1_1默认会开启长连接keep-alive,那么这样针对于咱们的客户端和服务端请求传输的效率、速度会比HTTP1.0要快不少。

response是后端返回到前端的代码。


讲一个扩展知识,不经过浏览器也能够去访问服务器。须要一个linux服务器,linux服务器和这台主机是须要相互ping通的。linux和本地是能够相互ping通。ping通以后咱们使用curl能够和当前这个Hello netty~进行交互。

看来不是处于同一个局域网是很难访问windows主机的。

相关文章
相关标签/搜索