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

本章介绍

  • 获得Netty4最新的版本号
  • 设置执行环境,以构建和执行netty程序
  • 创建一个基于Netty的server和client
  • 拦截和处理异常
  • 编制和执行Nettyserver和client

本章将简介Netty的核心概念,这个狠心概念就是学习Netty是怎样拦截和处理异常。对于刚開始学习netty的读者。利用netty的异常拦截机制来调试程序问题非常有帮助。本章还会介绍其它一些核心概念。如server和client的启动以及分离通道的处理程序。本章学习一些基础以便后面章节的深入学习。

本章中将编写一个基于netty的server和client来互相通信,我们首先来设置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 Nettyclient和server概述

本节将引导你构建一个完整的Nettyserver和client。普通情况下。你可能仅仅关心编写server,如一个httpserver的client是浏览器。然后在这个样例中,你若同一时候实现了server和client。你将会对他们的原理更加清晰。

一个Netty程序的工作图例如以下

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWJjX2tleQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >

  1. client连接到server
  2. 建立连接后,发送或接收数据
  3. server处理全部的client连接

从上图中能够看出,server会写数据到client而且处理多个client的并发连接。从理论上来说,限制程序性能的因素仅仅有系统资源和JVM。为了方便理解,这里举了个生活样例。在山谷或高山上大声喊,你会听见回声。回声是山返回的;在这个样例中,你是client。山是server。喊的行为就类似于一个Nettyclient将数据发送到server,听到回声就类似于server将同样的数据返回给你,你离开山谷就断开了连接。可是你能够返回进行重连server而且能够发送很多其它的数据。

尽管将同样的数据返回给client不是一个典型的样例。可是client和server之间数据的来来回回的传输和这个样例是一样的。本章的样例会证明这一点,它们会越来越复杂。

接下来的几节将带着你完毕基于Netty的client和server的应答程序。

2.3 编写一个应答server

写一个Nettyserver主要由两部分组成:

  • 配置server功能,如线程、port
  • 实现server处理程序,它包括业务逻辑,决定当有一个请求连接或接收数据时该做什么

2.3.1 启动server

通过创建ServerBootstrap对象来启动server。然后配置这个对象的相关选项。如port、线程模式、事件循环,而且加入逻辑处理程序用来处理业务逻辑(以下是个简单的应答server样例)

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

}

从上面这个简单的server样例能够看出,启动server应先创建一个ServerBootstrap对象。由于使用NIO,所以指定NioEventLoopGroup来接受和处理新连接。指定通道类型为NioServerSocketChannel,设置InetSocketAddress让server监听某个端口已等待client连接。

接下来,调用childHandler放来指定连接后调用的ChannelHandler。这种方法传ChannelInitializer类型的參数。ChannelInitializer是个抽象类,所以须要实现initChannel方法,这种方法就是用来设置ChannelHandler。

最后绑定server等待直到绑定完毕。调用sync()方法会堵塞直到server完毕绑定,然后server等待通道关闭。由于使用sync(),所以关闭操作也会被堵塞。如今你能够关闭EventLoopGroup和释放全部资源,包含创建的线程。

这个样例中使用NIO,由于它是眼下最经常使用的传输方式。你可能会使用NIO非常长时间。可是你能够选择不同的传输实现。比如。这个样例使用OIO方式传输,你须要指定OioServerSocketChannel。Netty框架中实现了多重传输方式,将再后面讲述。

本小节重点内容:

  • 创建ServerBootstrap实例来引导绑定和启动server
  • 创建NioEventLoopGroup对象来处理事件,如接受新连接、接收数据、写数据等等
  • 指定InetSocketAddress。server监听此port
  • 设置childHandler运行全部的连接请求
  • 都设置完成了,最后调用ServerBootstrap.bind() 方法来绑定server

2.3.2 实现server业务逻辑

Netty使用futures和回调概念。它的设计同意你处理不同的事件类型。更具体的介绍将再后面章节讲述。可是我们能够接收数据。

你的channel handler必须继承ChannelInboundHandlerAdapter而且重写channelRead方法,这种方法在不论什么时候都会被调用来接收数据,在这个样例中接收的是字节。

以下是handler的实现,事实上现的功能是将client发给server的数据返回给client:

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非常easy,它的每一个方法都能够被重写,它的全部的方法中仅仅有channelRead方法是必需要重写的。

2.3.3 捕获异常

重写ChannelHandler的exceptionCaught方法能够捕获server的异常,比方client连接server后强制关闭,server会抛出"client主机强制关闭错误",通过重写exceptionCaught方法就能够处理异常,比方发生异常后关闭ChannelHandlerContext。

2.4 编写应答程序的client

server写好了。如今来写一个client连接server。应答程序的client包含下面几步:

  • 连接server
  • 写数据到server
  • 等待接受server返回同样的数据
  • 关闭连接

2.4.1 引导client

引导client启动和引导server非常类似,client需同一时候指定host和port来告诉client连接哪个server。看以下代码:

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

创建启动一个client包括以下几步:

  • 创建Bootstrap对象用来引导启动client
  • 创建EventLoopGroup对象并设置到Bootstrap中,EventLoopGroup能够理解为是一个线程池。这个线程池用来处理连接、接受数据、发送数据
  • 创建InetSocketAddress并设置到Bootstrap中。InetSocketAddress是指定连接的server地址
  • 加入一个ChannelHandler,client成功连接server后就会被运行
  • 调用Bootstrap.connect()来连接server
  • 最后关闭EventLoopGroup来释放资源

2.4.2 实现client的业务逻辑

client的业务逻辑的实现依旧非常easy,更复杂的使用方法将在后面章节具体介绍。

和编写server的ChannelHandler一样,在这里将自己定义一个继承SimpleChannelInboundHandler的ChannelHandler来处理业务;通过重写父类的三个方法来处理感兴趣的事件:

  • channelActive():client连接server后被调用
  • channelRead0():从server接收到数据后调用
  • 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接口达到的。

为什么在server中不使用SimpleChannelInboundHandler呢?由于server要返回同样的消息给client,在server运行完毕写操作之前不能释放调用读取到的消息,由于写操作是异步的,一旦写操作完毕后,Netty中会自己主动释放消息。

client的编写完了,以下让我们来測试一下

2.5 编译和执行echo(应答)程序client和server

注意,netty4须要jdk1.7+。

本人測试,能够正通常执行。

2.6 总结一下

本章介绍如何编写基于一个简单的Netty的server和client和发送数据通信。它描述了如何创建server和client以及Netty异常处理机制。

时间: 2024-10-26 16:25:20

Netty In Action中国版 - 第二章:第一Netty程序的相关文章

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

注:本篇内容出自<Netty In Action>一书:         注:本人原创译文,转载请注明出处! 本章介绍 获取Netty4最新版本 设置运行环境来构建和运行netty程序 创建一个基于Netty的服务器和客户端 拦截和处理异常 编写和运行Netty服务器和客户端 本章将简单介绍Netty的核心概念,这个狠心概念就是学习Netty是如何拦截和处理异常,对于刚开始学习netty的读者,利用netty的异常拦截机制来调试程序问题很有帮助.本章还会介绍其他一些核心概念,如服务器和客户端的

软件构造 第二章 第一节 软件生命周期和版本控制

软件构造第二章 第一节 软件生命周期和版本控制 基本内容 Software Development Lifecycle (SDLC) Traditional software process models (waterfall, incremental, V- model, prototyping, spiral) Agile development and eXtreme Programming (XP) Collaborative software development Software

【C#入门经典(第五版)】第二章 编写C#程序

第二章 编写C#程序 1. CTS:通用类型系统(Common Type System);--->公共类型系统. 2. CLR:公共语言运行库(Common Language Runtime); 3. CIL: 通用中间语言(Common Intermediate Language); 4. JIT编译器:Just-In-Time; 5. GAC:全局程序集缓存(Global Assembly Caches); 6. 垃圾回收: 7. 链接: 8. ADO.NET:Active Data Obj

Pro ASP.NET Core MVC 第6版 第二章(前半章)

目录 第二章 第一个MVC 应用程序 学习一个软件开发框架的最好方法是跳进他的内部并使用它.在本章,你将用ASP.NET Core MVC创建一个简单的数据登录应用.我将它一步一步地展示,以便你能看清楚怎样构建一个MVC 应用程序.为了让事情简单,我跳过了一些技术细节,但是不要担心,如果你是一个MVC的新手,你将会发现许多东西足够提起你的兴趣.因为我用的东西有些没做解释,所以我提供了一些参考以便你可以看到所有的细节的东西. 安装Visual Studio 要想根据本书实践的话,必须安装Visua

Netty In Action中文版 - 第六章:ChannelHandler

本章介绍 ChannelPipeline ChannelHandlerContext ChannelHandler Inbound vs outbound(入站和出站) 接受连接或创建他们仅仅是你的应用程序的一部分,尽管这些不论什么非常重要,可是一个网络应用程序旺旺是更复杂的,须要很多其它的代码编写,如处理传入和传出的数据.Netty提供了一个强大的处理这些事情的功能,同意用户自己定义ChannelHandler的实现来处理数据.使得ChannelHandler更强大的是能够连接每一个Chann

python基础教程(第2版)第二章读后总结;

python第二章 序列是一种数据结构:典型的序列包括:列表,字符串,元组 数据结构是通过某种方式(例如对元素进行编号)组织在一起的数据元素的集合,这些数据元素 可以是数字或者字符,甚至可以是其他数据结构,在python中,最近本的数据结构是序列,序列中的每个元素被 分配一个序号-----即元素的位置,也成为索引,第一个索引是0,第二个则是1,以此类推. 列表和元组的主要区别: 列表可以修改,元组则不能,但他们都统称为序列; 列表的各个元素通过逗号分离,写在方括号中: 元组的各个元素通过逗号分离

Netty In Action中文版 - 第四章:Transports(传输)

本章内容 Transports(传输) NIO(non-blocking IO,New IO), OIO(Old IO,blocking IO), Local(本地), Embedded(嵌入式) Use-case(用例) APIs(接口) 网络应用程序一个非常重要的工作是数据传输. 数据传输的过程不一样取决是使用哪种交通工具,可是传输的方式是一样的:都是以字节码传输.Java开发网络程序数据传输的过程和方式是被抽象了的.我们不须要关注底层接口.仅仅须要使用Java API或其它网络框架如Net

Python核心编程第三版第二章学习笔记

第二章 网络编程 1.学习笔记 2.课后习题 答案是按照自己理解和查阅资料来的,不保证正确性.如由错误欢迎指出,谢谢 1. 套接字:A network socket is an endpoint of a connection across a computer network,Sockets are often represented internally as simple integers, which identify which connection to use. 套接字是网络通信的

C#高级编程第11版 - 第二章

导航 C# 全版本特性一览 全书目录 第二章 Core C 2.1 C#基础 29 2.2 变量 31 2.2.1 初始化变量 31 2.2.2 类型推断 32 2.2.3 变量的作用域 33 2.2.4 常量 34 2.3 预定义数据类型 35 2.3.1 值类型和引用类型 35 2.3.2 .NET 类型 36 2.3.3 预定义的值类型 36 2.3.4 预定义的引用类型 40 2.4 程序流控制 42 2.4.1 条件语句 42 2.4.2 循环 44 2.4.3 跳转语句 47 2.5