简单使用netty搭建一个http服务器

简介: 简单使用netty搭建一个http服务器

世上无难事,只要肯登攀。——毛泽东

Netty的入门使用

常见的http服务器有Tomcat、jetty等,netty也可以方便的开发一个Http服务器。


想要完整的实现一个高性能、功能完善的http服务器非常的复杂,本文仅为了方便理解 Netty 网络应用开发的基本过程,所以只实现最基本的请求响应的流程:


搭建 HTTP 服务器,配置相关参数并启动。

从浏览器或者终端发起 HTTP 请求。

成功得到服务端的响应结果。

准备工作

先创建一个maven项目,引入netty依赖,这里使用4.1.52.Final稳定版本

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

服务端启动类

简单梳理一下流程:首先创建引导器;然后配置线程模型,通过引导器绑定业务逻辑处理器,并配置一些网络参数,最后绑定端口,就可以实现服务器的启动了。

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
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.*;
import io.netty.util.CharsetUtil;
import java.net.InetSocketAddress;
/**
 * @PackageName: com.netty.demo.easy
 * @author: youjp
 * @create: 2020-11-23 16:33
 * @description: TODO netty简单服务端:这里使用的是主从Reactor多线程模式
 * @Version: 1.0
 */
public class HttpServer {
    public void start(int port) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(); //主
        EventLoopGroup workGroup = new NioEventLoopGroup();//从
        try {
            ServerBootstrap bootstrap = new ServerBootstrap(); //创建引导器
            bootstrap.group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class) // 推荐 Netty 服务端采用 NioServerSocketChannel 作为 Channel 的类型
                    .localAddress(new InetSocketAddress(port))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast("codec", new HttpServerCodec())// HTTP 编解码
                                    .addLast("compressor", new HttpContentCompressor())// HttpContent 压缩
                                    .addLast("aggregator", new HttpObjectAggregator(65536)) // HTTP 消息聚合
                                    .addLast("handler", new HttpServerHandler());
                        }
                    }).childOption(ChannelOption.SO_KEEPALIVE, true); //  设置为 true 代表启用了 TCP SO_KEEPALIVE 属性,TCP 会主动探测连接状态,即连接保活
            ChannelFuture f = bootstrap.bind().sync();
            System.out.println("Http Server started, Listening on " + port);
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            workGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
    /**
     * 业务自定义的逻辑处理类:它是入站 ChannelInboundHandler 类型的处理器,负责接收解码后的 HTTP 请求数据,并将请求处理结果写回客户端。
     */
    public class HttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
            Channel channel = ctx.channel(); // 获取通道
            System.out.println(channel.remoteAddress()); // 显示客户端的远程地址
            String content = String.format("Receive http request, uri: %s, method: %s, content: %s%n", msg.uri(), msg.method(), msg.content().toString(CharsetUtil.UTF_8));
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(content.getBytes()));
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }
    }
    public static void main(String[] args) throws Exception {
        new HttpServer().start(8088);
    }
}

通过上面的代码,我们可以完成 HTTP 服务器最基本的请求响应流程,测试步骤如下:

运行httpServer代码

20200401134307494.png

2.浏览器发起 HTTP 请求( http://localhost:8088/test) 测试服务端响应

20200401134307494.png

通过上述一个简单的 HTTP 服务示例,我们基本熟悉了 Netty 的编程模式。下面我将结合这个例子对 Netty 的引导器展开详细的介绍。

引导器实践

Netty 服务端的启动过程大致分为三个步骤:

  1. 配置线程池
  2. channel初始化
  3. 端口绑定

下面,我将逐一为大家介绍每一步具体需要做哪些工作。

配置线程池

Reactor设计也叫反应器模式,基于IO复用和线程池的结合,采用基于事件驱动的设计,能实现一个线程处理大量的事物

Netty 是采用 Reactor 模型进行开发的,可以非常容易切换三种 Reactor 模式:单线程模式、多线程模式、主从多线程模式。


Reactor单线程模式


Reactor 单线程模式即所有 I/O 操作都由一个线程完成,所以只需要启动一个 EventLoopGroup 即可。

EventLoopGroup group = new NioEventLoopGroup(1);
ServerBootstrap b = new ServerBootstrap();
b.group(group)

Reactor多线程模式

Reactor单线程模式有非常严重的性能瓶颈,因此多线程模式出现了,在netty中使用Reactor多线程模式和单线程模式非常相似,区分是NioEventLoopGroup可以不需要任何参数,它默认会启动2倍CPU核数的线程。当然,你也可以手动设置固定线程数。

EventLoopGroup group = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(group)

Reactor主从多线程模式

在大多数的场景下,我们采用的是主从多线程Reactor模式。Boss是主Reactor, Worker是从Reactor.它们分别应用于不同的NioEventLoopGroup,主Reactor负责处理请求(Accept),然后把channel注册到从Reactor上,从Reactor主要负责channel生命周期的所有I/O事件。

EventLoopGroup bossGroup = new NioEventLoopGroup(); //boss工作组
EventLoopGroup workGroup = new NioEventLoopGroup(); //worker工作组
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workGroup)

Reactor三种模式形象比喻

餐厅一般有接待员和服务员,接待员负责在门口接待顾客,服务员负责全程服务顾客。Reactor的三种线程模型可以用接待员和服务员类比;


单Reactor单线程模型:接待员和服务员是同一个人,一直为顾客服务。客流量较少适合


单Reactor多线程模型:一个接待员,多个服务员。客流量大,一个人忙不过来,由专门的接待员在门口接待顾客,然后安排好桌子后,由一个服务员一直服务,一般每个服务员负责一片中的几张桌子


多Reactor多线程模型:多个接待员,多个服务员。这种就是客流量太大了,一个接待员忙不过来了

设置Channel通道类型

NIO模型是netty中最成熟并被广泛 使用的模型,因此,推荐netty服务端采用NioServerSocketChannel作为Channel的类型,客户端采用NioSocketChannel.设置的方式如下:

ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.channel(NioServerSocketChannel.class);

Netty 提供了多种类型的Channel 实现类,例如 OioServerSocketChannel、EpollServerSocketChannel 等。你可以按需切换。


注册 ChannelHandler


在 Netty 中可以通过 ChannelPipeline 去注册多个 ChannelHandler,每个 ChannelHandler 各司其职,这样就可以实现最大化的代码复用,充分体现了 Netty 设计的优雅之处。那么如何通过引导器添加多个 ChannelHandler 呢?其实很简单,我们看下 HTTP 服务器代码示例

ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline()
                .addLast("codec", new HttpServerCodec())// HTTP 编解码
                .addLast("compressor", new HttpContentCompressor())// HttpContent 压缩
                .addLast("aggregator", new HttpObjectAggregator(65536)) // HTTP 消息聚合
                .addLast("handler", new HttpServerHandler()); //自定义业务逻辑处理器
    }
})

ServerBootstrap 的 childHandler() 方法需要注册一个 ChannelHandler。


ChannelInitializer是实现了 ChannelHandler接口的匿名类,通过实例化 ChannelInitializer 作为 ServerBootstrap 的参数。


Channel 初始化时都会绑定一个 管道-Pipeline,它主要用于服务编排。Pipeline 管理了多个 ChannelHandler。I/O 事件依次在 ChannelHandler 中传播。


ChannelHandler 负责业务逻辑处理。上述 HTTP 服务器示例中使用链式的方式加载了多个 ChannelHandler,包含HTTP 编解码处理器、HTTPContent 压缩处理器、HTTP 消息聚合处理器、自定义业务逻辑处理器。


在这里结合 HTTP 请求-响应的场景,分析下数据在 ChannelPipeline 中的流向。当服务端收到 HTTP 请求后,会依次经过 HTTP 编解码处理器、HTTPContent 压缩处理器、HTTP 消息聚合处理器、自定义业务逻辑处理器分别处理后,再将最终结果通过 HTTPContent 压缩处理器、HTTP 编解码处理器写回客户端。


设置 Channel 参数


Netty 提供了十分便捷的方法,用于设置 Channel 参数。关于 Channel 的参数数量非常多,如果每个参数都需要自己设置,那会非常繁琐。幸运的是 Netty 提供了默认参数设置,实际场景下默认参数已经满足我们的需求,我们仅需要修改自己关系的参数即可。

bootstrap.option(ChannelOption.SO_KEEPALIVE, true);

ServerBootstrap 设置 Channel 属性有option和childOption两个方法,option 主要负责设置 Boss 线程组,而 childOption 对应的是 Worker 线程组。

这里我列举了经常使用的参数含义,你可以结合业务场景,按需设置。

b8a536bce32d74897f8edaace4afdbe.png

端口绑定

在完成上述 Netty 的配置之后,bind() 方法会真正触发启动,sync() 方法则会阻塞,直至整个启动过程完成,具体使用方式如下:

ChannelFuture f = b.bind().sync();

bind() 方法涉及的细节比较多,在这里就先不做展开了。后续有笔记学习的话再介绍。

总结

关于如何使用引导器开发一个 Netty 网络应用我们就介绍完了,服务端的启动过程一定离不开配置线程池、Channel 初始化、端口绑定三个步骤,在 Channel 初始化的过程中最重要的就是绑定用户实现的自定义业务逻辑。是不是特别简单?

Http客户端类

使用netty进行客户端开发同服务端类似。这里我提供一下源码可以自行运行测试

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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.http.*;
import java.net.URI;
import java.nio.charset.StandardCharsets;
/**
 * @PackageName: com.netty.demo.easy
 * @author: youjp
 * @create: 2020-11-24 16:32
 * @description: TODO netty客户端
 * @Version: 1.0
 */
public class HttpClient {
    public void connect(String host,int port) throws Exception{
        EventLoopGroup group=new NioEventLoopGroup();
        try {
            Bootstrap bootstrap=new Bootstrap();
            bootstrap.group(group);
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new HttpResponseDecoder());
                    ch.pipeline().addLast(new HttpRequestEncoder());
                    ch.pipeline().addLast(new HttpClientHandler());
                }
            });
            ChannelFuture f = bootstrap.connect(host, port).sync();
            URI uri = new URI("http://127.0.0.1:8088");
            String content = "hello world";
            DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
                    uri.toASCIIString(), Unpooled.wrappedBuffer(content.getBytes(StandardCharsets.UTF_8)));
            request.headers().set(HttpHeaderNames.HOST, host);
            request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
            request.headers().set(HttpHeaderNames.CONTENT_LENGTH, request.content().readableBytes());
            f.channel().write(request);
            f.channel().flush();
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }
    /**
     * 客户端业务处理类
     */
   public class HttpClientHandler extends ChannelInboundHandlerAdapter{
       @Override
       public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
           if (msg instanceof HttpContent) {
               HttpContent content = (HttpContent) msg;
               ByteBuf buf = content.content();
               System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8));
               buf.release();
           }
       }
   }
    public static void main(String[] args) throws Exception {
        HttpClient client = new HttpClient();
        client.connect("127.0.0.1", 8088);
    }
}

有兴趣的老爷,可以关注我的公众号【一起收破烂】,回复【006】获取2021最新java面试资料以及简历模型120套哦~



相关文章
|
1月前
|
搜索推荐 安全 网络安全
服务器支持HTTPS的时机和条件
【10月更文挑战第23天】服务器支持HTTPS的时机和条件
19 5
|
2月前
使用Netty实现文件传输的HTTP服务器和客户端
本文通过详细的代码示例,展示了如何使用Netty框架实现一个文件传输的HTTP服务器和客户端,包括服务端的文件处理和客户端的文件请求与接收。
62 1
使用Netty实现文件传输的HTTP服务器和客户端
|
1月前
|
存储 Oracle 关系型数据库
oracle服务器存储过程中调用http
通过配置权限、创建和调用存储过程,您可以在Oracle数据库中使用UTL_HTTP包发起HTTP请求。这使得Oracle存储过程可以与外部HTTP服务进行交互,从而实现更复杂的数据处理和集成。在实际应用中,根据具体需求调整请求类型和错误处理逻辑,以确保系统的稳定性和可靠性。
50 0
|
3月前
HAProxy的高级配置选项-配置haproxy支持https协议及服务器动态上下线
文章介绍了如何配置HAProxy以支持HTTPS协议和实现服务器的动态上下线。
165 8
HAProxy的高级配置选项-配置haproxy支持https协议及服务器动态上下线
|
3月前
|
开发者
HTTP状态码是由网页服务器返回的三位数字响应代码,用于表示请求的处理结果和状态
HTTP状态码是由网页服务器返回的三位数字响应代码,用于表示请求的处理结果和状态
33 1
|
4月前
|
移动开发 网络协议 编译器
实战案例3:C语言实现的HTTP服务器
实战案例3:C语言实现的HTTP服务器
255 0
|
16天前
|
存储 人工智能 弹性计算
阿里云弹性计算(ECS)提供强大的AI工作负载平台,支持灵活的资源配置与高性能计算,适用于AI训练与推理
阿里云弹性计算(ECS)提供强大的AI工作负载平台,支持灵活的资源配置与高性能计算,适用于AI训练与推理。通过合理优化资源分配、利用自动伸缩及高效数据管理,ECS能显著提升AI系统的性能与效率,降低运营成本,助力科研与企业用户在AI领域取得突破。
35 6
|
21天前
|
人工智能 弹性计算 编解码
阿里云GPU云服务器性能、应用场景及收费标准和活动价格参考
GPU云服务器作为阿里云提供的一种高性能计算服务,通过结合GPU与CPU的计算能力,为用户在人工智能、高性能计算等领域提供了强大的支持。其具备覆盖范围广、超强计算能力、网络性能出色等优势,且计费方式灵活多样,能够满足不同用户的需求。目前用户购买阿里云gpu云服务器gn5 规格族(P100-16G)、gn6i 规格族(T4-16G)、gn6v 规格族(V100-16G)有优惠,本文为大家详细介绍阿里云gpu云服务器的相关性能及收费标准与最新活动价格情况,以供参考和选择。
|
26天前
|
机器学习/深度学习 人工智能 弹性计算
什么是阿里云GPU云服务器?GPU服务器优势、使用和租赁费用整理
阿里云GPU云服务器提供强大的GPU算力,适用于深度学习、科学计算、图形可视化和视频处理等多种场景。作为亚太领先的云服务提供商,阿里云的GPU云服务器具备灵活的资源配置、高安全性和易用性,支持多种计费模式,帮助企业高效应对计算密集型任务。
|
28天前
|
存储 分布式计算 固态存储
阿里云2核16G、4核32G、8核64G配置云服务器租用收费标准与活动价格参考
2核16G、8核64G、4核32G配置的云服务器处理器与内存比为1:8,这种配比的云服务器一般适用于数据分析与挖掘,Hadoop、Spark集群和数据库,缓存等内存密集型场景,因此,多为企业级用户选择。目前2核16G配置按量收费最低收费标准为0.54元/小时,按月租用标准收费标准为260.44元/1个月。4核32G配置的阿里云服务器按量收费标准最低为1.08元/小时,按月租用标准收费标准为520.88元/1个月。8核64G配置的阿里云服务器按量收费标准最低为2.17元/小时,按月租用标准收费标准为1041.77元/1个月。本文介绍这些配置的最新租用收费标准与活动价格情况,以供参考。