Netty是由JBOSS提供的一個(gè)java開源框架。Netty提供異步的、事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架和工具,用以快速開發(fā)高性能、高可靠性的網(wǎng)絡(luò)服務(wù)器和客戶端程序。也就是說,Netty 是一個(gè)基于NIO的客戶、服務(wù)器端編程框架,使用Netty 可以確保你快速和簡(jiǎn)單的開發(fā)出一個(gè)網(wǎng)絡(luò)應(yīng)用,例如實(shí)現(xiàn)了某種協(xié)議的客戶、服務(wù)端應(yīng)用。Netty相當(dāng)于簡(jiǎn)化和流線化了網(wǎng)絡(luò)應(yīng)用的編程開發(fā)過程,例如:基于TCP和UDP的socket服務(wù)開發(fā)?!翱焖佟焙汀昂?jiǎn)單”并不用產(chǎn)生維護(hù)性或性能上的問題。Netty 是一個(gè)吸收了多種協(xié)議(包括FTP、SMTP、HTTP等各種二進(jìn)制文本協(xié)議)的實(shí)現(xiàn)經(jīng)驗(yàn),并經(jīng)過相當(dāng)精心設(shè)計(jì)的項(xiàng)目。最終,Netty 成功的找到了一種方式,在保證易于開發(fā)的同時(shí)還保證了其應(yīng)用的性能,穩(wěn)定性和伸縮性
本文將帶領(lǐng)大家學(xué)習(xí)如何在SpringBoot項(xiàng)目中集成Netty
一、Netty服務(wù)端
由于是SpringBoot項(xiàng)目,因此這里不展示SpringBoot相關(guān)的依賴
1、導(dǎo)入依賴
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.36.Final</version>
</dependency>
2、編寫netty處理器
/**
* Socket攔截器,用于處理客戶端的行為
*
* @author Gjing
**/
@Slf4j
public class SocketHandler extends ChannelInboundHandlerAdapter {
public static final ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
/**
* 讀取到客戶端發(fā)來的消息
*
* @param ctx ChannelHandlerContext
* @param msg msg
* @throws Exception e
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 由于我們配置的是 字節(jié)數(shù)組 編解碼器,所以這里取到的用戶發(fā)來的數(shù)據(jù)是 byte數(shù)組
byte[] data = (byte[]) msg;
log.info("收到消息: " + new String(data));
// 給其他人轉(zhuǎn)發(fā)消息
for (Channel client : clients) {
if (!client.equals(ctx.channel())) {
client.writeAndFlush(data);
}
}
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
log.info("新的客戶端鏈接:" + ctx.channel().id().asShortText());
clients.add(ctx.channel());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
clients.remove(ctx.channel());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.channel().close();
clients.remove(ctx.channel());
}
}
3、編寫netty初始化器
/**
* Socket 初始化器,每一個(gè)Channel進(jìn)來都會(huì)調(diào)用這里的 InitChannel 方法
* @author Gjing
**/
@Component
public class SocketInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 添加對(duì)byte數(shù)組的編解碼,netty提供了很多編解碼器,你們可以根據(jù)需要選擇
pipeline.addLast(new ByteArrayDecoder());
pipeline.addLast(new ByteArrayEncoder());
// 添加上自己的處理器
pipeline.addLast(new SocketHandler());
}
}
4、編寫netty服務(wù)
/**
* @author Gjing
**/
@Slf4j
@Component
public class SocketServer {
@Resource
private SocketInitializer socketInitializer;
@Getter
private ServerBootstrap serverBootstrap;
/**
* netty服務(wù)監(jiān)聽端口
*/
@Value("${netty.port:8088}")
private int port;
/**
* 主線程組數(shù)量
*/
@Value("${netty.bossThread:1}")
private int bossThread;
/**
* 啟動(dòng)netty服務(wù)器
*/
public void start() {
this.init();
this.serverBootstrap.bind(this.port);
log.info("Netty started on port: {} (TCP) with boss thread {}", this.port, this.bossThread);
}
/**
* 初始化netty配置
*/
private void init() {
// 創(chuàng)建兩個(gè)線程組,bossGroup為接收請(qǐng)求的線程組,一般1-2個(gè)就行
NioEventLoopGroup bossGroup = new NioEventLoopGroup(this.bossThread);
// 實(shí)際工作的線程組
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
this.serverBootstrap = new ServerBootstrap();
this.serverBootstrap.group(bossGroup, workerGroup) // 兩個(gè)線程組加入進(jìn)來
.channel(NioServerSocketChannel.class) // 配置為nio類型
.childHandler(this.socketInitializer); // 加入自己的初始化器
}
}
5、啟動(dòng)netty
由于使用SpringBoot,因此我們可以監(jiān)聽項(xiàng)目啟動(dòng)成功后觸發(fā)啟動(dòng)Netty服務(wù)器,這時(shí)候只要SpringBoot啟動(dòng)就行了
/**
* 監(jiān)聽Spring容器啟動(dòng)完成,完成后啟動(dòng)Netty服務(wù)器
* @author Gjing
**/
@Component
public class NettyStartListener implements ApplicationRunner {
@Resource
private SocketServer socketServer;
@Override
public void run(ApplicationArguments args) throws Exception {
this.socketServer.start();
}
}
-
效果圖
image.png
二、Netty客戶端
客戶端這里以NIO來編寫,就不寫Netty了,在實(shí)際工作中,其實(shí)也都是Netty服務(wù)端,客戶端可能是 WebSocket 或者 Socket,我們這里就以 Socket 為例,由于 NIO 是Java提供的,所以我們不需要引入什么依賴
1、編寫客戶端線程
因?yàn)槲覀儾荒茏枞?主線程,因此需要開啟子線程來作為客戶端
/**
* @author Gjing
**/
public class ClientThread implements Runnable{
private final Selector selector;
public ClientThread(Selector selector) {
this.selector = selector;
}
@Override
public void run() {
try {
for (; ; ) {
int channels = selector.select();
if (channels == 0) {
continue;
}
Set<SelectionKey> selectionKeySet = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeySet.iterator();
while (keyIterator.hasNext()) {
SelectionKey selectionKey = keyIterator.next();
// 移除集合當(dāng)前得selectionKey,避免重復(fù)處理
keyIterator.remove();
if (selectionKey.isReadable()) {
this.handleRead(selector, selectionKey);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 處理可讀狀態(tài)
private void handleRead(Selector selector, SelectionKey selectionKey) throws IOException {
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
StringBuilder message = new StringBuilder();
if (channel.read(byteBuffer) > 0) {
byteBuffer.flip();
message.append(StandardCharsets.UTF_8.decode(byteBuffer));
}
// 再次注冊(cè)到選擇器上,繼續(xù)監(jiān)聽可讀狀態(tài)
channel.register(selector, SelectionKey.OP_READ);
System.out.println(message);
}
}
2、客戶端邏輯
/**
* 聊天客戶端
*
* @author Gjing
**/
public class ChatClient {
public void start(String name) throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8088));
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_READ);
// 監(jiān)聽服務(wù)端發(fā)來得消息
new Thread(new ClientThread(selector)).start();
// 監(jiān)聽用戶輸入
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String message = scanner.nextLine();
if (StringUtils.hasText(message)) {
socketChannel.write(StandardCharsets.UTF_8.encode(name + ": " + message));
}
}
}
}
3、客戶端1
/**
* @author Gjing
**/
public class Client1 {
public static void main(String[] args) throws IOException {
new ChatClient().start("李四");
}
}
4、客戶端2
/**
* @author Gjing
**/
public class Client2 {
public static void main(String[] args) throws IOException {
new ChatClient().start("張三");
}
}
5、啟動(dòng)這兩個(gè)客戶端

服務(wù)端也觸發(fā)了日志打印,監(jiān)聽到客戶端加入

現(xiàn)在我們通過客戶端2,發(fā)一條消息看看

客戶端1 成功收到了 客戶端2的消息,同理我們通過客戶端1 發(fā)送

服務(wù)端也輸出了消息日志

文章到此結(jié)束啦,這就是一個(gè)普通的小案例哦,更多知識(shí)大家可以通過官網(wǎng)進(jìn)行學(xué)習(xí),Demo源代碼地址:SpringBoot-Netty
