我的第一款Netty应用程序
对Netty慕名已久,在初步学习了解了Netty之后决定写点东西记录一下,也方便以后回顾。
使用工具: idea ,maven
使用netty版本:4.1.6.Final
maven依赖如下:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
1、首先我们需要设置开发环境,安装jdk 和 maven 并且配置好环境变量,下载并且安装IDE,这里就不多说了。
2、在我们安装好环境之后我们需要创建一个工程用来编写服务端和客户端代码
a、首先我们需要用idea创建一个maven工程studyNetty,在pom文件里面加上上面的netty依赖包。
b、在我们创建的studyNetty工程下面创建两个子工程Server和Client。分别用于写服务器和客户端代码。
此时studyNetty工程下pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.study</groupId>
<artifactId>studyNetty</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>Server</module>
<module>Client</module>
</modules>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
</dependencies>
</project>
编写服务器代码
所有的 Netty 服务器都需要以下两部分,一是 “至少一个 ChannelHandler”— 该组件实现了服务器对从客户端接收的数据的处理,即它的业务逻辑。二是 “引导” — 这是配置服务器的启动代码,至少,它会将服务器绑定到它要监听连接请求的端口上。
第一步在服务端工程Server创建EchoServerHandler类继承ChannelInboundHandlerAdapter 用来处理服务端业务逻辑。
/**
* 处理服务端业务逻辑
* Sharable注解标识一个ChannelHandler可以被多个Channel安全的共享
*/
@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
/**
* 对于每个传入的消息都要调用
* @param ctx
* @param msg
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
//将消息打印到控制台
System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));
//将接收到的消息写给发送者,而不是冲刷出站消息
ctx.write(in);
}
/**
* 通知ChannelInboundHandler最后一次对channelRead()的调用是当前批量读取中的最后一条消息
* @param ctx
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
//将未决消息冲刷到远程节点,并关闭该Channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
/**
* 在读取操作期间,有异常抛出时调用
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
//打印异常栈跟踪
cause.printStackTrace();
//关闭该Channel
ctx.close();
}
}
第二部,编写好服务端处理代码后,我们就需要编写服务端启动代码。在服务端工程Server创建EchoServer类
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
/**
* 引导服务器
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
//用maven启动该方法时传入端口,参数个数部对时打印错误信息
if (args.length != 1) {
System.err.println("Usage: " + EchoServer.class.getSimpleName() + " <port>");
}
//获取传入端口
int port = Integer.parseInt(args[0]);
//调用启动客户端方法
new EchoServer(port).start();
}
public void start() throws Exception {
final EchoServerHandler serverHandler = new EchoServerHandler();
//创建EventLoopGroup
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class) //指定所使用的nio传输Channel
.localAddress(new InetSocketAddress(port))//使用指定端口设置套接字地址
.childHandler(new ChannelInitializer<SocketChannel>(){//添加一个EchoServerHandler 到子Channel 的 ChannelPipeline
@Override
public void initChannel(SocketChannel ch) throws Exception {
//EchoServerHandler被标注为@Shareable,所以我们可以总是使用同样的实例
ch.pipeline().addLast(serverHandler);
}
});
//异步的绑定服务器,调用sync()方法阻塞等待直到绑定完成
ChannelFuture f = b.bind().sync();
//获取channel的CloseFuture,并且阻塞当前线程直到它完成
f.channel().closeFuture().sync();
} finally {
//关闭EventLoopGroup,并释放所有资源
group.shutdownGracefully().sync();
}
}
}
到这里我们就编写好服务端代码啦,想要启动服务端代码可以在main函数里面给端口具体值用ide启动,也可以通过maven来启动,命令为mvn exec:java。具体怎么启动和和客户端通信待写完客户端代码后讲述。
编写客户端代码
第一步 通过 ChannelHandler 实现客户端逻辑。
在Client工程中创建EchoClientHandler类继承SimpleChannelInboundHandler
/**
* 处理客户端逻辑
* Sharable注解标识一个ChannelHandler可以被多个channel安全的共享
*/
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
/**
* 在到服务器的链接已经建立之后调用
* @param ctx
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
//当被通知Channel是活跃的时候,发送一条消息
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!",CharsetUtil.UTF_8));
}
/**
* 当服务器收到一条消息时调用
* @param channelHandlerContext
* @param in
* @throws Exception
*/
@Override
public void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf in) throws Exception {
//记录已经接受消息的转储
System.out.println( "Client received: " + in.toString(CharsetUtil.UTF_8));
}
/**
* 在处理过程中发生异常时调用
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
第二部 编写客户端启动代码 ,在Client工程创建EchoClient类
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
Bootstrap b = new Bootstrap();
b.group(group)//指定 EventLoopGroup 以处理客户端事件;需要适用于 NIO 的实现
.channel(NioSocketChannel.class) //适用于 NIO 传输的Channel 类型
.remoteAddress(new InetSocketAddress(host, port)) //设置服务器的InetSocketAddress
.handler(new ChannelInitializer<SocketChannel>() {//在创建Channel时,向 ChannelPipeline中添加一个 EchoClientHandler 实例
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoClientHandler());
}
});
//连接到远程节点,阻塞等待直到连接完成
ChannelFuture f = b.connect().sync();
//阻塞,直到Channel 关闭
f.channel().closeFuture().sync();
} finally {
//关闭线程池并且释放所有的资源
group.shutdownGracefully().sync();
}
}
/**
* 引导客户端
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println("Usage: " + EchoClient.class.getSimpleName() + " <host> <port>");
return;
}
String host = args[0];
int port = Integer.parseInt(args[1]);
new EchoClient(host, port).start();
}
}
至此服务端和客户端代码已经编写完毕
测试
下面我们就来测试编写好的客户端和服务端通信
我们在studyNetty工程pom文件上添加如下配置:
<properties>
<echo-server.hostname>localhost</echo-server.hostname>
<echo-server.port>9999</echo-server.port>
</properties>
在Server工程pom文件上添加如下配置:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>run-server</id>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.server.study.netty.EchoServer</mainClass>
<arguments>
<argument>${echo-server.port}</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
在Client工程pom文件上添加如下配置:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>run-client</id>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.client.study.netty.EchoClient</mainClass>
<arguments>
<argument>${echo-server.hostname}</argument>
<argument>${echo-server.port}</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
添加这些配置的原因是为了用maven命令来启动服务端和客户端引导代码。
用mvn clean package命令将工程打包后
在Server服务端工程用maven命令执行mvn exec:java 即启动服务端main()方法,可以看到客户端启动成功
在Client客户端工程用maven命令执行mvn exec:java 即启动客户端main()方法,这时可以看到客户端将消息传递给服务端打印出来,并且将消息返回给客户端,然后客户端关闭。
服务端端结果
客户端结果