教学视频地址:B站尚硅谷netty教学视频
demo代码:Gitee链接
文章目录
一、IO模型
1、BIO
- 代码:
public class BIOServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(6666);
ExecutorService threadPool = Executors.newCachedThreadPool();
while (true) {
final Socket socket = serverSocket.accept();
System.out.println("成功连接");
threadPool.execute(new Runnable() {
@Override
public void run() {
byte[] bytes = new byte[1024];
try (InputStream inputStream = socket.getInputStream()) {
while (true) {
int read = inputStream.read(bytes);
if (read != -1) {
System.out.println(new String(bytes,0,read));
}else {
break;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
;
}
});
}
}
}
需要安装telnet,在命令行窗口使用命令telnet 127.0.0.1 6666进入连接,trl+]进入命令操作模式,
send “发送内容”即可向客户端发送信息
2、NIO
buffer
- 实例代码:
/**
* buffer的基本使用
*/
public class BasicBuffer {
public static void main(String[] args) {
//声明buffer
IntBuffer intBuffer = IntBuffer.allocate(10);
//写入
for (int i = 0; i < intBuffer.capacity(); i++) {
intBuffer.put(i);
}
//读写转换:读写的下标使用的是不同的变量,需要手动切换
intBuffer.flip();
//读取
for (int i = 0; i < intBuffer.capacity(); i++) {
System.out.println(intBuffer.get(i));
}
}
}
/**
* 只读buffer
*/
public static void readOnlyBuffer() {
ByteBuffer buffer = ByteBuffer.allocate(50);
for (int i = 0; i < 10; i++) {
buffer.put((byte) i);
}
ByteBuffer asReadOnlyBuffer = buffer.asReadOnlyBuffer();
for (int i = 0; i < 10; i++) {
System.out.println(asReadOnlyBuffer.get());
}
//不可写入,会报错
asReadOnlyBuffer.put((byte) 12);
}
chanel
输入信息
输入信息
文件拷贝
/**
* 内存映射channel,不用将数据复制到cache
*/
public static void mappered() throws FileNotFoundException {
//randomAccessFile有多种模式同时可读可写
try (RandomAccessFile randomAccessFile = new RandomAccessFile(".\\file.txt","rw")) {
FileChannel channel = randomAccessFile.getChannel();
//映射的模式,开始位置,空间大小
MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
map.put(0, (byte) 'H');
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 文件的输入输出和复制
* @throws IOException
*/
private static void fileInputOutputCopy() throws IOException {
//写入文件
//信息
String message = "hello word";
//输出流
FileOutputStream fileOutputStream = new FileOutputStream(new File(".\\file.txt"));
//获取chanel
FileChannel channel = fileOutputStream.getChannel();
//声明buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//将信息写入buffer
byteBuffer.put(message.getBytes("UTF-8"));
//读写转换
byteBuffer.flip();
//读取buffer里的信息,写入chanel
int read = channel.write(byteBuffer);
//读取文件
File file = new File(".\\file.txt");
FileInputStream fileInputStream = new FileInputStream(file);
FileChannel channel1 = fileInputStream.getChannel();
ByteBuffer byteBuffer1 = ByteBuffer.allocate((int) file.length());
//读取channel里的信息,写入buffer
channel1.read(byteBuffer1);
System.out.println(new String(byteBuffer1.array()));
//文件复制
byteBuffer.clear();
File file1 = new File(".\\file2.txt");
FileOutputStream fileOutputStream1 = new FileOutputStream(file1);
FileChannel channel2 = fileOutputStream1.getChannel();
while (true) {
//读取channel里的信息,写入buffer
int read1 = channel1.read(byteBuffer);
byteBuffer.flip();
//判断channel是否已经没有信息了
if (read1 == -1) {
break;
}
//读取buffer里的信息,写入channel
channel2.write(byteBuffer);
//再次翻转,以便下次的读取
byteBuffer.flip();
}
//关闭流
fileOutputStream.close();
fileInputStream.close();
fileOutputStream1.close();
}
/**
* API文件复制
* @throws IOException
*/
public static void APICopy() throws IOException {
FileInputStream fileInputStream = new FileInputStream(".\\dog.jpg");
FileOutputStream fileOutputStream = new FileOutputStream(".\\dogCopy.jpg");
FileChannel inputStreamChannel = fileInputStream.getChannel();
FileChannel outputStreamChannel = fileOutputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
//用输出通道调用复制API
outputStreamChannel.transferFrom(inputStreamChannel,0,inputStreamChannel.size());
//关闭通道
inputStreamChannel.close();
outputStreamChannel.close();
fileInputStream.close();
fileOutputStream.close();
}
类型化读取
ByteBuffer asReadOnlyBuffer = buffer.asReadOnlyBuffer();
/**
* 内存映射channel,不用将数据复制到cache
*/
public static void mappered() throws FileNotFoundException {
//randomAccessFile有多种模式同时可读可写
try (RandomAccessFile randomAccessFile = new RandomAccessFile(".\\file.txt","rw")) {
FileChannel channel = randomAccessFile.getChannel();
MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
map.put(0, (byte) 'H');
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 多buffer
*/
public static void scatterAndGatherBuffer() throws IOException {
//使用网络IO
//创建channel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//创建端口
InetSocketAddress inetSocketAddress = new InetSocketAddress(6666);
//绑定
serverSocketChannel.bind(inetSocketAddress);
//启动
serverSocketChannel.socket();
//声明buffer数组
ByteBuffer[] byteBuffers = new ByteBuffer[2];
byteBuffers[0] = ByteBuffer.allocate(5);
byteBuffers[1] = ByteBuffer.allocate(1);
//等待客户连接
SocketChannel socketChannel = serverSocketChannel.accept();
int messageLength = 6;
int byRead = 0;
while (true) {
byRead = 0;
while (byRead<messageLength) {
socketChannel.read(byteBuffers);
byRead++;
//输出buffer读取后内存情况
Arrays.stream(byteBuffers).map(buffer->{
return "position:"+buffer.position()+" limite:"+buffer.limit();
}).forEach(System.out::println);
}
//读写转换
Arrays.asList(byteBuffers).forEach(buffer->buffer.flip());
//读取
int byWrite = 0;
while (byWrite < messageLength) {
long write = socketChannel.write(byteBuffers);
byWrite+=write;
}
Arrays.asList(byteBuffers).forEach(buffer->buffer.clear());
//输出读取的长度,输出的长度,最大的长度
System.out.println("readNum:"+byRead+" WriteNum:"+byWrite+" maxNum:"+messageLength);
}
}
运行结果:会先写入第一个buffer,写满后再写第二个
selector及nio的其他组件
- 简介
一个selector就可以管理多个channel,需要io时向selector发起调用,系统准备好之后,有数据了selector才会返回一个seletorKey通知线程,阻塞线程进行数据复制。
其中selectorKey是保存在selector的一个集合,用来声明哪一个channel发生了什么事件的
调用selector是阻塞的,但是也可以设置等待中断时间,手动唤醒,立马返回(不阻塞)等方法
-
selectionKey
-
serverSocketChannel
-
socketChannel
-
案例:实现客户端与服务器之间的非阻塞通讯
服务端代码:
public class NIOServer {
public static void main(String[] args) throws IOException {
/**
* bio中使用serverSocket进行端口号设置和监听,真正的通讯需要用socket
* nio中使用serverSocketChannel进行端口号的设置和监听,真正通讯需要用socketChannel(都是多了个channel的后缀)
*/
//创建一个serverSocketChannel,并设置端口号,非阻塞模式
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
serverSocketChannel.configureBlocking(false);
//创建一个selector
Selector selector = Selector.open();
//将serverSocketChannel绑定到selector,第二个参数需要设置关心的时间
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//进行循环监听
while (true) {
//调用selector的监听方法,这里使用超时中断
if (selector.select(1000) == 0) {
System.out.println("服务器等待了一秒,没有事件");
continue;
}
//select调用返回大于零,说明有连接事件,直接获取所有selectionKey集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//遍历所有的key
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
//从iterrantor获取key
SelectionKey key = iterator.next();
//判断不同的时间类型
if (key.isAcceptable()) {
System.out.println("收到连接事件,正在生成socketChannel通讯通道。。。");
//连接事件 OP_ACCEPT,生成一个socketChannel进行通讯
SocketChannel socketChannel = serverSocketChannel.accept();
//先设置非阻塞模式,再将socketChannel注册到selector,还需要设置关注事件 、 buffer
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println("客户端连接成功,socketChannel:"+socketChannel.hashCode());
}
if (key.isReadable()) {
System.out.println("收到读取事件,正在通过selectionKey获取对应的socketChannel通讯通道。。。");
//读取事件 OP_READ
//通过key获取channel
SocketChannel channel = (SocketChannel)key.channel();
//通过key获取buffer
ByteBuffer buffer = (ByteBuffer)key.attachment();
//读取操作
channel.read(buffer);
System.out.println("一则来自客户端的消息:"+new String(buffer.array()));
}
//手动删除key,防止重复操作(必须有调用了next,才可以是用remove,不然会快速失败)
iterator.remove();
}
}
}
}
客户端代码:
public class NioClient {
public static void main(String[] args) throws IOException {
//直接声明socketChannel,并设置非阻塞模式
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
//声明地址和端口号
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",6666);
//开始连接
if (!socketChannel.connect(inetSocketAddress)) {
//未连接成功
while (!socketChannel.finishConnect()) {
System.out.println("因为连接需要时间,客户端不会阻塞,可以做其他工作......");
}
}
//连接成功,发送数据(wrap会根据发送内容自动自动申请buffer大小)
ByteBuffer buffer = ByteBuffer.wrap("hello nio!".getBytes());
socketChannel.write(buffer);
//阻塞主线程
System.in.read();
}
}
nio实战:nio群聊系统
- 需求
- 代码:
package com.example.netty.nio.groupChat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class GroupchatServer {
private ServerSocketChannel serverSocketChannel;
private Selector selector;
private static final int PORT = 6667;
public GroupchatServer() throws IOException {
//通道设置
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
//selector设置
selector = Selector.open();
//将通道注册到selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
public void listen() throws IOException {
//循环监听
while (true) {
int selectResult = selector.select(10000);
if (selectResult > 0) {
System.out.println("\n监听到事件:");
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
System.out.println("链接事件,正在创建socketChannel。。。。");
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ,ByteBuffer.allocate(1024));
System.out.println("创建成功,已注册到selector中,"+
socketChannel.getRemoteAddress()+"已上线......");
}
if (key.isReadable()) {
System.out.println("读事件,正在获取socketChannel。。。。");
readData(key);
}
keyIterator.remove();
}
}
}
}
private void readData(SelectionKey key) throws IOException {
SocketChannel socketChannel = null;
try {
//接收数据
socketChannel = (SocketChannel)key.channel();
// ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
ByteBuffer byteBuffer = (ByteBuffer)key.attachment();//在注册的时候如果没有绑定buffer的话,会报错
socketChannel.read(byteBuffer);
String message = new String(byteBuffer.array());
System.out.println("已成功接收客户端的消息:"+message.trim());
//将消息进行转发(要排除自己)
for (SelectionKey selectionKey : selector.keys()) {
//不可以在这里直接强转,会报错
SelectableChannel channel = selectionKey.channel();
if (channel instanceof SocketChannel && channel != socketChannel) {
SocketChannel destSocketChannel = (SocketChannel) channel;
destSocketChannel.write(ByteBuffer.wrap(message.getBytes()));
}
}
} catch (IOException e) {
//读取过程中发生异常,发送方已经断开连接即下线
// e.printStackTrace();
System.out.println(socketChannel.getRemoteAddress()+"已下线。。。。");
//不再监听这个key,关闭通道
key.cancel();
socketChannel.close();
}
}
public static void main(String[] args) throws IOException {
GroupchatServer groupchatServer = new GroupchatServer();
groupchatServer.listen();
}
}
package com.example.netty.nio.groupChat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.concurrent.Callable;
public class GroupChatClient {
private Selector selector;
private SocketChannel socketChannel;
public static final String HOST = "127.0.0.1";
public static final int ADDRESS = 6667;
private String userName;
public GroupChatClient() throws IOException {
socketChannel = SocketChannel.open(new InetSocketAddress(HOST,ADDRESS));
socketChannel.configureBlocking(false);
selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_READ,ByteBuffer.allocate(1024));
userName = socketChannel.getLocalAddress().toString().substring(1);
}
public void sendMessage(String message) throws IOException {
String info = userName+" 说:"+message;
socketChannel.write(ByteBuffer.wrap(info.getBytes()));
}
public void receiveMessage() throws IOException {
int selectResult = selector.selectNow();
if (selectResult > 0) {
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel)key.channel();
// ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
ByteBuffer byteBuffer = (ByteBuffer)key.attachment();//在注册的时候如果没有绑定buffer的话,会报错
socketChannel.read(byteBuffer);
System.out.println(new String(byteBuffer.array()).trim());
}
keyIterator.remove();
}
}
}
public static void main(String[] args) throws Exception {
//需要两个线程,循环机那挺读和写
GroupChatClient groupChatClient = new GroupChatClient();
//新建一个线程循环读
new Thread(){
public void run() {
while (true) {
try {
groupChatClient.receiveMessage();
Thread.sleep(1000);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}.start();
//用主线程监听写
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String message = scanner.nextLine();
groupChatClient.sendMessage(message);
}
}
}
nio与零拷贝
- 基本概念:零次cpu拷贝
- 优化原理
DMA copy:直接内存拷贝,不使用cpu
- 总结
- 案例
package com.example.netty.nio.zeroCpy.java传统io;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class OldIOServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(7001);
while (true) {
Socket socket = serverSocket.accept();
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
//这里只是为了测试速度,所以数据只读到byte里就不再做进一步的处理
byte[] bytes = new byte[4096];
while (true) {
int readResult = dataInputStream.read(bytes, 0, bytes.length);
if (readResult == -1) {
break;
}
}
}
}
}
package com.example.netty.nio.zeroCpy.java传统io;
import java.io.*;
import java.net.Socket;
public class OldIOClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 7001);
FileInputStream fileInputStream = new FileInputStream(new File("./dog.jpg"));
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
byte[] bytes = new byte[4096];
long readCount = 0;
long total = 0;
long startTime = System.currentTimeMillis();
//读取文件,发送到server端
while ((readCount = fileInputStream.read(bytes)) > 0) {
total+=readCount;
dataOutputStream.write(bytes);
}
// 结果:发送总字结:392715,耗时:4
System.out.println("发送总字结:"+total+",耗时:"+(System.currentTimeMillis()-startTime));
dataOutputStream.close();
socket.close();
fileInputStream.close();
}
}
package com.example.netty.nio.zeroCpy;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class NewIOServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(7001));
ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
int readCount = 0;
while (readCount != -1) {
readCount = socketChannel.read(byteBuffer);
//重置position,使mark作废
byteBuffer.rewind();
}
}
}
}
package com.example.netty.nio.zeroCpy;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
public class NewIOClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",7001);
socketChannel.connect(inetSocketAddress);
//获取文件channel
FileChannel fileInputChannel = new FileInputStream("./dog.jpg").getChannel();
long startTime = System.currentTimeMillis();
//调用零拷贝api:windows调用一次最多只能穿8M,需要分批传
long transferCount = fileInputChannel.transferTo(0, fileInputChannel.size(), socketChannel);
//结果:总字结数:392715,耗时:16 3 2 2 2 3
System.out.println("总字结数:"+transferCount+",耗时:"+(System.currentTimeMillis()-startTime));
socketChannel.close();
fileInputChannel.close();
}
}
3、AIO
未广泛使用,不做介绍,这里只重点讲NIO
- 三种IO模型的对比
二、Netty入门
1、Netty简介
- 概述
2、线程模型
- 传统阻塞IO模型
- Reacor模型总览:
- Reactor单线程模型:
之前写的NIO群聊系统就是这种模式
- Reactor多线程模式
- 主从Reactor模式
- Netty模型
简单版
进阶版
详细版
3、入门案例-TCP服务
- 代码:
package com.example.netty.netty.simpleExample;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
public static void main(String[] args) throws Exception{
//创建Group
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建一个服务器对象,用于设置参数
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup) //设置两个group
.channel(NioServerSocketChannel.class) //设置服务器bossGroup使用的通道
.option(ChannelOption.SO_BACKLOG, 128) //设置bossGroup线程队列最大连接个数
.childOption(ChannelOption.SO_KEEPALIVE, true) //设置workerGroup是否保持活动连接状态
//设置workerGroup通道初始化对象(匿名对象)
.childHandler(new ChannelInitializer<SocketChannel>() { //通道类型,netty包中的
//给pipline中设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(6668).sync();
System.out.println("服务器启动成功。。。。。");
//监听对通道的关闭
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
//出现异常后优雅关闭group
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
package com.example.netty.netty.simpleExample;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
//需要继承一个HandlerAdapter(有很多个)
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("上下文信息:"+ctx);
//这是netty包的,不是NIO的bytebuffer,进行了二次分装性能要高一些
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("接收到信息:"+byteBuf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址为:"+ctx.channel().remoteAddress());
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//读取完成后触发,一般用于数据返回
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,client...",CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//发生异常时触发,一般要关闭通道
ctx.close();
}
}
package com.example.netty.netty.simpleExample;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class NettyClient {
public static void main(String[] args) throws Exception {
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
//与server端的不一样
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventExecutors)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyClientHandler());
}
});
ChannelFuture sync = bootstrap.connect("127.0.0.1", 6668).sync();
System.out.println("客户端启动成功。。。。");
sync.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
eventExecutors.shutdownGracefully();
}
}
}
package com.example.netty.netty.simpleExample;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//通道准备好时触发
ctx.channel().writeAndFlush(Unpooled.copiedBuffer("hello,server...", CharsetUtil.UTF_8));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//有读取事件时触发
System.out.println("上下文信息:"+ctx);
ByteBuf buf = (ByteBuf) msg;
System.out.println("接受到信息:"+buf.toString(CharsetUtil.UTF_8));
System.out.println("服务器地址:"+ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
4、任务队列
- 简介
如果有非常耗时的逻辑,又不想阻塞当前线程可以通过ctx获取管道,再获取NioEventLoop提交taskQueue,用另一个线程来异步执行
taskQueue里面只有一个线程,会按顺序执行提交的任务
普通代码实现:
定时任务代码实现:
5、异步模型
不会阻塞主线程
代码:
6、入门案例-HTTP服务
-
需求
-
组件
只能处理一个客户端请求只针对bossGroup中只有一个nioEventLoop而言的
-
代码
package com.example.netty.netty.HTTPExample;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import java.net.URI;
//通讯数据会被封装成 httpObject
public class HTTPServerHandler extends SimpleChannelInboundHandler<HttpObject> {
//有读取事件时触发
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject) throws Exception {
if (httpObject instanceof HttpRequest) {
//在同一个bossGroup中每个浏览器对应一个workerGroup,但如果有多个bossGroup进行轮询就不会有对应关系
System.out.println("handler hashcode:"+this.hashCode()+
",pipLine hashCode:"+channelHandlerContext.channel().pipeline().hashCode());
System.out.println("消息类型:"+httpObject.getClass());
System.out.println("clientAddress:"+channelHandlerContext.channel().remoteAddress());
//过滤特定请求路径
HttpRequest httpRequest = (HttpRequest) httpObject;
URI uri = new URI(httpRequest.uri());
if (uri.getPath().equals("/favicon.ico")) {
System.out.println("请求favicon.ico,不做响应");
return;
}
//回复消息
ByteBuf buf = Unpooled.copiedBuffer("hello,httpClient....", CharsetUtil.UTF_8);
//构造一个http响应
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,buf);
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain;charset=utf-8")
.set(HttpHeaderNames.CONTENT_LENGTH,buf.readableBytes());
channelHandlerContext.writeAndFlush(response);
}
}
}
public class ServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//netty提供的http编码解码处理器
socketChannel.pipeline().addLast(new HttpServerCodec())
.addLast("myHTTPServerHandler",new HTTPServerHandler());
}
}
7、Unpooled组件
- 简介
不需要进行flip反转,底层维护了两个index,将buf空间分成了三个部分:
0-readerIndex:已读区
readerIndex-writerIndex:可读区
writerIndex-capacity:可写区 - 代码
package com.example.netty.netty.buf;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
public class fubExample {
public static void main(String[] args) {
ByteBuf buffer = Unpooled.buffer(10);
for (int i = 0; i < buffer.capacity(); i++) {
buffer.writeByte(i);
}
for (int i = 0; i < buffer.capacity(); i++) {
System.out.print(buffer.readByte()+" ");
}
System.out.println();
ByteBuf buf = Unpooled.copiedBuffer("hello,北京", CharsetUtil.UTF_8);
byte[] array = buf.array();
buf.writeByte('w');
System.out.println((char) buf.readByte());
System.out.println(buf.getCharSequence(1, 3, CharsetUtil.UTF_8));
buf.writeBytes(new byte[]{'a','d','s'});
System.out.println(new String(array,CharsetUtil.UTF_8).trim());
}
}
8、Netty群聊系统
- 需求
- 代码
package com.example.netty.netty.groupChat;
import com.example.netty.nio.groupChat.GroupChatClient;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
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;
public class GroupChatServer {
private int port;
public GroupChatServer(int port) {
this.port = port;
}
public void run() throws InterruptedException {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//加入string的编码解码器
pipeline.addLast("stringEncoder", new StringEncoder())
.addLast("stringDecoder", new StringDecoder());
pipeline.addLast(new ServerHandler());
}
});
ChannelFuture sync = serverBootstrap.bind(port).sync();
sync.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new GroupChatServer(7000).run();
}
}
package com.example.netty.netty.groupChat;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.HttpObject;
import io.netty.util.concurrent.GlobalEventExecutor;
import javax.lang.model.element.VariableElement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
public class ServerHandler extends SimpleChannelInboundHandler<String> {
//相当于channel列表,来用管理所有的channel
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//与handler建立连接时触发
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//将该通道加入管理列表
channelGroup.add(ctx.channel());
//通知信息,发送给所有人
channelGroup.writeAndFlush(dateFormat.format(new Date())+"-客户端:"
+ctx.channel().remoteAddress()+" 加入聊天\n");
}
//断开连接时触发
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
channelGroup.writeAndFlush(dateFormat.format(new Date())+"-客户端:"
+ctx.channel().remoteAddress()+" 离开了\n");
}
// TODO: 2022/9/12 与上面两个方法的区别?
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(dateFormat.format(new Date())+ctx.channel().remoteAddress()+" 上线了");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println(dateFormat.format(new Date())+ctx.channel().remoteAddress()+" 下线了");
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
Channel channel = channelHandlerContext.channel();
channelGroup.forEach(ch->{
if (ch != channel) {
ch.writeAndFlush(dateFormat.format(new Date())+"-客户端:" + channel.remoteAddress() + " 说:" + s + "\n");
} else {
//回显
ch.writeAndFlush(dateFormat.format(new Date())+"-自己说:"+s+"\n");
}
});
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// cause.printStackTrace();
ctx.close();
}
}
package com.example.netty.netty.groupChat;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.util.Scanner;
public class GroupChatClient {
private final String host;
private final int port;
public GroupChatClient(String host, int port) {
this.host = host;
this.port = port;
}
public void run() throws InterruptedException {
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventExecutors)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//加入string的编码解码器
pipeline.addLast("stringEncoder", new StringEncoder())
.addLast("stringDecoder", new StringDecoder());
pipeline.addLast(new ClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
//发消息
Channel channel = channelFuture.channel();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
channel.writeAndFlush(scanner.nextLine()+"\r\n");
}
channelFuture.channel().closeFuture().sync();
} finally {
eventExecutors.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new GroupChatClient("127.0.0.1",7000).run();
}
}
package com.example.netty.netty.groupChat;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class ClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
System.out.println(s);
}
}
- 私聊系统
大部分一样,下面列出不同代码:
9、心跳机制
出现空闲直接关闭连接
客户端发送消息前先检验连接是否已经断开,若断开需要重连后再发送
10、WebSocket长连接开发
要求实现全双工长连接通讯
核心代码:
server:
handler:
client:
<script>
var socket;
//判断当前浏览器是否支持websocket
if (window.WebSocket) {
socket = new WebSocket("ws://localhost:7000/hello");
socket.onmessage = function (ev) {
var rt = document.getElementById("responseText");
rt.value = rt.value + "\n" + ev.data;
};
socket.onopen = function (ev) {
var rt = document.getElementById("responseText");
rt.value = "连接开启了。。"
};
socket.onclose = function (ev) {
var rt = document.getElementById("responseText");
rt.value = rt.value + "\n" + "连接关闭了。。"
};
} else {
alert("当前流浪器不支持websocket")
}
function send(message) {
if (!window.socket) {
return;
}
if (socket.readyState == WebSocket.OPEN) {
socket.send(message);
} else {
alert("连接没有开启");
}
}
</script>
<form onsubmit="return false">
<textarea name="message" style="height: 300px;width: 300px"></textarea>
<input type="button" value="发送消息" onclick="send(this.form.message.value)">
<textarea id="responseText" style="height: 300px;width: 300px"></textarea>
<input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
</form>
11、编码解码机制
- 编码解码基本概念
- netty的编码解码
- ProtoBuf
- ProtoBuf案例1
1、引入依赖
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.21.5</version>
</dependency>
</dependencies>
2、编写proto文件
syntax = "proto3"; //版本
option java_outer_classname = "StudentPOJO"; //生成的外部类名,同时也是文件名
//protobuf使用message管理数据
message Student{ //会在StudentPOJO外部生成一个内部类,他是真正发送的POJO对象
int32 id = 1; //int32对应java中的int,1代表序号,不是值
string name = 2;
}
3、下载protoc.exe(要与依赖版本对应),与proto文件放在同一目录下,打开命令行,执行一下命令即可生成对象文件
注意.后面有个空格
protoc.exe --java_out=. Student.proto
若生成的文件报错,是因为编译器修改了jdk版本,改回来就好了
4、编写传输代码:
加入编码解码器,这里只加一个,单向
client:
clientHandler:
server:
serverHandler:
- ProtoBuf案例2-传输多种类型
proto文件:
syntax = "proto3";
option optimize_for = SPEED; //加快解析
option java_package = "com.example.netty.netty.codec2"; //生成的文件所在的包
option java_outer_classname = "MyDataInfo"; //外部类名称
message MyMessage{
//定义一个枚举类
enum DataType{
StudentType = 0; //proto3枚举类下标要求从0开始
WorkerType = 1;
}
//生命一个变量,用来标识是哪一个枚举类
DataType data_type = 1;
//声明正真的数据对象,用oneof括起来可以只选其中一个已节省空间
oneof dataBody{
Student student = 2;
Worker worder = 3;
}
}
message Student{
int32 id = 1;
string name = 2;
}
message Worker{
string name = 1;
int32 age = 2;
}
传输关键代码:
加入编码解码器(这里每一端只加一个)
client
server
clientHandler:
serverHandler:
12、Handler调用链
- 图解
- 案例
- 其他编解码器
13、Log4j整合到Netty
1、导入依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
2、在resource中新建log4j.properties写入以下内容
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%p] %c{1} - %m%n
3、使用
14、TCP粘包拆包问题
- 简介
- 解决方案
协议-消息体:
编码解码:
三、Netty源码解读
1、启动过程
2、接受请求的过程
源码部分先挑过
四、利用Netty实现RPC框架
1、流程分析
2、代码
在gitee中,这里就不贴了