Netty实现一个小应用服务器 +消息收发推送系统

本文详细介绍了如何使用Netty实现Servlet容器、仿支付功能和多人聊天室。通过配置Servlet映射、HTTP协议的编解码、服务端启动以及处理业务逻辑,演示了Netty在构建高性能IO服务中的应用。此外,还涵盖了WebSocket协议的使用和自定义协议设计,以及静态资源的处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


项目内容

  • 构建一个Servlet容器(仿Tomcat)
  • 写一个仿支付的页面,其可以在接收到支付成功通知后自动跳转
  • 多人聊天室

建议至少有使用过Netty的经验、了解HTTP协议、了解Servlet用法的读者阅读,否则可能会有一点吃力
文章中的示例demo已上传至本人Github,戳此访问?
其中html、js文件均为网络web上的源码copy而来,如有侵犯版权,请告知作者

1. Servlet容器

1.1 介绍

在tomcat中,启动tomcat应用首先会读取web.xml文件,去读取servlet配置,在最早的时候我们就是在web.xml中配置servlet映射关系,然后在具体的servlet类中编写我们的业务逻辑,但是这样做相当冗余,表现在多一个uri请求我就要加一个servlet类,管理起来十分繁琐且复杂,所以这时候MVC思想就来了,使用仅一个DispatcherServlet接受所有请求,我们就不需要在web.xml配置Servlet映射了,正好Spring中有一个MVC模块,我们只需要将具体业务逻辑变成一个个method,变成Bean交给Spring管理,此时Spring就会让这个DispatcherServlet去根据uri执行它管理的Bean —>执行对应的method。

有点扯远了,但我想说的是,将每个uri映射到某个Servlet上这个过程十分繁琐,但项目中为了还原tomcat的原始度,构建最底层最古老的写法,包括对http协议的构建,io的处理,servlet的映射和业务逻辑处理,关注更多的也更值得我们学习的是如何实现服务端的io高性能处理,如何仿tomcat去接受请求,仿造一个Servlet容器的网络请求做法,和如何构建http协议,所以暂且不要关注它的可用性。

1.2 协议

既然仿造的是tomcat,那么协议一定是HTTP协议,这里客户端指的是浏览器,当在浏览器输入一个url后,将会向服务端发起一个HttpRequest,然后服务端需要根据request的不同,构造不同的response返回出去。

1.3 Servlet映射关系初始化

在这里插入图片描述
这里我没有照搬tomcat的web.xml配置,而是使用properties放置配置,怎么方便怎么来,大致意思有了就行

servlet.chatServlet.url=/
servlet.chatServlet.className=com.push.server.servlet.ChatIndexServlet

servlet.RestfulServlet.url=/payOrder
servlet.RestfulServlet.className=com.push.server.servlet.RestfulServlet

servlet.WaitPayServlet.url=/pay
servlet.WaitPayServlet.className=com.push.server.servlet.WaitPayServlet

这里是我demo中的一个配置,你需要按照以上规则去配置url与Servlet的映射关系,这样,我在浏览器访问/pay就会交给WaitPayServlet这个Servlet去处理请求了

具体初始化Servlet的代码如下:

public class ServletUtils {
   
   

  private static Properties webProperties = new Properties();

  // 存放url与Servlet关系的Map
  private static Map<String, IServlet> servletMapping = new HashMap<String, IServlet>();

  public static final String WEB_INF = "/WEB-INF";

  // 初始化url与Servlet关系
  public static synchronized void init() throws IOException {
   
   

    if (AbstractServlet.successInit) {
   
   
      log.info("已经初始化过一次了,不要重复初始化servlet");
    }
    InputStream in = null;
    try {
   
   
      // 获取 resources/web.properties 下的文件流
      log.info("开启文件流");
      in = ServletUtils.class.getResourceAsStream("/web.properties");

      log.info("读取文件流");
      // 读取properties的内容
      webProperties.load(in);

      for (Object k : webProperties.keySet()) {
   
   

        log.info("读取内容");
        String key = k.toString();
        // 按照我们的规则去解析它
        if (key.endsWith(".url")) {
   
   
          String servletName = key.replaceAll("\\.url$", "");
          String url = webProperties.getProperty(key);
          String className = webProperties.getProperty(servletName + ".className");
          // 单实例,多线程
          IServlet obj = (IServlet) Class.forName(className).newInstance();
          servletMapping.put(url, obj);
        }

      }
      if (servletMapping.size() == 0) {
   
   
        log.info("没有读取到servlet映射配置");
        throw new RuntimeException("没有读取到servlet映射配置");
      } else {
   
   
        AbstractServlet.successInit = true;
      }

    } catch (Exception e) {
   
   
      log.info("读取servlet配置文件失败: [{}]", e.getMessage());
      throw new RuntimeException("读取servlet配置文件失败");
    } finally {
   
   
      if (in != null) {
   
   
        in.close();
      }
    }
  }

主要做的就是读取指定的文件名,然后按照我们的规则去解析映射关系,并且使用反射去创建一个Servlet实例,并保存在Map中,以便后续使用。值得一提的是,这里和tomcat的思想一致,Servlet是单实例,并多线程访问的。

之后我们只需要根据url拿到对应的Servlet去执行service方法即可:

public static IServlet findUriMapping(String uri) {
   
   
  return servletMapping.get(uri);
}

1.4 HTTP协议的编解码

  • 解码器作用:在网络io的过程中,数据的传输一般都是使用字节来传输,同样,在浏览器对服务端发起一个http请求的时候也是发送一串字节,此时我们需要根据字节解析出可以看得懂的HttpRequest对象,这时候我们就需要一个解码器。

  • 编码器作用:当我们根据请求构造相应的响应对象HttpResponse时,同样需要将其变成字节,利用网络传输出去给浏览器,这时候我们就需要一个编码器。

编解码过程相对繁琐,但Netty帮我们实现了大部分的公有协议,例如HTTP协议、WebSocket协议等等,所以编解码这块不需要我们关心,如果想自己实现一个轻量的协议,Netty提供了一些基类,所以实现起来也是很方便的,在后面的聊天室服务器的实现中我就自定义了一个通信规则,感兴趣的读者可以在下面了解自定义协议的实现。

1.5 服务端的启动

private static final int DEFAULT_PORT = 8888;

public static void start(int port) {
   
   

  EventLoopGroup bossEventLoop = new NioEventLoopGroup(1);
  EventLoopGroup workerEventLoop = new NioEventLoopGroup();

  try {
   
   
    // 初始化servlet的映射关系
    ServletUtils.init();

    ServerBootstrap bootstrap = new ServerBootstrap();

    bootstrap.group(bossEventLoop, workerEventLoop)
      .channel(NioServerSocketChannel.class)
      .childHandler(new ChannelInitializer() {
   
   

        @Override
        protected void 
### 使用 Netty 实现 WebSocket 消息发送 以下是基于 Spring Boot 和 Netty 的 WebSocket 消息推送实现方案。此方法利用了 Netty 提供的强大异步事件驱动模型以及 WebSocket 协议的支持。 #### 1. 添加依赖 在 `pom.xml` 文件中引入必要的 Maven 依赖: ```xml <dependencies> <!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- Netty Dependency --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.92.Final</version> </dependency> </dependencies> ``` #### 2. 配置文件设置 在 `application.yml` 或 `application.properties` 中定义 WebSocket 相关参数[^2]: ```yaml ws: port: 8071 readerIdleTimeSeconds: 10 writerIdleTimeSeconds: 10 allIdleTimeSeconds: 15 ``` 这些配置项用于控制 WebSocket 连接的行为,例如读写超时时间等。 #### 3. 初始化 Netty Server 创建一个初始化类来启动 Netty WebSocket 服务: ```java 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; public class NettyWebSocketServer { private final int port; public NettyWebSocketServer(int port) { this.port = port; } public void start() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap() .group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer()); ChannelFuture future = bootstrap.bind(port).sync(); System.out.println("Netty WebSocket server started at port " + port); future.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { new NettyWebSocketServer(8071).start(); // 启动服务器并监听指定端口 } } ``` #### 4. 自定义 ChannelInitializer 编写自定义的 `ChannelInitializer` 来处理 HTTP 请求升级到 WebSocket 协议的过程: ```java import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.stream.ChunkedWriteHandler; public class ChannelInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); // 解码和编码HTTP请求/响应 pipeline.addLast(new HttpServerCodec()); // 支持大帧消息 pipeline.addLast(new HttpObjectAggregator(65536)); // 处理分块写入 pipeline.addLast(new ChunkedWriteHandler()); // WebSocket协议处理器 pipeline.addLast(new WebSocketServerProtocolHandler("/ws")); // 用户自定义的消息处理器 pipeline.addLast(new MessageHandler()); } } ``` #### 5. 编写消息处理器 创建一个简单的消息处理器来接收和广播消息: ```java import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; public class MessageHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { @Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) { String request = msg.text(); System.out.println("Client message -> " + request); // 广播消息给其他客户端 ctx.writeAndFlush(new TextWebSocketFrame("Server received your message: " + request)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } } ``` #### 6. 测试 WebSocket 推送功能 运行程序后,在浏览器或其他工具(如 Postman)中访问 URL `ws://localhost:8071/ws`,即可建立 WebSocket 连接并测试消息收发功能。 --- ### 技术优势分析 Netty 是一款高性能、可定制化的网络应用框架,其特点包括但不限于以下几点[^3]: - **API 简单易用**:开发者能够快速构建复杂的网络应用程序。 - **强大的编解码能力**:内置支持多种主流协议,减少手动解析的工作量。 - **灵活性强**:通过扩展 `ChannelHandler` 可以满足特定需求。 - **高效性能**:经过优化后的 NIO 设计使其成为行业首选之一。 - **稳定性保障**:修复已知 JDK NIO Bug,降低维护成本。 --- ###
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值