架構師入門筆記十 Netty5快速入門

架構師入門筆記十 Netty5快速入門

在瞭解IO,NIO,AIO知識後,學習Netty5便會輕鬆很多,本章節主要介紹Netty是如何接收,反饋數據和拆包粘包的問題

1 Netty基礎知識

1.1 Netty作用

Netty是一個提供異步事件驅動的網絡應用框架,用以快速開發高性能高可靠性 的網絡服務器和客戶端程序。Netty是一個NIO框架,使用它可以簡單快速地開發網絡應用程序,比如客戶端和服務端的協議。Netty簡化了網絡程序的開發過程,比如TCP和UDP的 Socket的開發。

1.2 TCP和UDP

TCP(Transmission Control Protocol,傳輸控制協議)是基於連接的協議,也就是說,在正式收發數據前,必須和對方建立可靠的連接。
UDP(User Data Protocol,用戶數據報協議)是與TCP相對應的協議。它是面向非連接的協議,它不與對方建立連接,而是直接就把數據包發送過去!
  TCP UDP
是否連接 面向連接 面向非連接
傳輸可靠性 可靠 不可靠
應用場合 傳輸大量數據 傳輸少量數據
速度 慢 

2 HelloWorld代碼

2.1 DISCARD服務:丟棄服務,丟棄了所有接收到的數據,並不做任何響應。簡單理解就是接收數據,不返回數據
2.2 ECHO服務:響應式協議,這個協議針對任何接收的數據都會返回一個響應
2.3 代碼事例:
首選是服務器處理類
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;

/**
 * DISCARD協議 丟棄協議,其實就是隻接收數據,不做任何處理。
 * ECHO服務(響應式協議),其實就是返回數據。
 * 實現步驟:
 * step1 繼承 ChannelHandlerAdapter
 * step2 覆蓋chanelRead()事件處理方法
 * step3 釋放ByteBuffer,ByteBuf是一個引用計數對象,這個對象必須顯示地調用release()方法來釋放
 * step4 異常處理,即當Netty由於IO錯誤或者處理器在處理事件時拋出的異常時。在大部分情況下,捕獲的異常應該被記錄下來並且把關聯的channel給關閉掉。
 */
public class DiscardServerHandler extends ChannelHandlerAdapter{
	
	@Override
	public void channelRead(ChannelHandlerContext chc, Object msg) {
		try {
			// 簡單的讀寫操作
			ByteBuf buf = (ByteBuf) msg;
			byte[] req = new byte[buf.readableBytes()];
			buf.readBytes(req);
			String body = new String(req, "utf-8");
			System.out.println("Server :" + body);
			chc.writeAndFlush(Unpooled.copiedBuffer("卒...... ".getBytes())); // 返回數據
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			ReferenceCountUtil.release(msg); // 釋放msg
		}
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext chc, Throwable cause) {
		// 這個方法的處理方式會在遇到不同異常的情況下有不同的實現,比如你可能想在關閉連接之前發送一個錯誤碼的響應消息。
		cause.printStackTrace();
		chc.close();
	}
}
然後是服務端Netty啓動代碼
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;

/**
 * step1 創建兩個線程組分別負責接收和處理
 * step2 啓動NIO服務輔助類
 * step3 綁定兩個線程組,指定一個通道,關聯一個處理類,設置一些相關參數
 * step4 監聽端口
 * step5 關閉一些資源
 */
public class DiscardServer {
	
	private static final int PORT = 8888; // 監聽的端口號
	
	public static void main(String[] args) {
		// NioEventLoopGroup 是用來處理I/O操作的多線程事件循環器
		EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用於接收進來的連接
		EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用於處理進來的連接
		try {
			ServerBootstrap bootstrap = new ServerBootstrap(); // ServerBootstrap 是一個啓動NIO服務的輔助啓動類
			bootstrap.group(bossGroup, workerGroup) // 綁定倆個線程組
			.channel(NioServerSocketChannel.class) // 指定用 NioServerSocketChannel 通道
			.childHandler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel socketChannel) throws Exception {
					socketChannel.pipeline().addLast(new DiscardServerHandler()); // DiscardServerHandler是我們自定義的服務器處理類,負責處理連接
				}
			})
			.option(ChannelOption.SO_BACKLOG, 128) // 設置tcp緩衝區
			.childOption(ChannelOption.SO_KEEPALIVE, true); // 設置保持連接
			
			ChannelFuture future = bootstrap.bind(PORT).sync(); // 綁定端口
			future.channel().closeFuture().sync(); // 等待關閉
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			workerGroup.shutdownGracefully(); // 關閉線程組,先打開的後關閉
			bossGroup.shutdownGracefully();
		}
	}

}
客戶端代碼和服務端代碼類似,區別在於:服務端提供監聽端口,客戶端負責連接端口;服務端的輔助類是ServerBootstrap,而客戶端的輔助類是Bootstrap(和ServerSocket,Socket關係很像)
客戶端處理類
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;

/**
 * 
 * 和ServerHandler類似
 *
 */
public class ClientHandler extends ChannelHandlerAdapter{
		
	public void channelRead(ChannelHandlerContext chc, Object msg) {
		try {
			ByteBuf buf = (ByteBuf) msg;
			byte[] req = new byte[buf.readableBytes()];
			buf.readBytes(req);
			String body = new String(req, "utf-8");
			System.out.println("Client :" + body);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			ReferenceCountUtil.release(msg); // 釋放msg
		}
	}
	
	public void exceptionCaught(ChannelHandlerContext chc, Throwable cause) {
		// 這個方法的處理方式會在遇到不同異常的情況下有不同的實現,比如你可能想在關閉連接之前發送一個錯誤碼的響應消息。
		cause.printStackTrace();
		chc.close();
	}

}
客戶端啓動服務類
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class Client {
	
	private static final int PORT = 8888;
	private static final String HOST = "127.0.0.1";
	
	public static void main(String[] args) {
		NioEventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			Bootstrap bootstrap = new Bootstrap();
			bootstrap.group(workerGroup)
			.channel(NioSocketChannel.class)
			.handler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel socketChannel) throws Exception {
					socketChannel.pipeline().addLast(new ClientHandler());
				}
			})
			.option(ChannelOption.SO_KEEPALIVE, true);
			
			ChannelFuture future = bootstrap.connect(HOST, PORT).sync(); // 建立連接
			future.channel().writeAndFlush(Unpooled.copiedBuffer("快醒醒,還有幾個bug沒有改".getBytes())); // 向服務端發送數據
			future.channel().closeFuture().sync();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			workerGroup.shutdownGracefully();
		}
	}

}
到這裏,便是Netty的簡單應用。打印的結果:
Server :快醒醒,還有幾個bug沒有改
Client :卒...... 

3 拆包粘包

3.1 使用特殊的分隔符
3.2 限定長度,不推薦。若發送數據的長度不夠指定長度,則一直處於等待中。
代碼在原來的基礎上做了簡單修改,可以打開註釋自己調試
首選是服務器處理類
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;

/**
 * DISCARD協議 丟棄協議,其實就是隻接收數據,不做任何處理。
 * ECHO服務(響應式協議),其實就是返回數據。
 * 實現步驟:
 * step1 繼承 ChannelHandlerAdapter
 * step2 覆蓋chanelRead()事件處理方法
 * step3 釋放ByteBuffer,ByteBuf是一個引用計數對象,這個對象必須顯示地調用release()方法來釋放
 * step4 異常處理,即當Netty由於IO錯誤或者處理器在處理事件時拋出的異常時。在大部分情況下,捕獲的異常應該被記錄下來並且把關聯的channel給關閉掉。
 */
public class DiscardServerHandler extends ChannelHandlerAdapter{
	
	private static final String DELIMITER = "^_^"; // 拆包分隔符
	
	@Override
	public void channelRead(ChannelHandlerContext chc, Object msg) {
		try {
			// 簡單的讀寫操作
			/*
			ByteBuf buf = (ByteBuf) msg;
			byte[] req = new byte[buf.readableBytes()];
			buf.readBytes(req);
			String body = new String(req, "utf-8");
			System.out.println("Server :" + body);
			chc.writeAndFlush(Unpooled.copiedBuffer("卒...... ".getBytes())); // 返回數據
			*/
			// 加了 StringDecoder 字符串解碼器後可以直接讀取
			System.out.println("Server :" + msg);
			// 分隔符拆包
//			String response = msg + " , 騙你的" + DELIMITER;
//			chc.channel().writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));
			// 限定長度拆包
			chc.channel().writeAndFlush(Unpooled.copiedBuffer(msg.toString().getBytes()));
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			ReferenceCountUtil.release(msg); // 釋放msg
		}
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext chc, Throwable cause) {
		// 這個方法的處理方式會在遇到不同異常的情況下有不同的實現,比如你可能想在關閉連接之前發送一個錯誤碼的響應消息。
		cause.printStackTrace();
		chc.close();
	}
}
然後是服務端Netty啓動代碼
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

/**
 * step1 創建兩個線程組分別負責接收和處理
 * step2 啓動NIO服務輔助類
 * step3 綁定兩個線程組,指定一個通道,關聯一個處理類,設置一些相關參數
 * step4 監聽端口
 * step5 關閉一些資源
 */
public class DiscardServer {
	
	private static final int PORT = 8888; // 監聽的端口號
	private static final String DELIMITER = "^_^"; // 拆包分隔符
	
	public static void main(String[] args) {
		// NioEventLoopGroup 是用來處理I/O操作的多線程事件循環器
		EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用於接收進來的連接
		EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用於處理進來的連接
		try {
			ServerBootstrap bootstrap = new ServerBootstrap(); // ServerBootstrap 是一個啓動NIO服務的輔助啓動類
			bootstrap.group(bossGroup, workerGroup) // 綁定倆個線程組
			.channel(NioServerSocketChannel.class) // 指定用 NioServerSocketChannel 通道
			.childHandler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel socketChannel) throws Exception {
					// 考慮到tcp拆包粘包的問題,升級代碼
					// step1 獲取特殊分隔符的ByteBuffer
					ByteBuf delimiter = Unpooled.copiedBuffer(DELIMITER.getBytes());
					// step2 設置特殊分隔符
//					socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(128, delimiter));
					// 還有一種就是指定長度   二選一 (用的比較少)
					socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(5));
					// step3 設置字符串形式的解碼
					socketChannel.pipeline().addLast(new StringDecoder());
					// step4 設置處理類
					socketChannel.pipeline().addLast(new DiscardServerHandler()); // DiscardServerHandler是我們自定義的服務器處理類,負責處理連接
				}
			})
			.option(ChannelOption.SO_BACKLOG, 128) // 設置tcp緩衝區
			.childOption(ChannelOption.SO_KEEPALIVE, true); // 設置保持連接
			
			ChannelFuture future = bootstrap.bind(PORT).sync(); // 綁定端口
			future.channel().closeFuture().sync(); // 等待關閉
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			workerGroup.shutdownGracefully(); // 關閉線程組,先打開的後關閉
			bossGroup.shutdownGracefully();
		}
	}

}
客戶端處理類
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;

/**
 * 
 * 和ServerHandler類似
 *
 */
public class ClientHandler extends ChannelHandlerAdapter{
		
	public void channelRead(ChannelHandlerContext chc, Object msg) {
		try {
			/*
			ByteBuf buf = (ByteBuf) msg;
			byte[] req = new byte[buf.readableBytes()];
			buf.readBytes(req);
			String body = new String(req, "utf-8");
			*/
			System.out.println("Client : " + msg);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			ReferenceCountUtil.release(msg); // 釋放msg
		}
	}
	
	public void exceptionCaught(ChannelHandlerContext chc, Throwable cause) {
		// 這個方法的處理方式會在遇到不同異常的情況下有不同的實現,比如你可能想在關閉連接之前發送一個錯誤碼的響應消息。
		cause.printStackTrace();
		chc.close();
	}

}
客戶端啓動服務類
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

public class Client {
	
	private static final int PORT = 8888;
	private static final String HOST = "127.0.0.1";
	private static final String DELIMITER = "^_^"; // 拆包分隔符
	
	public static void main(String[] args) {
		NioEventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			Bootstrap bootstrap = new Bootstrap();
			bootstrap.group(workerGroup)
			.channel(NioSocketChannel.class)
			.handler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel socketChannel) throws Exception {
					// 考慮到tcp拆包粘包的問題,升級代碼
					ByteBuf delimiter = Unpooled.copiedBuffer(DELIMITER.getBytes());
//					socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(128, delimiter));
					socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(5));
					socketChannel.pipeline().addLast(new StringDecoder());
					
					socketChannel.pipeline().addLast(new ClientHandler());
				}
			})
			.option(ChannelOption.SO_KEEPALIVE, true);
			
			ChannelFuture future = bootstrap.connect(HOST, PORT).sync(); // 建立連接
//			future.channel().writeAndFlush(Unpooled.copiedBuffer("快醒醒,還有幾個bug沒有改".getBytes())); // 向服務端發送數據
//			future.channel().writeAndFlush(Unpooled.copiedBuffer(("又要加班了"+DELIMITER).getBytes()));
//			future.channel().writeAndFlush(Unpooled.copiedBuffer(("好開心啊T。T"+DELIMITER).getBytes()));
			future.channel().writeAndFlush(Unpooled.copiedBuffer("123456789".getBytes())); // 傳的個數是9個,只打印了5個,還有4個在等待中
			future.channel().closeFuture().sync();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			workerGroup.shutdownGracefully();
		}
	}

}

4 優質博客

Netty 5用戶指南



以上便是Netty5快速入門的內容,如果你覺得不錯,可以點個贊哦大笑大笑大笑 ,下一章介紹Netty5的編解碼知識。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章