今天换换口味,由于本人工作中马上要用到Netty这个东西,所以这几天也是开始学习,此学习过程应该会是一个完整的系列,初步的目标是先会用,之后有机会再深入。鉴于笔者之前也从未使用过Netty,所以有什么疏漏错误的,希望大家指正,先行感谢!
学习Netty之前需要先回顾一下java中的IO编程和NIO编程,因为根据我的理解Netty就是对JDK中的NIO编程做了一层很好的封装,可以让开发者专注于业务逻辑的开发。
官方的定义是这样的:
Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients
大致意思是说:
Netty是一个异步事件驱动的网络应用程序框架
用于快速开发可维护的高性能协议服务器和客户端。
1
IO编程模型
回顾一个Java Socket编程的简单例子,应用场景为 客户度每隔2秒钟向服务端发送一个消息,
服务端代码如下:
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by zhoudl on 2018/10/3.
* IO编程 服务端
*/
public class IOServer {
public static void main(String[] args) {
try {
server(8080);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 1.首先创建了一个serverSocket来监听 某个 端口,然后创建一个线程
* 2.线程里面不断调用阻塞方法 serversocket.accept();获取新的连接
* 3.当获取到新的连接之后,给每条连接创建一个新的线程,这个线程负责从该连接中读取数据
* 4.然后读取数据是以字节流的方式
*
* @param port
* @throws IOException
*/
public static void server(int port) throws IOException {
// 1
ServerSocket serverSocket = new ServerSocket(port);
new Thread(() -> {
while (true) {
try {
// 2.调用阻塞方法获取新的连接
Socket socket = serverSocket.accept();
// 3.每个连接都交给一个新的线程去处理
new Thread (() -> {
int length;
byte[] bytes = new byte[1024];
try {
InputStream inputStream = socket.getInputStream();
// 4.根据字节流方式获取数据
while ((length = inputStream.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, length));
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
客户端代码如下:
package top.zhoudl.io;
import java.io.IOException;
import java.net.Socket;
import java.util.Date;
/**
* Created by zhoudl on 2018/10/3.
* IO 编程 客户端
*/
public class IOClient {
public static void client(int port,String address){
new Thread(() -> {
try {
// 建立连接
Socket socket = new Socket(address,port);
while (true) {
try {
socket.getOutputStream()
.write((new Date() + ": hello world").getBytes());
// 每隔2秒向服务器发送一条 hello world
Thread.sleep(2000);
} catch (Exception e) {
}
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
public static void main(String[] args) {
client(8080,"127.0.0.1");
}
}
2
NIO编程模型
3
总结
根据大佬的讲解,这是大佬的原话:
接下来进入正题,开始学习Netty编程
4
Netty编程模型
服务端
package top.zhoudl.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
/**
* Created by zhoudl on 2018/10/3.
*/
public class NettyServer {
public static void server (int port) {
ServerBootstrap serverBootstrap = new ServerBootstrap();
// boss 对应 IOServer.java 中的接受新连接线程,主要负责创建新连接
NioEventLoopGroup boss = new NioEventLoopGroup();
// worker 对应 IOClient.java 中的负责读取数据的线程,主要用于读取数据以及业务逻辑处理
NioEventLoopGroup worker = new NioEventLoopGroup();
serverBootstrap.group(boss,worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast(new StringDecoder());
channel.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println(msg);
}
});
}
})
.bind(8080);
}
public static void main(String[] args) {
server(8080);
}
}
客户端
package top.zhoudl.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import java.util.Date;
/**
* Created by zhoudl on 2018/10/3.
*/
public class NettyClient {
public static void client(String hots,int port) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
// 设置线程组
bootstrap.group(group)
// 设置线程模型
.channel(NioSocketChannel.class)
// 设置连接读写处理逻辑
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) {
channel.pipeline().addLast(new StringEncoder());
}
});
// 连接指定地址的指定端口
ChannelFuture connect = bootstrap.connect(hots, port);
// 判断是否连接成功
// 实际开发中 在此处肯定是要添加连接重试的逻辑的
connect.addListener(future -> {
if (future.isSuccess()) {
System.out.println("连接成功!");
} else {
System.err.println("连接失败!");
}
});
Channel channel = connect.channel();
while (true) {
channel.writeAndFlush(new Date() + ": hello world!");
Thread.sleep(2000);
}
}
public static void main(String[] args) throws InterruptedException {
client("127.0.0.1",8080);
}
}
5
总结
生活是好的,峰回路转,柳暗花明,前面总会有另一番不同的风光。
使用了Netty进行NIO编程之后,是不是觉得爽了很多,虽然现在你还不太了解上述代码具体是做什么用的,但我想聪明的你对比着前边的或者是自己已经熟知的Java Socket编程也能猜个一二。所谓学习,便是大胆猜测,小心验证,当最终的结果证实你的猜想之后 ,你会发现,疯起来,整个世界都是你的!(相应的,沉默下来,整个世界都与你无关)
服务端启动过程分析
最小参数启动举例:
public class NettyServer {
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
}
});
serverBootstrap.bind(8080);
}
}
NioEventLoopGroup
,我们暂且定义名字为 bossGroup
和 workerGroup
,和传统的IO编程相比,他们俩可以理解为 IO编程中的两个线程组,前者表示监听端口,使用accept()
方法建立连接,后者表示每一个连接的读写数据线程组,bossGroup
负责接活,workerGroup
则负责真正的干活(这也是起这个名字的源头,bossGroup
就好比老板,负责接活签合同,而workerGroup
则表示下海干活的员工);ServerBootstrap
是一个启动类,负责引导我们对服务端的操作;ServerBootstrap
的.group()
方法将两大线程组设置进去,也就是说这个引导类至此线程模型就确定了;.channel(NioServerSocketChannel.class)
,如果要设置成Bio的话对应 OioServerSocketChannel.class
childHandler()
方法,给引导类创建一个ChannelInitializer
,这个类 是 Netty 对 NIO 类型的连接的抽象,而我们前面NioServerSocketChannel
也是对 NIO 类型的连接的抽象,NioServerSocketChannel
和NioSocketChannel
的概念可以和 BIO 编程模型中的ServerSocket
以及Socket
两个概念对应上。总结一点
启动一个Netty服务端,最少需要3个过程
客户端启动流程概述
学完服务端启动流程之后,客户端启动流程就比较简单了,代码直接参考上述NettyClient.java
Netty提供的API个人觉得很优秀,而且大多都简介明了,无论是客户端还是服务端。客户端启动流程大致概括如下:
Bootstrap
,创建线程NioEventLoopGroup
;Bootstrap
的 group()
方法为Bootstrap
设置线程组,也就是指定线程模型;.channel(NioSocketChannel.class)
指定为NIO模型;.handler(new ChannelInitializer<Channel>()
指定一个handler
;connect()
方法的第一个参数为IP或者域名,第二个参数为连接端口号。此外,connect()
方法返回的是一个Future
,了解Java的应该会知道这是个一个异步的,通过 addListener
方法可以监听到连接是否成功,进而打印出连接信息。客户端启动过程中会有个失败重连的问题:
鉴于失败重连和第一次获取连的逻辑基本分毫不差,所以此处我们很自然的想到了可以使用递归,所以地NettyClient.java中的connect()
方法做出封装
private static void connect(Bootstrap bootstrap, String host, int port, int retryCounts) {
bootstrap.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
System.out.println("success!");
} else if (retryCounts == 0) {
System.err.println("The number of retries has been used up, giving up the connection!");
} else {
// 第几次重新建立连接
int number = (MAX_RETRY - retryCounts) + 1;
// 本次重连的时间间隔
int delay = 1 << order;
System.err.println(new Date() + ":Connection failed,Reconnected for the" + number + "time");
bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retryCounts - 1), delay, TimeUnit
.SECONDS);
}
});
}
以上今天的文章就结束了,在此感谢闪电侠大佬的Netty学习教程,此文是作为学习总结,有不的地方欢迎大家指正!
下一篇将了解利用Netty进行服务端和客户端的双向通信。
以上代码会同步更新在本人的Github和CSDN上
Github地址:https://github.com/Bylant/LeetCode
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有