【Netty入门和实践】3.Netty服务端helloWorld入门

上两篇我们探讨了传统IO和NIO的区别,本篇就来正式介绍Netty的内容。

一、Netty介绍
学习了NIO的童鞋应该都知道,NIO是一个非阻塞的多线程的socket网络通信API,而正如我们上一篇写的demo一样,每次使用NIO进行网络通信的时候我们都需要自己编写网络交互的服务端、客户端,都要去编写数据的接收、解析、返回等逻辑方法,十分麻烦。基于此,JBOSS就推出了一个可以快速开发高性能、高可靠性的网络服务器和客户端程序的框架-----Netty。

Netty说白了就是一个基于NIO的客户、服务器端编程框架,本来需要写很久的客户端和服务端的代码,这里只需要Netty就可以快速、简单的开发出一个实现某种协议的网络应用。

Netty版本大致版本分为Netty3.x、Netty4.x和Netty5.x。
Netty在目前开发中的应用主要在以下两个方面:
1.分布式进程通信
像Hadoop、dubbo、akka等具有分布式功能的框架,底层RPC通信都是基于Netty实现的,这些框架版本通常都还在使用Netty3.x。

2.游戏服务器开发
众所周知,许多网络游戏需要在各个客户端传输网络信息,所以也需要快速搭建客户端与服务端之间的网络交互逻辑。
最新的游戏服务器有部分公司可能已经开始使用Netty4.x和Netty5.x。


二、Netty服务端hello world案例
我们这里的样例主要针对Netty3.x进行讲解,后面逐步衍生到Netty4.x以上,以便于大家对Netty的逐步理解。
首先打开Eclipse,我们新建一个java项目。名为“Netty_Hello”:
【Netty入门和实践】3.Netty服务端helloWorld入门_第1张图片
然后创建一个名为“libs”的文件夹,用于放置依赖jar包。我们在里面放置的是netty3.10.5的依赖jar和源码:
【Netty入门和实践】3.Netty服务端helloWorld入门_第2张图片
并且将netty-3.10.5.Final.jar的依赖add进编译环境。
我们在src下新建一个类(com.server包下)Server:
【Netty入门和实践】3.Netty服务端helloWorld入门_第3张图片
代码:
package com.server;
import org.jboss.netty.channel.Channels;

import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;

public class Server {
    public static void main(String[] args) {
		//1.创建一个Netty服务类
    	/**
         * ServerBootstrap 是一个启动NIO服务的辅助启动类
         * 你可以在这个服务中直接使用Channel
         * */
    	ServerBootstrap bootstrap = new ServerBootstrap();
    	
    	//2.创建两个线程池
    	/**
    	 * 第一个经常被叫做‘boss’,用来接收进来的连接。
         * 第二个经常被叫做‘worker’,用来处理已经被接收的连接,
         * 一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上。
    	 * */
    	ExecutorService boss = Executors.newCachedThreadPool();
    	ExecutorService worker = Executors.newCachedThreadPool();
    	
    	//3.为服务类设置一个NioSocket工厂
    	bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker));
	
        //4.设置管道的工厂(匿名内部类实现)
    	bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			
			@Override
			public ChannelPipeline getPipeline() throws Exception {
				ChannelPipeline pipeline = Channels.pipeline();
				//设置一个处理服务端消息和各种消息事件的类(Handler)  
				pipeline.addLast("hellohandler", new HelloHandler());
				return pipeline;
			}
		});
    	
    	//5.为服务端设置一个端口
    	bootstrap.bind(new InetSocketAddress(10101));
    
        System.out.println("server start!");
    }
}
其中的HelloHandler也是我们自己新建的,继承了SimpleChannelHandler:
package com.server;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

public class HelloHandler extends SimpleChannelHandler{

	/**
	 * 连接关闭
	 * */
	@Override
	public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
			throws Exception {
		System.out.println("channelClosed");
		super.channelClosed(ctx, e);
	}

	/**
	 * 新建连接
	 * */
	@Override
	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
			throws Exception {
		System.out.println("channelConnected");
		super.channelConnected(ctx, e);
	}

	/**
	 * 连接断开
	 * */
	@Override
	public void channelDisconnected(ChannelHandlerContext ctx,
			ChannelStateEvent e) throws Exception {
		System.out.println("channelDisconnected");
		super.channelDisconnected(ctx, e);
	}

	/**
	 * 捕获异常
	 * */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
			throws Exception {
		System.out.println("exceptionCaught,error:"+e.toString());
		super.exceptionCaught(ctx, e);
	}

	/**
	 * 消息接收
	 * */
	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
			throws Exception {
		System.out.println("messageReceived");
		//获取到的信息是使用ChannelBuffer包装的字节流
		ChannelBuffer message = (ChannelBuffer)e.getMessage();
		String msg = new String(message.array());//将字节流转换为String
		System.out.println(msg);//打印收到的字符串
		super.messageReceived(ctx, e);
	}
}
实现了以下方法:
【Netty入门和实践】3.Netty服务端helloWorld入门_第4张图片
这些方法分别是处理网络连接、信息接收、连接断开、连接关闭、异常捕获的方法,我们为了看清楚它们的调用顺序,这里在重写的方法中打印了一些信息。

服务端搭建完毕之后,右键运行起来:

然后我们使用cmd控制台的telnet调用该服务:
【Netty入门和实践】3.Netty服务端helloWorld入门_第5张图片
回车之后,观察控制台,可以看到连接已经建立:
【Netty入门和实践】3.Netty服务端helloWorld入门_第6张图片
我们在cmd中使用“ctrl+]”进入消息输入界面,向服务端发送“hello”信息:
【Netty入门和实践】3.Netty服务端helloWorld入门_第7张图片
可以看到服务端收到了客户端传来的消息。

然后我们关闭cmd,可以看到控制台先打印了“channelDisconnected”,然后打印了“channelClosed”:
【Netty入门和实践】3.Netty服务端helloWorld入门_第8张图片
原因是,对于channelDisconnected方法,是必须有连接建立之后,关闭通道的时候才会触发此方法,而对于channelClosed就是无论连接建立与否,在channel关闭的时候就会触发。(例如客户端连接失败后,不会触发channelDisconnected,只会触发channelClosed)。

小伙伴们可以发现,我们的消息接收时总是要把字节流转换为String字符串,可不可以省去这一步,直接拿到字符串呢?是可以的,我们只需要在服务端的管道的工厂中设置decoder解码类为“StringDecoder”类即可:
//4.设置管道的工厂(匿名内部类实现)
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
	
	@Override
	public ChannelPipeline getPipeline() throws Exception {
		ChannelPipeline pipeline = Channels.pipeline();
		//设置解码方式为String
		pipeline.addLast("decoder", new StringDecoder());
		//设置一个处理服务端消息和各种消息事件的类(Handler)  
		pipeline.addLast("hellohandler", new HelloHandler());
		return pipeline;
	}
});
然后接收方法我们直接使用String接收即可:
/**
 * 消息接收
 * */
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
		throws Exception {
	System.out.println("messageReceived");
	String msg = (String)e.getMessage();//将字节流转换为String
	System.out.println(msg);//打印收到的字符串
	super.messageReceived(ctx, e);
}
实验:
【Netty入门和实践】3.Netty服务端helloWorld入门_第9张图片
可以看到使用String接收成功,是因为我们设置的decoder类帮我们把字符流转换为String类型的。

我们的Server服务端接收到信息后,可以会写给客户端信息,我们在messageReceived中编写回复逻辑:
/**
 * 消息接收
 * */
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
		throws Exception {
	System.out.println("messageReceived");
	String msg = (String)e.getMessage();//将字节流转换为String
	System.out.println(msg);//打印收到的字符串
	
	//回写数据(需要用ChannelBuffer包装要回复的字节流信息)
	ChannelBuffer channelBuffer = ChannelBuffers.copiedBuffer("hi".getBytes());
	ctx.getChannel().write(channelBuffer);
	super.messageReceived(ctx, e);
}
即是使用ChannelHandlerContext获取Channel,然后使用writer我们需要给客户端回复的信息。

然后我们重新启动服务端,然后使用telnet进去后(不用ctrl+])随便输一个字符(这里输入a),就拿到了服务端的回复信息:
【Netty入门和实践】3.Netty服务端helloWorld入门_第10张图片
如果我们在ctrl+]状态下输入信息时,发送很多信息后,直接关闭cmd窗口,服务端就会捕获一个强制关闭的异常:

以上就是一个基于Netty技术的的网络通信服务端的一个样例,下一篇讲解基于Netty的客户端的样例,并且与本篇的服务端进行通信。

转载请注明出处:https://blog.csdn.net/acmman/article/details/80298062

你可能感兴趣的:(Netty)