`

Netty系列之一:回显服务端和客户端

阅读更多
Netty是一款基于Java NIO的框架,能够建立通道、
处理事件、编解码和异常处理等,为上层应用提供了清晰、简洁的开发接口:减少用户的编码和错误,使应用开发者能够把注意力集中在业务逻辑上。

下面以回显功能为例:

一、服务端:

1. 实例化引导类

抽象类为AbstractBootstrap,服务端使用ServerBootstrap:
ServerBootstrap b = new ServerBootstrap();


2. 设置参数

Netty将EventLoopGroup, Channel, Address, Handler以及其它配置都放到了AbstractBootstrap中,统一设置:


a. 设置事件组

Netty是基于事件处理的,EventLoopGroup是接口,实现类有NioEventLoopGroup和OioEventLoopGroup (Old IO即Java IO) 等:

EventLoopGroup bossGroup = new NioEventLoopGroup(); // 接受连接事件组
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理每个连接业务事件组
b.group(bossGroup, workerGroup); // 可以只使用一个group,分成两个group的好处是:业务耗时较长导致阻塞时,不会对接受连接造成影响。


b. 设置通道类型

接口为ServerSocketChannel,实现类有NioServerSocketChannel和OioServerSocketChannel。这里使用NioServerSocketChannel
b.channel(NioServerSocketChannel.class);


c. 设置服务启动绑定的地址

传入一个SocketAddress实例,指定端口即可(如8080):
b.localAddress(new InetSocketAddress(8080))

当然,也可以在真正绑定的时候设置:
ChannelFuture f = b.bind(new InetSocketAddress(8080)).sync();

调用sync()会等待前面的方法执行完毕,后面会有很多这样的写法。
ChannelFuture就是Channel的Future类,可以拿到执行结果。

d. 设置childHandler

实现ChannelInitializer接口的initChannel方法,将处理器(业务逻辑)加到通道管道的末尾:
b.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(new EchoServerHandler());
    }
});


3. 绑定操作

和Socket的绑定类似,如果地址已经通过localAddress设定,这里就可以调用无参方法:
ChannelFuture f = b.bind().sync();


4. 等待结束
f.channel().closeFuture().sync();


5. 最终

在finally块中关闭事件组以及线程池等资源:
group.shutdownGracefully().sync();



二、回显客户端:

和服务端大体类似。不同的地方:

1'. 设置客户端引导类:
Bootstrap b = new Bootstrap();


2'. 设置参数:

a'. 设置事件组

一个客户端只有一个通道,一个group就够了:

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


b'. 设置通道类型

根据使用要求,也可以使用其它类型的客户端通道,如OioSocketChannel:
b.channel(NioSocketChannel.class);


c'. 设置连接地址:

因为是客户端,需要指定连接的服务端主机和端口:
b.remoteAddress(new InetSocketAddress(host, port));


d'. 设置事件处理类:

注意这里的方法是handler
b.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(new EchoClientHandler());
    }
});


3'. 连接:

这里是连接,而不是绑定。

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


如果远程地址没有在remoteAddress中设定,需要在连接时设置:
ChannelFuture f = b.connect("localhost", 8080);


三、事件处理类

EchoServerHandler,EchoClientHandler都是先往引导程序注册,事件发生时触发相应处理方法:

i. 服务器事件处理类

EchoServerHandler扩展io.netty.channel.ChannelInboundHandlerAdapter类,重写下面三个方法,当然,可以根据需要重写更多的方法:

channelRead: 服务端收到客户端发来的数据

channelReadComplete: 服务端读取客户端数据完毕

exceptionCaught: 发生异常,比如客户端关闭连接时

ii. 客户端事件处理类

EchoClientHandler扩展io.netty.channel.SimpleChannelInboundHandler类,重写下面三个方法:

channelActive: 连接已建立,这时可以向服务端发送数据

channelRead0: 服务端向客户端发送数据,可以读取

exceptionCaught: 发生异常,比如服务端关闭连接时

四、代码

回显服务端

public class EchoServer {

	private final int port; // 服务端绑定端口

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

	public void start() throws Exception {
		EventLoopGroup bossGroup = new NioEventLoopGroup(); // 接受连接事件组
		EventLoopGroup workerGroup = new NioEventLoopGroup(); // 每个连接业务处理事件组

		try {
			ServerBootstrap b = new ServerBootstrap(); // 引导器
			// 指定事件组,通道、绑定地址、业务处理器
			b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
					.localAddress(new InetSocketAddress(port)).childHandler(new ChannelInitializer<SocketChannel>() { // 虽然EchoServerHandler和ChannelInitializer都是ChannelHandler的实现类,但这里不能直接传入EchoServerHandler,否则会导致业务处理器无法使用

						@Override
						protected void initChannel(SocketChannel ch) throws Exception {
							// 将业务处理器加到通道管理线(处理器队列)的末尾
							ch.pipeline().addLast(new EchoServerHandler());
						}
					});

			ChannelFuture f = b.bind().sync(); // 绑定指定端口
			System.out.println(EchoServer.class.getName() + " started and listen on " + f.channel().localAddress());
			f.channel().closeFuture().sync();
		} finally {
			bossGroup.shutdownGracefully().sync(); // 释放资源和线程池
		}
	}

	public static void main(String[] args) throws Exception {
		args = new String[1];
		args[0] = "8180";

		if (args.length != 1) {
			System.err.println("?Usage: ?" + EchoServer.class.getSimpleName() + "<port>?");
		}

		int port = Integer.parseInt(args[0]);
		new EchoServer(port).start();
	}
}


客户端
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());
						}
					});

			// 连接到服务端,sync()阻塞直到连接过程结束
			ChannelFuture f = b.connect().sync();

			// 等待通道关闭
			f.channel().closeFuture().sync();
		} finally {
			// 关闭引导器并释放资源,包括线程池
			group.shutdownGracefully().sync();
		}
	}

	public static void main(String[] args) throws Exception {
		args = new String[2];
		args[0] = "mysit.cnsuning.com";
		args[1] = "8180";
		
		if (args.length != 2) {
			System.err.println("Usage: " + EchoClient.class.getSimpleName() + " <host> <port>");
			return;
		}

		final String host = args[0];
		final int port = Integer.parseInt(args[1]);
		new EchoClient(host, port).start();
	}
}


服务端处理

public class EchoServerHandler extends ChannelInboundHandlerAdapter {

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		ByteBuf buf = (ByteBuf) msg;
		System.out.println("Server received: " + ByteBufUtil.hexDump(buf.readBytes(buf.readableBytes()))); 缓冲内部存储读写位置,readBytes将指针后移
		// System.out.println("?Server received: ?" + msg);
		buf.resetReaderIndex(); // 重置读写位置,如果省略这一句,ctx.write(msg)往客户端发送的数据为空
		ctx.write(msg);
	}

	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		// 读写完毕,调用flush将数据真正发送到客户端
		ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace(); // 打印异常
		ctx.close(); // 关闭通道
	}
}


客户端处理
@Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		// 连接建立,向服务端发送数据
		ctx.write(Unpooled.copiedBuffer("Hello Netty!", CharsetUtil.UTF_8));

		// 注意:需要调用flush将数据发送到服务端
		ctx.flush();
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		// 打印异常并关闭通道
		cause.printStackTrace();
		ctx.close();
	}

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
		// 读取服务端返回的数据并打印
		System.out.println("Client received: " + ByteBufUtil.hexDump(msg.readBytes(msg.readableBytes())));
	}
}
分享到:
评论

相关推荐

    Netty3客户端服务端hi allen.zip

    在本案例中,"Netty3客户端服务端hi allen.zip" 提供了一个基于 Netty 3 的简单客户端和服务端示例,旨在帮助初学者理解和入门 Netty 3。 首先,我们需要了解 Netty 的核心概念。Netty 是基于 Java NIO(非阻塞I/O...

    Netty3.x 源码解析

    Netty源码阅读的目的通常有两个:一是因为工作中使用到了Netty,希望通过阅读源码来更加深入地了解它;二是出于对Java网络编程的兴趣,希望通过学习Netty来探索如何构建高性能网络应用。同时,Netty的代码结构组织...

    java Netty 框架例子源码.rar

    - **Echo 示例**:最基本的 Netty 应用,服务端接收客户端发送的数据并回显,展示了 Netty 的基本使用。 - **HTTP 示例**:展示如何使用 Netty 实现一个简单的 HTTP 服务器,处理 GET 和 POST 请求。 - **WebSocket ...

    netty-in-action中文版

    - **Netty客户端/服务端总览**:通过概述客户端和服务端的基本结构,帮助读者理解它们之间的通信机制。 - **写一个echo服务器**:通过实现简单的回显服务器来展示Netty的基本使用方法。 - **写一个echo客户端**:与...

    Java学习之IO总结及mina和netty

    `TCPEchoClientNonblocking`和`TCPServerSelector$EchoServerProtocol`可能是Netty中的示例代码,`TCPEchoClientNonblocking`可能是一个非阻塞的TCP回显客户端,而`EchoServerProtocol`可能是自定义的服务端协议处理...

    Netty 3.2 用户手册

    这个过程介绍了如何构建一个基础的Netty服务端和客户端,以及如何处理ChannelBuffer,即Netty中的数据容器。 手册接着提供了对ReceivedData的详解,通过编写Echo服务,即对收到的任何消息进行回显,来进一步解释...

    JavaNetty.rar 消息通信 补充了很多注释

    "EchoClient.rar" 和 "EchoServer.rar" 可能是两个核心部分,分别代表了Netty中的回声服务端和客户端。 Netty的核心概念之一是它的异步I/O模型,它基于NIO(非阻塞I/O)构建,使得在处理大量并发连接时表现出色。...

    以netty4.1源码中的EchoServer为例对netty的源码进行分析.docx

    Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。在本文中,我们将深入分析 Netty 4.1 源码中的 EchoServer 示例,以理解其核心组件和工作原理。 首先,我们...

    netty 新NIO框架 文档

    ### Netty新NIO框架知识点概述 #### 一、Netty框架简介 Netty是一款高性能的网络应用开发框架,它采用事件驱动...无论是构建高性能的服务端应用还是处理复杂的网络协议,Netty都是一个值得深入学习和使用的强大工具。

    nio-benchmark:Java NIO回显服务器的基准测试

    cycle = 如果为true,则控制台将打印客户端/服务器的回显(默认为false) 服务器 可用的服务器实现: nio.ReadInSelectorServer nio.ReadInTaskServer netty.NettyServer 两个nio服务器都接受以下可选args: ...

    一款iOS平台UDP双向通信源码

    在源码中,`ios_echo_client_udp`很可能是一个简单的UDP回显客户端,它向服务器发送一个数据包,然后等待服务器回传相同的数据。这是测试网络连接和延迟的一个常见做法。客户端发送一个消息,服务器接收到后立即原样...

    DotNetty_Test.rar

    在"DotNetty_Test.rar"这个压缩包中,包含了两个关键的文件:Echo.Server和Echo.Client,这显然是一个简单的回显服务示例,展示了如何使用DotNetty进行客户端和服务端之间的通信。 首先,让我们深入了解一下...

    RunCodeOnline:Java+Netty+Docker开发的在线运行代码后端服务

    用户前端通过Websocket和后端服务器连接,将编程语言类型、源代码(目前仅支持单文件)传到服务器,服务器交给Docker运行,并将执行过程中实时产生的输出回显给客户端。 保存代码(HTTP服务) 保存代码的目的是记录...

    javatcp.rar_java TCP线程_java tcp 线程

    在实际应用中,Java的NIO(非阻塞I/O)和Netty框架也可以提供更高效、更灵活的并发处理机制,比如使用选择器(Selector)来监听多个套接字通道,减少线程的创建和管理开销。 在“javatcp.rar”压缩包中的文件可能...

Global site tag (gtag.js) - Google Analytics