概念
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();
}
}