附录 A 完整工程代码
本附录内容由随书工程源文件直接生成,避免电子书与工程发生手工抄写差异。请按标出的路径保存文件,并保留 package book;。项目根目录是 examples,所有 Maven 命令都在该目录执行。
pom.xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion><groupId>book</groupId><artifactId>netty-beginner</artifactId><version>1.0.0</version>
<properties><maven.compiler.release>17</maven.compiler.release><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><netty.version>4.1.139.Final</netty.version></properties>
<dependencies><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>${netty.version}</version></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.11.4</version><scope>test</scope></dependency></dependencies>
<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.13.0</version></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>3.5.2</version></plugin><plugin><groupId>org.codehaus.mojo</groupId><artifactId>exec-maven-plugin</artifactId><version>3.5.0</version></plugin></plugins></build></project>src/main/java/book/LineServer.java
package book;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
public final class LineServer {
public static void main(String[] args) throws InterruptedException {
int port = args.length == 0 ? 8080 : Integer.parseInt(args[0]);
EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup worker = new NioEventLoopGroup();
try {
Channel server = new ServerBootstrap()
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(
new LineBasedFrameDecoder(1024),
new StringDecoder(CharsetUtil.UTF_8),
new StringEncoder(CharsetUtil.UTF_8),
new ReplyHandler());
}
}).bind("127.0.0.1", port).sync().channel();
System.out.println("LISTENING " + port);
server.closeFuture().sync();
} finally {
boss.shutdownGracefully().sync();
worker.shutdownGracefully().sync();
}
}
static final class ReplyHandler extends SimpleChannelInboundHandler<String> {
@Override protected void channelRead0(ChannelHandlerContext ctx, String msg) {
if ("quit".equals(msg)) {
ctx.writeAndFlush("再见\n").addListener(ChannelFutureListener.CLOSE);
} else {
ctx.writeAndFlush("收到:" + msg + "\n");
}
}
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
}src/main/java/book/LineClient.java
package book;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
public final class LineClient {
public static void main(String[] args) throws InterruptedException {
int port = args.length == 0 ? 8080 : Integer.parseInt(args[0]);
EventLoopGroup group = new NioEventLoopGroup(1);
try {
Channel ch = new Bootstrap().group(group).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override protected void initChannel(SocketChannel c) {
c.pipeline().addLast(new LineBasedFrameDecoder(2048),
new StringDecoder(CharsetUtil.UTF_8),
new StringEncoder(CharsetUtil.UTF_8),
new SimpleChannelInboundHandler<String>() {
@Override protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println(msg);
}
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace(); ctx.close();
}
});
}
}).connect("127.0.0.1", port).sync().channel();
ch.writeAndFlush("你好,Netty\nquit\n").sync();
ch.closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
}src/test/java/book/ProtocolTest.java
package book;
import io.netty.buffer.*;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.*;
import io.netty.handler.codec.string.*;
import io.netty.util.CharsetUtil;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class ProtocolTest {
private EmbeddedChannel channel() {
return new EmbeddedChannel(new LineBasedFrameDecoder(1024),
new StringDecoder(CharsetUtil.UTF_8), new StringEncoder(CharsetUtil.UTF_8),
new LineServer.ReplyHandler());
}
private String take(EmbeddedChannel ch) {
ByteBuf b = ch.readOutbound();
assertNotNull(b);
try { return b.toString(CharsetUtil.UTF_8); } finally { b.release(); }
}
@Test void splitAndCombinedMessages() {
EmbeddedChannel ch = channel();
try {
ch.writeInbound(Unpooled.copiedBuffer("he", CharsetUtil.UTF_8));
assertNull(ch.readOutbound());
ch.writeInbound(Unpooled.copiedBuffer("llo\nworld\n", CharsetUtil.UTF_8));
assertEquals("收到:hello\n", take(ch));
assertEquals("收到:world\n", take(ch));
} finally { ch.finishAndReleaseAll(); }
}
@Test void utf8MaySplitInsideCharacter() {
EmbeddedChannel ch = channel();
byte[] bytes = "你\n".getBytes(CharsetUtil.UTF_8);
try {
ch.writeInbound(Unpooled.wrappedBuffer(new byte[]{bytes[0]}));
assertNull(ch.readOutbound());
ch.writeInbound(Unpooled.wrappedBuffer(java.util.Arrays.copyOfRange(bytes,1,bytes.length)));
assertEquals("收到:你\n",take(ch));
} finally { ch.finishAndReleaseAll(); }
}
@Test void quitFlushesThenCloses() {
EmbeddedChannel ch = channel();
try {
ch.writeInbound(Unpooled.copiedBuffer("quit\n", CharsetUtil.UTF_8));
assertEquals("再见\n",take(ch));
assertFalse(ch.isOpen());
} finally { ch.finishAndReleaseAll(); }
}
@Test void oversizedLineRejected() {
EmbeddedChannel ch = new EmbeddedChannel(new LineBasedFrameDecoder(8, true, true));
try {
assertThrows(TooLongFrameException.class, () -> ch.writeInbound(
Unpooled.copiedBuffer("123456789",CharsetUtil.UTF_8)));
} finally { ch.finishAndReleaseAll(); }
}
@Test void lengthFieldSurvivesFragmentation() {
EmbeddedChannel ch = new EmbeddedChannel(new LengthFieldBasedFrameDecoder(1024,0,4,0,4));
try {
ByteBuf first = Unpooled.buffer().writeInt(3).writeByte(1);
assertFalse(ch.writeInbound(first));
assertTrue(ch.writeInbound(Unpooled.wrappedBuffer(new byte[]{2,3})));
ByteBuf result = ch.readInbound();
try { assertEquals(3,result.readableBytes());assertEquals(1,result.readByte());assertEquals(2,result.readByte());assertEquals(3,result.readByte()); }
finally { result.release(); }
} finally { ch.finishAndReleaseAll(); }
}
}