Netty In Action中文版 - 第二章:第一个Netty程序

本文介绍如何使用Netty4创建服务器和客户端进行通信。主要内容包括搭建开发环境、编写服务器及客户端代码、处理异常等,适合Netty初学者。

本章介绍

  • 获取Netty4最新版本
  • 设置运行环境来构建和运行netty程序
  • 创建一个基于Netty的服务器和客户端
  • 拦截和处理异常
  • 编写和运行Netty服务器和客户端
        本章将简单介绍Netty的核心概念,这个狠心概念就是学习Netty是如何拦截和处理异常,对于刚开始学习netty的读者,利用netty的异常拦截机制来调试程序问题很有帮助。本章还会介绍其他一些核心概念,如服务器和客户端的启动以及分离通道的处理程序。本章学习一些基础以便后面章节的深入学习。本章中将编写一个基于netty的服务器和客户端来互相通信,我们首先来设置netty的开发环境。

2.1 设置开发环境

        设置开发环境的步骤如下:
  • 安装JDK7,下载地址http://www.oracle.com/technetwork/java/javase/archive-139210.html
  • 下载netty包,下载地址http://netty.io/
  • 安装Eclipse
        《Netty In Action》中描述的比较多,没啥用,这里就不多说了。本系列博客将使用Netty4,需要JDK1.7+

2.2 Netty客户端和服务器概述

        本节将引导你构建一个完整的Netty服务器和客户端。一般情况下,你可能只关心编写服务器,如一个http服务器的客户端是浏览器。然后在这个例子中,你若同时实现了服务器和客户端,你将会对他们的原理更加清晰。
        一个Netty程序的工作图如下

  1. 客户端连接到服务器
  2. 建立连接后,发送或接收数据
  3. 服务器处理所有的客户端连接

        从上图中可以看出,服务器会写数据到客户端并且处理多个客户端的并发连接。从理论上来说,限制程序性能的因素只有系统资源和JVM。为了方便理解,这里举了个生活例子,在山谷或高山上大声喊,你会听见回声,回声是山返回的;在这个例子中,你是客户端,山是服务器。喊的行为就类似于一个Netty客户端将数据发送到服务器,听到回声就类似于服务器将相同的数据返回给你,你离开山谷就断开了连接,但是你可以返回进行重连服务器并且可以发送更多的数据。

        虽然将相同的数据返回给客户端不是一个典型的例子,但是客户端和服务器之间数据的来来回回的传输和这个例子是一样的。本章的例子会证明这一点,它们会越来越复杂。
        接下来的几节将带着你完成基于Netty的客户端和服务器的应答程序。

2.3 编写一个应答服务器

        写一个Netty服务器主要由两部分组成:

  • 配置服务器功能,如线程、端口
  • 实现服务器处理程序,它包含业务逻辑,决定当有一个请求连接或接收数据时该做什么
2.3.1 启动服务器

        通过创建ServerBootstrap对象来启动服务器,然后配置这个对象的相关选项,如端口、线程模式、事件循环,并且添加逻辑处理程序用来处理业务逻辑(下面是个简单的应答服务器例子)

package netty.example;

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

public class EchoServer {

	private final int port;

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

	public void start() throws Exception {
		EventLoopGroup group = new NioEventLoopGroup();
		try {
			//create ServerBootstrap instance
			ServerBootstrap b = new ServerBootstrap();
			//Specifies NIO transport, local socket address
			//Adds handler to channel pipeline
			b.group(group).channel(NioServerSocketChannel.class).localAddress(port)
					.childHandler(new ChannelInitializer<Channel>() {
						@Override
						protected void initChannel(Channel ch) throws Exception {
							ch.pipeline().addLast(new EchoServerHandler());
						}
					});
			//Binds server, waits for server to close, and releases resources
			ChannelFuture f = b.bind().sync();
			System.out.println(EchoServer.class.getName() + "started and listen on “" + f.channel().localAddress());
			f.channel().closeFuture().sync();
		} finally {
			group.shutdownGracefully().sync();
		}
	}
	
	public static void main(String[] args) throws Exception {
		new EchoServer(65535).start();
	}

}
        从上面这个简单的服务器例子可以看出,启动服务器应先创建一个ServerBootstrap对象,因为使用NIO,所以指定NioEventLoopGroup来接受和处理新连接,指定通道类型为NioServerSocketChannel,设置InetSocketAddress让服务器监听某个端口已等待客户端连接。
        接下来,调用childHandler放来指定连接后调用的ChannelHandler,这个方法传ChannelInitializer类型的参数,ChannelInitializer是个抽象类,所以需要实现initChannel方法,这个方法就是用来设置ChannelHandler。
        最后绑定服务器等待直到绑定完成,调用sync()方法会阻塞直到服务器完成绑定,然后服务器等待通道关闭,因为使用sync(),所以关闭操作也会被阻塞。现在你可以关闭EventLoopGroup和释放所有资源,包括创建的线程。
        这个例子中使用NIO,因为它是目前最常用的传输方式,你可能会使用NIO很长时间,但是你可以选择不同的传输实现。例如,这个例子使用OIO方式传输,你需要指定OioServerSocketChannel。Netty框架中实现了多重传输方式,将再后面讲述。
        本小节重点内容:
  • 创建ServerBootstrap实例来引导绑定和启动服务器
  • 创建NioEventLoopGroup对象来处理事件,如接受新连接、接收数据、写数据等等
  • 指定InetSocketAddress,服务器监听此端口
  • 设置childHandler执行所有的连接请求
  • 都设置完毕了,最后调用ServerBootstrap.bind() 方法来绑定服务器
2.3.2 实现服务器业务逻辑
        Netty使用futures和回调概念,它的设计允许你处理不同的事件类型,更详细的介绍将再后面章节讲述,但是我们可以接收数据。你的channel handler必须继承ChannelInboundHandlerAdapter并且重写channelRead方法,这个方法在任何时候都会被调用来接收数据,在这个例子中接收的是字节。
        下面是handler的实现,其实现的功能是将客户端发给服务器的数据返回给客户端:
package netty.example;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class EchoServerHandler extends ChannelInboundHandlerAdapter {

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		System.out.println("Server received: " + msg);
		ctx.write(msg);
	}

	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}

}
        Netty使用多个Channel Handler来达到对事件处理的分离,因为可以很容的添加、更新、删除业务逻辑处理handler。Handler很简单,它的每个方法都可以被重写,它的所有的方法中只有channelRead方法是必须要重写的。
2.3.3 捕获异常
        重写ChannelHandler的exceptionCaught方法可以捕获服务器的异常,比如客户端连接服务器后强制关闭,服务器会抛出"客户端主机强制关闭错误",通过重写exceptionCaught方法就可以处理异常,比如发生异常后关闭ChannelHandlerContext。

2.4 编写应答程序的客户端

        服务器写好了,现在来写一个客户端连接服务器。应答程序的客户端包括以下几步:
  • 连接服务器
  • 写数据到服务器
  • 等待接受服务器返回相同的数据
  • 关闭连接
2.4.1 引导客户端
        引导客户端启动和引导服务器很类似,客户端需同时指定host和port来告诉客户端连接哪个服务器。看下面代码:
package netty.example;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.example.echo.EchoClientHandler;

import java.net.InetSocketAddress;

public class EchoClient {

	private final String host;
	private final int port;

	public EchoClient(String host, int port) {
		this.host = host;
		this.port = port;
	}

	public void start() throws Exception {
		EventLoopGroup group = new NioEventLoopGroup();
		try {
			Bootstrap b = new Bootstrap();
			b.group(group).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress(host, port))
					.handler(new ChannelInitializer<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel ch) throws Exception {
							ch.pipeline().addLast(new EchoClientHandler());
						}
					});
			ChannelFuture f = b.connect().sync();
			f.channel().closeFuture().sync();
		} finally {
			group.shutdownGracefully().sync();
		}
	}

	public static void main(String[] args) throws Exception {
		new EchoClient("localhost", 20000).start();
	}
}
创建启动一个客户端包含下面几步:
  • 创建Bootstrap对象用来引导启动客户端
  • 创建EventLoopGroup对象并设置到Bootstrap中,EventLoopGroup可以理解为是一个线程池,这个线程池用来处理连接、接受数据、发送数据
  • 创建InetSocketAddress并设置到Bootstrap中,InetSocketAddress是指定连接的服务器地址
  • 添加一个ChannelHandler,客户端成功连接服务器后就会被执行
  • 调用Bootstrap.connect()来连接服务器
  • 最后关闭EventLoopGroup来释放资源
2.4.2 实现客户端的业务逻辑
        客户端的业务逻辑的实现依然很简单,更复杂的用法将在后面章节详细介绍。和编写服务器的ChannelHandler一样,在这里将自定义一个继承SimpleChannelInboundHandler的ChannelHandler来处理业务;通过重写父类的三个方法来处理感兴趣的事件:
  • channelActive():客户端连接服务器后被调用
  • channelRead0():从服务器接收到数据后调用
  • exceptionCaught():发生异常时被调用
        实现代码如下
package netty.example;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
	
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		ctx.write(Unpooled.copiedBuffer("Netty rocks!",CharsetUtil.UTF_8));
	}

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
		System.out.println("Client received: " + ByteBufUtil.hexDump(msg.readBytes(msg.readableBytes())));
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}
}
        可能你会问为什么在这里使用的是SimpleChannelInboundHandler而不使用ChannelInboundHandlerAdapter?主要原因是ChannelInboundHandlerAdapter在处理完消息后需要负责释放资源。在这里将调用ByteBuf.release()来释放资源。SimpleChannelInboundHandler会在完成channelRead0后释放消息,这是通过Netty处理所有消息的ChannelHandler实现了ReferenceCounted接口达到的。
        为什么在服务器中不使用SimpleChannelInboundHandler呢?因为服务器要返回相同的消息给客户端,在服务器执行完成写操作之前不能释放调用读取到的消息,因为写操作是异步的,一旦写操作完成后,Netty中会自动释放消息。
        客户端的编写完了,下面让我们来测试一下

2.5 编译和运行echo(应答)程序客户端和服务器

        注意,netty4需要jdk1.7+。

        本人测试,可以正常运行。

2.6 总结

        本章介绍了如何编写一个简单的基于Netty的服务器和客户端并进行通信发送数据。介绍了如何创建服务器和客户端以及Netty的异常处理机制。


<think>首先,用户的问题是关于解决seata依赖引入时出现的一个错误:报io.netty:netty-transport-native-kqueue:jar:osx-aarch_64:4.1.92.Final问题。错误信息表明在构建过程中找不到这个特定的artifact。 从站内引用来看: - 引用[1]讨论了Netty的tcnative.classifier问题,提到需要将classifier改为windows-x86_64,并确保环境是64位。这暗示了classifier的设置在解决依赖问题中的重要性。 - 引用[2]是关于Java集合的内容,似乎与当前问题无关,可以忽略。 用户上一次的问题(在系统级指令中)是关于快速排序的,但当前问题是一个全新的依赖问题,所以我需要直接关注用户当前的查询。 关键错误信息是:"io.netty:netty-transport-native-kqueue:jar:osx-aarch_64:4.1.92.Final"。这看起来是针对macOS的特定架构(aarch_64,即ARM64)的依赖问题。用户可能是使用macOS with Apple Silicon(如M1/M2芯片),在引入seata依赖时遇到了这个依赖缺失的问题。 解决这类依赖问题的常见方法: 1. **检查依赖本兼容性**:确保seata本与Netty本兼容。seata可能需要特定本的Netty。 2. **添加classifier或排除依赖**:类似于引用[1]中的建议,可能需要手动指定classifier或排除不必要的native依赖。 3. **配置Maven仓库**:确保Maven可以访问正确的仓库,如Maven Central或公司内部仓库。 4. **本地编译或下载**:如果artifact缺失,可能需要手动下载或编译。 5. **使用正确的classifier**:错误中提到了osx-aarch_64,这可能是针对Apple Silicon的。如果用户的环境不是这个架构,可能需要指定不同的classifier。 具体到这个错误: - `netty-transport-native-kqueue`是Netty的一个模块,用于支持kqueue事件通知机制,主要在BSD-like系统(如macOS)上使用。 - `osx-aarch_64`表示macOS on ARM64 architecture。 - 错误是找不到这个artifact,可能是因为在仓库中不存在,或者用户的环境需要配置。 可能的解决方案: - 在pom.xml中,为Netty依赖添加classifier,或者排除这个特定的native dependency。 - 由于seata可能依赖于Netty,我需要查看seata的官方文档或常见问题来解决这个问题。 - 参考引用[1],它提到在Netty tcnative的问题中,需要设置classifier为windows-x86_64,这里类似,可能需要指定classifier为适合用户环境的本。 用户的环境:用户提到引用[1]中有windows-x86_64的讨论,但目前错误是针对macOS的(osx-aarch_64),所以用户可能在macOS上开发。我需要假设用户使用的是macOS Apple Silicon。 标准步骤: 1. **添加依赖时指定classifier**:在pom.xml中,对于Netty依赖,添加classifier属性。 2. **排除不必要的依赖**:在seata的依赖中,排除掉native transport依赖。 3. **检查依赖树**:使用Maven命令(如`mvn dependency:tree`)查看依赖关系。 4. **更新本**:确保使用的seata和Netty本兼容。 具体到seata:seata是一个分布式事务解决方案,它依赖于Netty。我回想或假设常见问题中,可能需要添加特定的classifier。 在回答中,我需要: - 使用中文,因为系统指令要求。 - 结构清晰,逐步解决问题。 - 引用添加标识,如[^1]。 - 生成相关问题。 - 遵守格式:行内数学表达式用$...$,独立公式用$$...$$(但这里可能不涉及数学公式)。 步骤建议: 1. 确认问题:描述错误。 2. 分析原因:为什么会出现这个错误。 3. 提供解决方案: - Option 1: 在pom.xml中指定正确的classifier。 - Option 2: 排除掉这个依赖。 - Option 3: 确保Maven仓库配置正确。 4. 示例代码:给出pom.xml的修改片段。 5. 验证步骤:如何测试是否解决。 可能的解决方案代码: ```xml <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>your_version</version> <exclusions> <exclusion> <groupId>io.netty</groupId> <artifactId>netty-transport-native-kqueue</artifactId> <classifier>osx-aarch_64</classifier> </exclusion> </exclusions> </dependency> ``` 或者添加依赖时指定classifier。 但更常见的是排除它,因为如果不需要这个native绑定,可以排除以避免错误。 另一个思路:如果用户确实需要这个依赖,但它在仓库中缺失,可能需要添加额外的仓库或手动安装。 然而,在引用[1]中,它提到编译Netty源码来生成类文件,这可能适用于这里。 但是,最直接的是排除这个依赖。 生成相关问题: 基于这个错误,相关问题可以是: 1. 如何排查Maven依赖冲突? 2. 如何在Maven中排除特定依赖? 3. seata的常见依赖问题有哪些? 4. 如何配置Maven仓库来解决依赖缺失? 确保回答真实可靠:我需要基于一般知识。我不是seata专家,但可以从Maven和Netty角度给出通用建议。 最终回答结构: - 开头确认问题。 - 分析原因。 - 列出解决方案步骤。 - 提供示例代码。 - 结尾添加相关问题。 </think>### 解决seata依赖引入报`io.netty:netty-transport-native-kqueue:jar:osx-aarch_64:4.1.92.Final`错误的方法 此错误通常在macOS(尤其是Apple Silicon架构)环境下引入seata依赖时发生,原因是对特定操作系统架构的Netty native依赖缺失或本不兼容。以下是详细解决方案: --- ### **问题分析** - **错误根源**: - `netty-transport-native-kqueue`是Netty的Native Transport模块,用于优化macOS系统的网络性能。 - `osx-aarch_64`表示针对Apple Silicon芯片(如M1/M2)的架构。 - 当Maven仓库中缺少该架构的artifact或JDK/IDE环境不匹配时,会报此错误[^1]。 - **常见场景**: - 使用macOS Apple Silicon设备开发。 - seata依赖的Netty本与本地环境冲突。 - Maven未正确配置仓库或依赖。 --- ### **解决方案步骤** #### **1. 检查并确保环境兼容性** - **JDK本**:使用64位JDK,确保与macOS架构匹配。验证命令: ```bash java -version # 输出应包含"64-Bit Server VM" ``` - **IDE**:如IntelliJ或Eclipse,必须为64位本(Apple Silicon原生本更佳)。 - **操作系统**:确认macOS为ARM64架构(Apple Silicon),可通过终端运行`uname -m`,输出应为`arm64`。 #### **2. 在pom.xml中排除冲突依赖** - 在seata的依赖声明中添加`<exclusions>`,排除特定native依赖: ```xml <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <!-- 或您使用的seata artifact --> <version>1.7.1</version> <!-- 替换为您的seata--> <exclusions> <exclusion> <groupId>io.netty</groupId> <artifactId>netty-transport-native-kqueue</artifactId> <classifier>osx-aarch_64</classifier> <!-- 指定classifier避免匹配错误 --> </exclusion> </exclusions> </dependency> ``` - **为何有效**:seata间接依赖Netty,排除后强制Maven使用通用本,避免架构校验失败[^1]。 #### **3. 手动添加Netty依赖并指定classifier** - 显式添加兼容的Netty依赖,匹配本地架构: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-transport-native-kqueue</artifactId> <version>4.1.92.Final</version> <!-- 与错误中的本一致 --> <classifier>osx-aarch_64</classifier> <!-- 关键:指定classifier --> </dependency> ``` - **注意**:若Maven Central无此artifact: - 添加Netty仓库到pom.xml: ```xml <repositories> <repository> <id>netty-releases</id> <url>https://repo1.maven.org/maven2/</url> </repository> </repositories> ``` #### **4. 降级或升级Netty本** - **降级**:若seata本允许,使用低Netty(如4.1.86.Final),避免高本兼容性问题。 ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.86.Final</version> </dependency> ``` - **升级**:更新seata至最新(如1.8.0),可能已修复依赖冲突。 #### **5. 本地编译Netty(可选)** - 若上述无效,参考引用[1]方法编译Netty源码: 1. 克隆Netty仓库: ```bash git clone https://github.com/netty/netty.git ``` 2. 进入`netty-transport-native-kqueue`模块目录: ```bash cd netty/transport-native-unix-common ``` 3. 执行Maven编译: ```bash mvn clean compile -DskipTests ``` 4. 安装到本地仓库: ```bash mvn install -DskipTests ``` - **效果**:生成的artifact将包含`osx-aarch_64` classifier,供Maven本地解析[^1]。 --- ### **验证步骤** 1. 清理Maven缓存: ```bash mvn clean install -U ``` 2. 重新构建项目,确认错误是否消失。 3. 运行简单测试,验证seata功能正常。 --- ### **常见问题预防** - **依赖树分析**:使用`mvn dependency:tree`检查冲突,优先解决本不一致。 - **环境对齐**:确保JDK、IDE、操作系统均为64位ARM架构。 - **仓库镜像**:在企业内网中,配置Nexus镜像加速依赖下载。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值