概念
Zero Copy
- 0拷贝,速度快
- 操作数据时, 不需要将数据 buffer 从一个内存区域拷贝到另一个内存区域. 因为少了一次内存的拷贝, 因此 CPU 的效率就得到的提升.
- 在 OS 层面上的 Zero-copy 通常指避免在 用户态(User-space) 与 内核态(Kernel-space) 之间来回拷贝数据. 例如 Linux 提供的 mmap 系统调用, 它可以将一段用户空间内存映射到内核空间, 当映射成功后, 用户对这段内存区域的修改可以直接反映到内核空间; 同样地, 内核空间对这段区域的修改也直接反映用户空间. 正因为有这样的映射关系, 我们就不需要在 用户态(User-space) 与 内核态(Kernel-space) 之间拷贝数据, 提高了数据传输的效率.
而需要注意的是, Netty 中的 Zero-copy 与上面我们所提到到 OS 层面上的 Zero-copy 不太一样, Netty的 Zero-coyp 完全是在用户态(Java 层面)的, 它的 Zero-copy 的更多的是偏向于 优化数据操作 这样的概念.
实操
代码
服务器
新建Maven项目:
添加插件:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<!-- // 可以添加对应的执行阶段-->
<phase>test</phase>
<!-- ...-->
<goals>
<!-- // 指定来的 goal为java,表示运行java程序-->
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- // 指定了运行的main class-->
<mainClass>com.cc.netty.server.EchoServer</mainClass>
<!-- // 执行运行 main class的参数-->
<!-- // 其实就是传入main方法的String[]-->
<arguments>
<argument>argument1</argument>
<!-- ...-->
</arguments>
<!-- // 运行java的程序的系统参数-->
<systemProperties>
<systemProperty>
<key>myproperty</key>
<value>myvalue</value>
</systemProperty>
<!-- ...-->
</systemProperties>
</configuration>
</plugin>
执行mvn exec:java
:
报错:
说明插件里的参数有问题。
- 不好用,用下面的简单代码:
- 服务端:
package com.cc.netty.test;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import java.util.Scanner;
/**
* netty服务端
* NioEventLoopGroup是一个处理I/O操作的多线程事件循环。
* Netty为不同类型的传输提供了各种EventLoopGroup实现。
* 我们在这个例子中实现了一个服务器端应用程序,
* 因此将使用两个NioEventLoopGroup。第一个通常被称为“boss”,接受传入的连接。第二个通常称为“worker”,
* 在boss接受连接并将接受的连接注册到worker之后,处理接受连接的流量。使用多少线程以及如何将它们映射到创建的通道取决于EventLoopGroup实现,
* 甚至可以通过构造函数进行配置。
* <p>
* ServerBootstrap是一个设置服务器的助手类。您可以直接使用通道设置服务器。但是,请注意这是一个冗长的过程,在大多数情况下您不需要这样做。
* 在这里,我们指定使用NioServerSocketChannel类,该类用于实例化一个新通道以接受传入连接。
* <p>
* 这里指定的处理程序总是由新接受的通道计算。ChannelInitializer是用于帮助用户配置新通道的特殊处理程序。
* 您很可能希望通过添加一些处理程序(如DiscardServerHandler)来实现您的网络应用程序,来配置新通道的ChannelPipeline。
* 随着应用程序变得复杂,您可能会向管道中添加更多的处理程序,并最终将这个匿名类提取到顶级类中。
* <p>
* 您还可以设置特定于通道实现的参数。我们正在编写TCP/IP服务器,因此我们可以设置套接字选项,如tcpNoDelay和keepAlive。
* 请参阅ChannelOption的apidocs和特定的ChannelConfig实现,以获得受支持的ChannelOptions的概述。
* <p>
* 你注意到option()和childOption()了吗?option()用于接收传入连接的NioServerSocketChannel。
* childOption()用于父服务器通道接受的通道,在本例中是NioServerSocketChannel。
* 我们现在准备好出发了。剩下的就是绑定到端口并启动服务器。在这里,我们绑定到机器中所有NICs(网络接口卡)的端口8080。
* 现在,您可以任意次数地调用bind()方法(使用不同的绑定地址)。
* <p>
* telnet 可以测试服务器是否工作
* telnet localhost 8080
*/
public class NettyServer {
public static void main(String[] args) throws Exception {
new NettyServer().run(8666);
}
//新建一个netty服务器
public void run(int port) throws Exception {
//NioEventLoopGroup是一个处理I/O操作的多线程循环
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
System.out.println("准备运行端口:" + port);
try {
//服务器的设置助手类
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap = serverBootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
//这里指定的处理程序总是由新接受的通道计算。
.childHandler(new ChildChannelHandler());
//绑定端口,同步等待成功
ChannelFuture future = serverBootstrap.bind(port).sync();
//等待服务监听端口关闭
future.channel().closeFuture().sync();
} finally {
//退出,释放线程资源
workGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
//ChannelInitializer是用于帮助用户配置新通道的特殊处理程序
class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
//请求到达后调用
protected void initChannel(SocketChannel socketChannel) throws Exception {
// ByteBuf byteBuf= Unpooled.copiedBuffer("$".getBytes());
// socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,byteBuf));
socketChannel.pipeline().addLast(new StringDecoder());//进行字符串的编解码设置
socketChannel.pipeline().addLast(new StringEncoder());
socketChannel.pipeline().addLast(new ReadTimeoutHandler(60));//设置超时时间
socketChannel.pipeline().addLast(new DiscardServerHandler());
}
}
/**
* 服务器类型设置
**/
class DiscardServerHandler extends ChannelHandlerAdapter {
@Override
//只要接收到数据,就会调用channelRead()方法
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
//ByteBuf是一个引用计数的对象,必须通过release()方法显式地释放它
System.out.println(String.format("%s === %s","收到信息",msg));
// ByteBuf in = (ByteBuf) msg;
System.out.println("传输内容是");
// System.out.println(in.toString(CharsetUtil.UTF_8));
//返回信息
ByteBuf resp = Unpooled.copiedBuffer("服务端收到信息,ack$".getBytes());
ctx.writeAndFlush(resp);
// Scanner scanner = Scanner.
} finally {
System.out.println(msg);
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 出现异常就关闭
cause.printStackTrace();
ctx.close();
}
}
- 客户端:
package com.cc.netty.test;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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.DelimiterBasedFrameDecoder;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
/**
* netty 编写的客户端
*/
public class TimeClient {
public static void main(String[] args) throws Exception {
new TimeClient().connect(8666, "localhost");
}
public void connect(int port, String host) throws Exception {
//配置客户端
System.out.println("连接=>" + host + ":" + port);
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ByteBuf byteBuf = Unpooled.copiedBuffer("$".getBytes());
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, byteBuf));
socketChannel.pipeline().addLast(new TimeClientHandler());
}
});
//绑定端口,同步等待成功
ChannelFuture future = bootstrap.connect(host, port).sync();
//等待服务监听端口关闭
future.channel().closeFuture().sync();
} finally {
//优雅退出,释放线程资源
eventLoopGroup.shutdownGracefully();
}
}
}
class TimeClientHandler extends ChannelHandlerAdapter {
private byte[] req;
public TimeClientHandler() {
req = "中国必胜!武汉加油!".getBytes();
// req = "$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$".getBytes();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf message = null;
for (int i = 0; i < 5; i++) {
message = Unpooled.buffer(req.length);
message.writeBytes(req);
ctx.writeAndFlush(message);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
ByteBuf in = (ByteBuf) msg;
System.out.println(in.toString(CharsetUtil.UTF_8));
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 出现异常就关闭
cause.printStackTrace();
ctx.close();
}
}