网址:https://netty.io/wiki/related-projects.html
实例说明:
package com.wolfx.bio;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @description: bio服务器
* @author: sukang
* @date: 2020-06-11 15:41
*/
public class BIOServer {
public static void main(String[] args) throws Exception{
//线程池机制
//思路
//1.创建一个线程池
//2.如果有客户端连接,就创建一个线程,与之通讯(单独写一个方法)
ExecutorService executorService = Executors.newCachedThreadPool();
//创建ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
System.out.println("服务器启动了");
while (true){
System.out.println("线程信息id=" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());
//监听,等待客户端连接
System.out.println("等待连接...");
final Socket socket = serverSocket.accept();
System.out.println("连接到一个客户端");
//就创建一个线程,与之通讯(单独写一个方法)
executorService.execute(new Runnable() {
public void run() {//我们重写
//可以和客户端通讯
handler(socket);
}
});
}
}
//编写一个handler方法,和客户端通讯
public static void handler(Socket socket){
try {
System.out.println("线程信息id="+Thread.currentThread().getId()+"名字="+Thread.currentThread().getName());
byte[] bytes=new byte[1024];
//通过socket获取输入流
InputStream inputStream = socket.getInputStream();
//循环的读取客户端发送的数据
while (true) {
System.out.println("线程信息id="+Thread.currentThread().getId()+"名字="+Thread.currentThread().getName());
System.out.println("read....");
int read = inputStream.read(bytes);
if(read != -1){
System.out.println(new String(bytes,0,read));//输出客户端发送的数据
}else{
break;
}
}
} catch ( IOException e ) {
e.printStackTrace();
}finally {
System.out.println("关闭和client的连接");
try{
socket.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
package com.wolfx.nio;
import java.nio.IntBuffer;
/**
* @description:
* @author: sukang
* @date: 2020-06-11 16:34
*/
public class BasicBuffer {
public static void main(String[] args) {
//举例说明Buffer的使用(简单说明)
//创建一个Buffer,大小为5,即可以存放5个int
IntBuffer intBuffer= IntBuffer.allocate(5);
//向buffer存放数据
for(int i=0; i<intBuffer.capacity(); i++){
intBuffer.put(i*2);
}
//如何从buffer读取数据
//将buffer转换,读写切换(!!!)
intBuffer.flip();
while(intBuffer.hasRemaining()){
System.out.println(intBuffer.get());
}
}
}
一张图描述NIO的Selector、Channel和Buffer的关系
缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由Buffer,如图:【后面举例说明】
从前面可以看出对于Java中的基本数据类型(boolean除外),都有一个Buffer类型与之相对应,最常用的自然是ByteBuffer类(二进制数据),该类的主要方法如下:
通道可以同时进行读写,而流只能读或者只能写
通道可以实现异步读写数据
通道可以从缓冲读数据,也可以写数据到缓冲
实例要求:
package com.wolfx.nio;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @description:
* @author: sukang
* @date: 2020-06-12 10:33
*/
public class NIOFileChannel01 {
public static void main(String[] args) throws Exception{
String str = "hello,你好";
//创建一个输出流->channel
FileOutputStream fileOutputStream = new FileOutputStream("d:\\file01.txt");
//通过fileOutputStream获取对应的FileChannel
//这个fileChannel真实类型是FileChannelImpl
FileChannel fileChannel = fileOutputStream.getChannel();
//创建一个缓冲区ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//将str放入byteBuffer
byteBuffer.put(str.getBytes());
//对byteBuffer进行flip
byteBuffer.flip();
//将byteBuffer数据写入到fileChannel
fileChannel.write(byteBuffer);
fileOutputStream.close();
}
}
实例要求:
package com.wolfx.nio;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @description:
* @author: sukang
* @date: 2020-06-12 11:10
*/
public class NIOFileChannel02 {
public static void main(String[] args) throws Exception{
//创建文件的输入流
File file=new File("d:\\file01.txt");
FileInputStream fileInputStream = new FileInputStream(file);
//通过fileInputStream获取对应的FileChannel->实际类型 FileChannelImpl
FileChannel fileChannel = fileInputStream.getChannel();
//创建缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
//将通道的数据读入到缓冲区
fileChannel.read(byteBuffer);
//将byteBuffer的字节数据转成String
System.out.println(new String(byteBuffer.array()));
fileInputStream.close();
}
}
实例要求:
package com.wolfx.nio;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @description:
* @author: sukang
* @date: 2020-06-12 11:26
*/
public class NIOFileChannel03 {
public static void main(String[] args) throws Exception{
File file = new File("1.txt");
FileInputStream fileInputStream = new FileInputStream(file);
FileChannel channel = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
FileChannel channel1 = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
while (true){ //循环读取
//这里有个重要的操作,一定不要忘了
/**
* public final Buffer clear(){
* position=0;
* limit=capacity;
* mark=-1;
* return this;
* }
*/
byteBuffer.clear();//清空buffer
int read = channel.read(byteBuffer);
System.out.println("read=" + read);
if(read == -1){//表示读完
break;
}
//将buffer中的数据写入到channel1 -- 2.txt
byteBuffer.flip();
channel1.write(byteBuffer);
}
fileInputStream.close();
fileOutputStream.close();
}
}
package com.wolfx.nio;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
/**
* @description:
* @author: sukang
* @date: 2020-06-12 13:48
*/
public class NIOFileChannel04 {
public static void main(String[] args) throws Exception{
//创建相关流
FileInputStream fileInputStream = new FileInputStream("a.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("b.jpg");
//创建相关的通道
FileChannel source = fileInputStream.getChannel();
FileChannel target = fileOutputStream.getChannel();
//使用transferFrom完成拷贝
target.transferFrom(source,0, source.size());
source.close();
target.close();
fileInputStream.close();
fileInputStream.close();
}
}
package com.wolfx.nio;
import java.nio.ByteBuffer;
/**
* @description:
* @author: sukang
* @date: 2020-06-12 14:00
*/
public class NIOByteBufferPutGet {
public static void main(String[] args) {
//创建一个Buffer
ByteBuffer buffer = ByteBuffer.allocate(64);
//类型化方式放入数据
buffer.putInt(100);
buffer.putLong(9);
buffer.putChar('苏');
buffer.putShort((short) 4);
//取出
buffer.flip();
System.out.println();
System.out.println(buffer.getInt());
System.out.println(buffer.getLong());
System.out.println(buffer.getChar());
System.out.println(buffer.getShort());
}
}
package com.wolfx.nio;
import java.nio.ByteBuffer;
/**
* @description:
* @author: sukang
* @date: 2020-06-12 14:10
*/
public class ReadOnlyBuffer {
public static void main(String[] args) {
//创建一个buffer
ByteBuffer buffer = ByteBuffer.allocate(64);
for (int i = 0; i < 64; i++) {
buffer.put((byte) i);
}
//读取
buffer.flip();
//得到一个只读的Buffer
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
System.out.println(readOnlyBuffer.getClass());
//读取
while (readOnlyBuffer.hasRemaining()){
System.out.println(readOnlyBuffer.get());
}
readOnlyBuffer.put((byte) 100);
}
}
NIO非阻塞网络编程相关的(Selector、SelectionKey、ServerScoketChannel和SocketChannel)关系梳理图
对上图的说明:
案例要求:
package com.wolfx.nio;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
/**
* @description:
* @author: sukang
* @date: 2020-06-12 15:29
*/
public class NIOServer {
public static void main(String[] args) throws Exception{
//创建ServerSocketChannel->ServerSocket
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//得到一个Selecor对象
Selector selector = Selector.open();
//绑定一个端口6666,在服务器端监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
//把serverSocketChannel注册到selector关心事件为OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//循环等待客户端连接
while (true){
//这里我们等待1秒,如果没有事件发生,返回
if(selector.select(1000) == 0){//没有事件发生
System.out.println("服务器等待1秒,无连接");
continue;
}
//如果返回的>0,就获取到相关的selectionKey集合
//1.如果返回的>0,表示已经获取到关注的事件
//2.selector.selectedKeys()返回关注事件的集合
//通过selectionKeys反向获取通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//遍历Set,使用迭代器遍历
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()){
//获取到SelectionKey
SelectionKey key = keyIterator.next();
//根据key对应的通道发生的事件做相应处理
if(key.isAcceptable()){ //如果是OP_ACCEPT,有新的客户端连接
//给改客户端生成一个SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("客户端连接成功生成了一个socketChannel"+socketChannel.hashCode());
//将SocketChannel设置为非阻塞
socketChannel.configureBlocking(false);
//将socketChannel注册到selector,关注事件为OP_READ,同时给socketChannel
//关联一个Buffer
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(key.isReadable()){//发生OP_READ
//通过key反向获取到对应channel
SocketChannel channel = (SocketChannel) key.channel();
//获取到该channel关联的buffer
ByteBuffer buffer = (ByteBuffer)key.attachment();
channel.read(buffer);
System.out.println("from 客户端:" + new String(buffer.array()));
}
//手动从集合中移动当前的selectionKey,防止重复操作
keyIterator.remove();
}
}
}
}
NIOClient
package com.wolfx.nio;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @description:
* @author: sukang
* @date: 2020-06-12 16:08
*/
public class NIOClient {
public static void main(String[] args) throws Exception{
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞
socketChannel.configureBlocking(false);
//提供服务器端的ip和端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",6666);
//连接服务器
if(!socketChannel.connect(inetSocketAddress)){
while (!socketChannel.finishConnect()){
System.out.println("因为连接需要时间,客户端不会阻塞,可以做其他工作");
}
}
//...如果连接成功,就发送数据
String str="hello...";
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
//发送数据,将buffer数据写入channel
socketChannel.write(buffer);
System.in.read();
}
}
SelectionKey,表示Selector和网络通道的注册关系,共四种:
int OP_ACCEPT:有新的网络连接可以accept,值为16
int OP_CONNECT:代表连接已经建立,值为8
int OP_READ:代表读操作,值为1
int OP_WRITE:代表写操作,值为4
源码中:
public static final int OP_READ = 1<<0;
public static final int OP_WRITE = 1<<2;
public static final int OP_CONNECT = 1<<3;
public static final int OP_ACCEPT = 1<<4;
实例要求:
package com.wolfx.nio.groupchat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
/**
* @description:
* @author: sukang
* @date: 2020-06-12 16:39
*/
public class GroupChatServer {
//定义属性
private Selector selector;
private ServerSocketChannel listenChannel;
private static final int PORT = 6667;
//构造器
//初始化工作
public GroupChatServer() {
try {
//得到选择器
selector = Selector.open();
//ServerSocketChannel
listenChannel = ServerSocketChannel.open();
//绑定端口
listenChannel.socket().bind(new InetSocketAddress(PORT));
//设置非阻塞模式
listenChannel.configureBlocking(false);
//将该listenChannel注册到selector
listenChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch ( IOException e ) {
e.printStackTrace();
}
}
//监听
public void listen(){
try {
//循环处理
while(true){
int count = selector.select();
if(count > 0){ //有事件处理
//遍历得到selectionKey集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
//取出selectionkey
SelectionKey key = iterator.next();
//监听到accept
if(key.isAcceptable()){
SocketChannel sc = listenChannel.accept();
sc.configureBlocking(false);
//将该sc注册到seletor
sc.register(selector,SelectionKey.OP_READ);
//提示
System.out.println(sc.getRemoteAddress()+"上线");
}
if(key.isReadable()){//通道发送read事件,即通道是可读的状态
//处理读(专门写方法..)
readData(key);
}
//当前的key删除,防止重复处理
iterator.remove();
}
}else{
System.out.println("等待....");
}
}
} catch ( Exception e ) {
e.printStackTrace();
} finally {
//发生异常处理....
}
}
//读取客户端消息
private void readData(SelectionKey key){
//取到关联的channel
SocketChannel channel = null;
try {
//得到channel
channel = (SocketChannel)key.channel();
//创建buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = channel.read(buffer);
if(count > 0){
//把缓存区的数据转成字符串
String msg = new String(buffer.array());
//输出该消息
System.out.println("from 客户端:" + msg);
//向其它的客户端转发消息(去掉自己),专门写一个方法来处理
sendInfoToOtherClients(msg,channel);
}
} catch ( IOException e ) {
try {
System.out.println(channel.getRemoteAddress() + "离线了..");
//取消注册
key.cancel();
//关闭通道
channel.close();
} catch ( IOException ex ) {
ex.printStackTrace();
}
}
}
//转发消息给其他客户(通道)
private void sendInfoToOtherClients(String msg, SocketChannel self) throws IOException{
System.out.println("服务器转发消息中...");
//遍历 所有注册到selector上的SocketChannel,并排除self
for (SelectionKey key : selector.keys()) {
//通过key取出对应的SocketChannel
Channel targetChannel = key.channel();
//排除自己
if(targetChannel instanceof SocketChannel && targetChannel != self){
//转型
SocketChannel dest = (SocketChannel) targetChannel;
//将msg存储到buffer
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
//将buffer的数据写入通道
dest.write(buffer);
}
}
}
public static void main(String[] args) {
//创建服务器对象
GroupChatServer groupChatServer = new GroupChatServer();
groupChatServer.listen();
}
}
package com.wolfx.nio.groupchat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
/**
* @description:
* @author: sukang
* @date: 2020-06-22 16:05
*/
public class GroupChatClient {
//定义相关的属性
private final String HOST = "127.0.0.1";//服务器的ip
private final int PORT = 6667;//服务器端口
private Selector selector;
private SocketChannel socketChannel;
private String username;
//构造器,完成初始化工作
public GroupChatClient() throws IOException {
selector = Selector.open();
//连接服务器
socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",PORT));
//设置非阻塞
socketChannel.configureBlocking(false);
//将channel注册到selector
socketChannel.register(selector, SelectionKey.OP_READ);
//得到username
username = socketChannel.getLocalAddress().toString().substring(1);
System.out.println(username + "is ok");
}
//向服务器发送消息
public void sendInfo(String info){
info = username + "说: " + info;
try {
socketChannel.write(ByteBuffer.wrap(info.getBytes()));
} catch ( IOException e ) {
e.printStackTrace();
}
}
//读取从服务器端回复的消息
public void readInfo(){
try {
int readChannels = selector.select();
if(readChannels > 0){//有可用的通道
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
if(key.isReadable()){
//得到相关的通道
SocketChannel sc = (SocketChannel)key.channel();
//得到一个Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取
sc.read(buffer);
//把读到的缓冲区的数据转成字符串
String msg=new String(buffer.array());
System.out.println(msg.trim());
}
}
iterator.remove();//删除当前的selectionKey,防止重复操作
}else {
//System.out.println("没有可以用的通道...");
}
} catch ( IOException e ) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception{
//启动我们客户端
final GroupChatClient chatClient=new GroupChatClient();
//启动一个线程,每个3秒,读取从服务器发送数据
new Thread(){
@Override
public void run(){
while (true){
chatClient.readInfo();
try {
sleep(3000);
} catch ( InterruptedException e ) {
e.printStackTrace();
}
}
}
}.start();
//发送数据给服务器端
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String s = scanner.nextLine();
chatClient.sendInfo(s);
}
}
}
初学 Java 时,我们在学习 IO 和 网络编程时,会使用以下代码:
File file = new File("index.html");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
byte[] arr = new byte[(int) file.length()];
raf.read(arr);
Socket socket = new ServerSocket(8080).accept();
socket.getOutputStream().write(arr);
我们会调用read方法读取index.html的内容—变成字节数组,然后调用write方法,将index.html字节流写到socket中,那么,我们调用者两个方法,在OS底层发生了什么呢?我这里借鉴了一张其他文字的图片,尝试解释这个过程
上图中,上半部分表示用户态和内核态的上下文切换。下半部分表示数据复制操作。下面说说他们的步骤:
mmap 优化
mmap 通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户控件的拷贝次数。如下图:
如上图,user buffer 和 kernel buffer 共享 index.html。如果你想把硬盘的 index.html 传输到网络中,再也不用拷贝到用户空间,再从用户空间拷贝到 Socket 缓冲区。
现在,你只需要从内核缓冲区拷贝到 Socket 缓冲区即可,这将减少一次内存拷贝(从 4 次变成了 3 次),但不减少上下文切换次数。
sendFile
那么,我们还能继续优化吗? Linux 2.1 版本 提供了 sendFile 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到 Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文切换。
如上图,我们进行 sendFile 系统调用时,数据被 DMA 引擎从文件复制到内核缓冲区,然后调用,然后掉一共 write 方法时,从内核缓冲区进入到 Socket,这时,是没有上下文切换的,因为在一个用户空间。
最后,数据从 Socket 缓冲区进入到协议栈。
此时,数据经过了 3 次拷贝,3 次上下文切换。
那么,还能不能再继续优化呢? 例如直接从内核缓冲区拷贝到网络协议栈?
实际上,Linux 在 2.4 版本中,做了一些修改,避免了从内核缓冲区拷贝到 Socket buffer 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。具体如下图:
现在,index.html 要从文件进入到网络协议栈,只需 2 次拷贝:第一次使用 DMA 引擎从文件拷贝到内核缓冲区,第二次从内核缓冲区将数据拷贝到网络协议栈;内核缓存区只会拷贝一些 offset 和 length 信息到 SocketBuffer,基本无消耗。
等一下,不是说零拷贝吗?为什么还是要 2 次拷贝?
答:首先我们说零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有 kernel buffer 有一份数据,sendFile 2.1 版本实际上有 2 份数据,算不上零拷贝)。例如我们刚开始的例子,内核缓存区和 Socket 缓冲区的数据就是重复的。
而零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的 CPU 缓存伪共享以及无 CPU 校验和计算。
再稍微讲讲 mmap 和 sendFile 的区别。
Java 世界的例子
kafka 在客户端和 broker 进行数据传输时,会使用 transferTo 和 transferFrom 方法,即对应 Linux 的 sendFile。
tomcat 内部在进行文件拷贝的时候,也会使用 transferto 方法。
tomcat 在处理一下心跳保活时,也会调用该 sendFile 方法。
在 pulsar 项目中,下载文件时,也会使用 sendFile。如下图:
所以,如果你需要优化网络传输的性能,或者文件读写的速度,请尽量使用零拷贝。他不仅能较少复制拷贝次数,还能较少上下文切换,缓存行污染。
官网:https://netty.io/
Netty对JDK自带的NIO的API进行了封装,解决了上述问题。
根据Reactor的数量和处理资源池线程的数量不同,有3种典型的实现
针对单Reactor多线程模型中,Reactor在单线程中运行,高并发场景下容易成为性能瓶颈,可以让Reactor在多线程中运行
Netty主要基于主从Reactors多线程模型(如图)做了一定的改进,其中主从Reactor多线程模型有多个Reactor
package com.wolfx.netty.simple;
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;
/**
* @description:
* @author: sukang
* @date: 2020-06-23 14:43
*/
public class NettyServer {
public static void main(String[] args) throws Exception {
//创建BossGroup和WorkerGroup
//说明
//1.创建两个线程组bossGroup和workerGroup
//2.bossGroup只是处理连接请求,真正的和客户端业务处理,会交给workerGroup完成
//3.两个都是无限循环
//4.bossGroup和workerGroup含有的子线程(NioEventLoop)的个数
//默认实际cpu核数*2
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap = new ServerBootstrap();
//使用链式编程来进行设置
bootstrap.group(bossGroup, workerGroup)//设置两个线程组
.channel(NioServerSocketChannel.class)//使用NioSocketChanel作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG, 128)//设置线程队列得到连接个数
.childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>(){//创建一个通道测试对象(匿名对象)
//给pipeline设置处理器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyServerHandler());
}
});//给我们的workerGroup的EventLoop对应的管道设置处理器
System.out.println(".....服务器isready...");
//绑定一个端口并且同步,生成了一个ChannelFuture对象
//启动服务器(并绑定端口)
ChannelFuture cf=bootstrap.bind(6668).sync();
//对关闭通道进行监听
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
package com.wolfx.netty.simple;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.CharsetUtil;
/**
* @description:
* 1.我们自定义一个Handler需要继续netty规定好的某个HandlerAdapter(规范)
* 2.这时我们自定义一个Handler,才能称为一个handler
* @author: sukang
* @date: 2020-06-23 16:01
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
//读取数据实际(这里我们可以读取客户端发送的消息)
/**
* @description:
* 1.ChannelHandlerContextctx:上下文对象,含有管道pipeline,通道channel,地址
* 2.Object msg:就是客户端发送的数据默认Object
* @param ctx
* @param msg
* @return: void
* @author: sukang
* @date: 2020/6/23 16:03
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("服务器读取线程" + Thread.currentThread().getName());
System.out.println("server ctx=" + ctx);
System.out.println("看看channel和pipeline的关系");
Channel channel = ctx.channel();
ChannelPipeline pipeline = ctx.pipeline();//本质是一个双向链接,出站入站
//将msg转成一个ByteBuf
//ByteBuf是Netty提供的,不是NIO的ByteBuffer.
ByteBuf buf = (ByteBuf)msg;
System.out.println("客户端发送消息是:"+buf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址:"+channel.remoteAddress());
}
//数据读取完毕
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//writeAndFlush 是write + flush
//将数据写入到缓存,并刷新
//一般讲,我们对这个发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端~(>^ω^<)喵",CharsetUtil.UTF_8));
}
//处理异常,一般是需要关闭通道
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
package com.wolfx.netty.simple;
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;
/**
* @description:
* @author: sukang
* @date: 2020-06-23 16:15
*/
public class NettyClient {
public static void main(String[] args) throws Exception{
//客户端需要一个事件循环组
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建客户端启动对象
//注意客户端使用的不是 ServerBootstrap 而是 Bootstrap
Bootstrap bootstrap = new Bootstrap();
//设置相关参数
bootstrap.group(group) //设置线程组
.channel(NioSocketChannel.class) // 设置客户端通道的实现类(反射)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyClientHandler()); //加入自己的处理器
}
});
System.out.println("客户端 ok..");
//启动客户端去连接服务器端
//关于 ChannelFuture 要分析,涉及到netty的异步模型
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
//给关闭通道进行监听
channelFuture.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}
package com.wolfx.netty.simple;
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 {
System.out.println("client " + ctx);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, server: (>^ω^<)喵", CharsetUtil.UTF_8));
}
//当通道有读取事件时,会触发
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
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();
}
}
package com.wolfx.netty.simple;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import java.util.concurrent.TimeUnit;
/*
说明
1. 我们自定义一个Handler 需要继续netty 规定好的某个HandlerAdapter(规范)
2. 这时我们自定义一个Handler , 才能称为一个handler
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
//读取数据实际(这里我们可以读取客户端发送的消息)
/*
1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
2. Object msg: 就是客户端发送的数据 默认Object
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//比如这里我们有一个非常耗时长的业务-> 异步执行 -> 提交该channel 对应的
//NIOEventLoop 的 taskQueue中,
//解决方案1 用户程序自定义的普通任务
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));
System.out.println("channel code=" + ctx.channel().hashCode());
} catch (Exception ex) {
System.out.println("发生异常" + ex.getMessage());
}
}
});
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵3", CharsetUtil.UTF_8));
System.out.println("channel code=" + ctx.channel().hashCode());
} catch (Exception ex) {
System.out.println("发生异常" + ex.getMessage());
}
}
});
//解决方案2 : 用户自定义定时任务 -》 该任务是提交到 scheduleTaskQueue中
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵4", CharsetUtil.UTF_8));
System.out.println("channel code=" + ctx.channel().hashCode());
} catch (Exception ex) {
System.out.println("发生异常" + ex.getMessage());
}
}
}, 5, TimeUnit.SECONDS);
System.out.println("go on ...");
// System.out.println("服务器读取线程 " + Thread.currentThread().getName() + " channle =" + ctx.channel());
// System.out.println("server ctx =" + ctx);
// System.out.println("看看channel 和 pipeline的关系");
// Channel channel = ctx.channel();
// ChannelPipeline pipeline = ctx.pipeline(); //本质是一个双向链接, 出站入站
//
//
// //将 msg 转成一个 ByteBuf
// //ByteBuf 是 Netty 提供的,不是 NIO 的 ByteBuffer.
// ByteBuf buf = (ByteBuf) msg;
// System.out.println("客户端发送消息是:" + buf.toString(CharsetUtil.UTF_8));
// System.out.println("客户端地址:" + channel.remoteAddress());
}
//数据读取完毕
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//writeAndFlush 是 write + flush
//将数据写入到缓存,并刷新
//一般讲,我们对这个发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵1", CharsetUtil.UTF_8));
}
//处理异常, 一般是需要关闭通道
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
NioEventLoopGroup下包含多个NioEventLoop
每个NioEventLoop中包含有一个Selector,一个taskQueue
每个NioChannel只会绑定在唯一的NioEventLoop上
每个NioChannel都绑定有一个自己的ChannelPipeline
通过isDone方法来判断当前操作是否完成;
通过isSuccess方法来判断已完成的当前操作是否成功;
通过getCause方法来获取已完成的当前操作失败的原因;
通过isCancelled方法来判断已完成的当前操作是否被取消;
通过addListener方法来注册监听器,当操作已完成(isDone方法返回完成),将会通知指定的监听器;如果Future对象已完成,则通知指定的监听器
package com.wolfx.netty.http;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @description:
* @author: sukang
* @date: 2020-06-24 10:58
*/
public class TestServer {
public static void main(String[] args) throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new TestServerInitializer());
ChannelFuture channelFuture = serverBootstrap.bind(6668).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
package com.wolfx.netty.http;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
/**
* @description:
* @author: sukang
* @date: 2020-06-24 11:09
*/
public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//向管道加入处理器
//得到管道
ChannelPipeline pipeline= ch.pipeline();
//加入一个netty提供的 httpServerCodec codec=>[coder-decoder]
//HttpServerCodec说明
//1.HttpServerCodec是netty提供的处理http的编-解码器
pipeline.addLast("MyHttpServerCodec",new HttpServerCodec());
//2.增加一个自定义的handler
pipeline.addLast("MyTestHttpServerHandler",new TestHttpServerHandler());
}
}
package com.wolfx.netty.http;
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;
/*
说明
1. SimpleChannelInboundHandler 是 ChannelInboundHandlerAdapter
2. HttpObject 客户端和服务器端相互通讯的数据被封装成 HttpObject
*/
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
//channelRead0 读取客户端数据
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
System.out.println("对应的channel=" + ctx.channel() + " pipeline=" + ctx
.pipeline() + " 通过pipeline获取channel" + ctx.pipeline().channel());
System.out.println("当前ctx的handler=" + ctx.handler());
//判断 msg 是不是 httprequest请求
if(msg instanceof HttpRequest) {
System.out.println("ctx 类型="+ctx.getClass());
System.out.println("pipeline hashcode" + ctx.pipeline().hashCode() + " TestHttpServerHandler hash=" + this.hashCode());
System.out.println("msg 类型=" + msg.getClass());
System.out.println("客户端地址" + ctx.channel().remoteAddress());
//获取到
HttpRequest httpRequest = (HttpRequest) msg;
//获取uri, 过滤指定的资源
URI uri = new URI(httpRequest.uri());
if("/favicon.ico".equals(uri.getPath())) {
System.out.println("请求了 favicon.ico, 不做响应");
return;
}
//回复信息给浏览器 [http协议]
ByteBuf content = Unpooled.copiedBuffer("hello, 我是服务器", CharsetUtil.UTF_8);
//构造一个http的相应,即 httpresponse
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
//将构建好 response返回
ctx.writeAndFlush(response);
}
}
}
Netty中所有的IO操作都是异步的,不能立刻得知消息是否被正确处理。但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过Future和ChannelFutures,他们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件
常见的方法有
Channel channel(),返回当前正在进行IO操作的通道
ChannelFuture sync(),等待异步操作执行完毕
ChannelPipeline是一个重点:
假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到字节数是不确定的,故可能存在以下四种情况:
- 服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包
- 服务端一次接受到了两个数据包,D1和D2粘合在一起,称之为TCP粘包
- 服务端分两次读取到了数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这称之为TCP拆包
- 服务端分两次读取到了数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余部分内容D1_2和完整的D2包。