开发环境
ubuntu18,jdk1.8,maven
创建多模块的maven项目
创建好的目录:
├── echoclient
│ ├── pom.xml
│ ├── src
├── echoserver
│ ├── pom.xml
│ ├── src
├── pom.xml
服务器代码
在echo/echoserver/src/main/java/netty/echo 目录,创建EchoServerHandler.java(删除原来的App.java),用以处理服务端逻辑:
package nia.chapter2.echoserver;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/**
* 代码清单 2-1 EchoServerHandler
*
* @author <a href="mailto:norman.maurer@gmail.com">Norman Maurer</a>
*/
//标示一个ChannelHandler可以被多个 Channel 安全地共享
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@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);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx)
throws Exception {
//将未决消息冲刷到远程节点,并且关闭该 Channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
//打印异常栈跟踪
cause.printStackTrace();
//关闭该Channel
ctx.close();
}
}
创建EchoServer.java,用以服务器启动引导代码:
package nia.chapter2.echoserver;
import io.netty.bootstrap.ServerBootstrap;
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.NioServerSocketChannel;
import java.net.InetSocketAddress;
/**
* 代码清单 2-2 EchoServer 类
*
* @author <a href="mailto:norman.maurer@gmail.com">Norman Maurer</a>
*/
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args)
throws Exception {
if (args.length != 1) {
System.err.println("Usage: " + EchoServer.class.getSimpleName() +
" <port>"
);
return;
}
//设置端口值(如果端口参数的格式不正确,则抛出一个NumberFormatException)
int port = Integer.parseInt(args[0]);
//调用服务器的 start()方法
new EchoServer(port).start();
}
public void start() throws Exception {
final EchoServerHandler serverHandler = new EchoServerHandler();
//(1) 创建EventLoopGroup
EventLoopGroup group = new NioEventLoopGroup();
try {
//(2) 创建ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
b.group(group)
//(3) 指定所使用的 NIO 传输 Channel
.channel(NioServerSocketChannel.class)
//(4) 使用指定的端口设置套接字地址
.localAddress(new InetSocketAddress(port))
//(5) 添加一个EchoServerHandler到于Channel的 ChannelPipeline
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
//EchoServerHandler 被标注为@Shareable,所以我们可以总是使用同样的实例
//这里对于所有的客户端连接来说,都会使用同一个 EchoServerHandler,因为其被标注为@Sharable,
//这将在后面的章节中讲到。
ch.pipeline().addLast(serverHandler);
}
});
//(6) 异步地绑定服务器;调用 sync()方法阻塞等待直到绑定完成
ChannelFuture f = b.bind().sync();
System.out.println(EchoServer.class.getName() +
" started and listening for connections on " + f.channel().localAddress());
//(7) 获取 Channel 的CloseFuture,并且阻塞当前线程直到它完成
f.channel().closeFuture().sync();
} finally {
//(8) 关闭 EventLoopGroup,释放所有的资源
group.shutdownGracefully().sync();
}
}
}
客户端代码
进入echo/echoclient/src/main/java/netty/echo 目录,删除App.java,创建EchoClientHandler.java,用以客户端逻辑
package nia.chapter2.echoclient;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
/**
* 代码清单 2-3 客户端的 ChannelHandler
*
* @author <a href="mailto:norman.maurer@gmail.com">Norman Maurer</a>
*/
@Sharable
//标记该类的实例可以被多个 Channel 共享
public class EchoClientHandler
extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelActive(ChannelHandlerContext ctx) {
//当被通知 Channel是活跃的时候,发送一条消息
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!",
CharsetUtil.UTF_8));
}
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
//记录已接收消息的转储
System.out.println(
"Client received: " + in.toString(CharsetUtil.UTF_8));
}
@Override
//在发生异常时,记录错误并关闭Channel
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
创建EchoClient.java,用于客户端启动引导:
package nia.chapter2.echoclient;
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 java.net.InetSocketAddress;
/**
* 代码清单 2-4 客户端的主类
*
* @author <a href="mailto:norman.maurer@gmail.com">Norman Maurer</a>
*/
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();
//指定 EventLoopGroup 以处理客户端事件;需要适用于 NIO 的实现
b.group(group)
//适用于 NIO 传输的Channel 类型
.channel(NioSocketChannel.class)
//设置服务器的InetSocketAddress
.remoteAddress(new InetSocketAddress(host, port))
//在创建Channel时,向 ChannelPipeline中添加一个 EchoClientHandler实例
.handler(new ChannelInitializer<SocketChannel>() {
@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();
}
}
public static void main(String[] args)
throws Exception {
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();
}
}
整体编译
进入echo目录 执行
mvn clean package
出现一堆错误:
package io.netty.buffer does not exist
package io.netty.buffer does not exist
package io.netty.channel.ChannelHandler does not exist
package io.netty.channel does not exist
package io.netty.channel does not exist
package io.netty.util does not exist
应该是缺少netty 的依赖,在echo目录下的pom.xml下添加:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
重新编译,还是报错:
echo/echoclient/src/main/java/netty/echo/EchoClientHandler.java:[26,5] method does not override or implement a method from a supertype
因为在netty5中,channelRead0() → messageReceived()做了变化,所以要改一下客户端代码:
@Override
//public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
public void messageReceived(ChannelHandlerContext ctx, ByteBuf in) {
//记录已接收消息的转储
System.out.println(
"Client received: " + in.toString(CharsetUtil.UTF_8));
}
这样应该就可以了
[INFO] Building jar: /home/ubuntu/mywork/netty/project/echotest/echo/echoserver/t
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for echo 1.0-SNAPSHOT:
[INFO]
[INFO] echo ............................................... SUCCESS [ 0.564 s]
[INFO] echoclient ......................................... SUCCESS [ 4.934 s]
[INFO] echoserver ......................................... SUCCESS [ 1.403 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.171 s
[INFO] Finished at: 2019-09-20T20:51:55+08:00
[INFO] ------------------------------------------------------------------------
运行服务器
打开一个窗口,在echoserver目录下执行:
mvn exec:java -Dexec.args="9999"
指定服务器的监听端口:9999
貌似还是报错,是什么mainclass的问题,比较了下和源码的pom.xml,还缺点东西:
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>nia.chapter2.echoserver.EchoServer</mainClass>
</configuration>
</plugin>
</plugins>
</pluginManagement>
……
加入<plugin>..</plugin>这一段,应该就可以了,注意类名要写对:
nia.chapter2.echoserver.EchoServer started and listening for connections on /0.0.0.0:9999
运行客户端
同样要补充下pom.xml,指定mainclass
打开新窗口,在echoclient目录下执行:
mvn exec:java -Dexec.args="127.0.0.1 9999"
向本机的9999发起请求,收到响应:
Client received: Netty rocks!
同样服务器端也可以看到:
Server received: Netty rocks!
参考: