netty入门-5 ServerBootstrap与Bootstarp

前言

本来这篇应该紧接着说明FuturePromise
但是考虑前文第三篇即用到了ServerBootstrap来启动一个服务器,并且我读的闪电侠netty,先写的服务器与客户端启动这部分。索性就先写出来了。主要内容来自闪电侠netty

ServerBootstrap

ServerBootstrap就是一个辅助我们把服务器拉起来的辅助类,怎么用直接看下面。

服务器编写

解释全写在注释里,方便看。
示例程序是书里的。

public class NettyServer{
	public static  void  main(String[] args){
		//下面我们可以看到用了两个EventLoopGroup,一个作为boss,一个作为worker。
		//boss内的EventLoop负责处理连接请求。
		//worker中则是负责连接后的channel的io请求处理。
		NioEventLoopGroup  bossGroup  =  newNioEventLoopGroup();
		NioEventLoopGroup  workerGroup  =  newNioEventLoopGroup();

		ServerBootstrap  serverBootstrap  =  newServerBootstrap();
		
		serverBootstrap
		.group(bossGroup,  workerGroup)//给服务器指定boss和worker。两个线程组职能分离
		.channel(NioServerSocketChannel.class)//这里使用NIO类型的ServerSocketChannel。也有其他类型,不过常用NIO类型的。
		//childHandler之前第三篇文章说过,child指代ServerSocketChannel处理连接请求后产生的新的SocketChannel,所以childHandler表示对服务器连接的Channel添加的处理器。也即对Channel的数据读写如何进行处理。第三篇文章讲到了。
		.childHandler(newChannelInitializer<NioSocketChannel>(){
			protectedvoid  initChannel(NioSocketChannel  ch){
			
			}
		})//绑定一个端口
		serverBootstrap.bind(8000);
	}
}

梳理一下就是用ServerBootstrap 类,指定groupIo模型channel),写新连接的读写处理逻辑,绑定端口。

这里注意bind操作是一个异步操作
且我们要绑定的端口可能已经被其他应用占用了。所以需要我们递增寻找一个没有被占用的端口。
我们在第四篇文章中说到有这个ChannelFuture处理异步结果。
而这个bind也是一个异步操作,也会返回一个ChannelFuture
我们正好可以对bind操作产生的`ChannelFuture添加一个回调,来进行递增寻找端口的操作。

端口递增绑定

比如端口9000被占用了,我们要继续去bind 9001,直到找到一个可用端口。

//下面代码是一个框架,可以通过future的状态来做不同处理(关于future的状态,下篇来讲,这里可以理解为通过isSuccess()可以了解到bind操作是否成功)
//成功不用多说,失败了就要递增访问端口号。
serverBootstrap.bind(8000).addListener((ChannelFutureListener) future -> {
    if(future.isSuccess()){
    
    }else{
    	serverBootstrap.bind(8001);
    }
})

//书里这个代码挺妙的
//我们发现直接在else部分写一个bind操作似乎不行,因为这个bind操作做完失败的话我们就没有后续了。
//抽象一下,似乎无限复制代码在else部分再写一个bind(8000).addListener..,然后在其内部再写一个逻辑判断,再进行bind。
//失败就不断调用本身,递归思想

private static void Bind(ServerBootstrap serverBootstrap, int port){
	serverBootstrap.bind(8000).addListener((ChannelFutureListener) future -> {
	    if(future.isSuccess()){
	    	system.out.println("端口 " + port +"绑定成功" );
	    }else{
	    	system.out.println("端口 " + port +"绑定失败" );
	    	Bind(serverBootstrap,port+1);	
	    }
	})
} 

其他方法

这些方法课上确实没咋说,书里这里带了一笔。

//handler方法,和上文的childHandler一样,第三篇文章说到了。
//childHandler用于给新Channel指定读写操作的处理逻辑
//handler就是给服务器启动过程指定一些逻辑。通常用不到
serverBootstrap.handler(newChannelInitializer<NioServerSocketChannel>(){
	protected void initChannel(NioServerSocketChannel  ch)  
	{
		System.out.println("服务端启动中")}
})


//attr和childAttr两个方法都是给Channel添加一个键值对。attr是给ServerSocketChannel的。childAttr是给所有正常连接channel的。
//即Channel内部会维护一个Map存储这些键值对。要取可以再用attr或者childAttr,根据键来取属性值
serverBootstrap.attr(AttributeKey.newInstance("serverName"),  "nettyServer")
serverBootstrap.childAttr(AttributeKey.newInstance("clientKey"),  "clientValue")


//option和childOption方法是设置TCP参数的。
//同样一个针对ServerSocketChannel。childOption针对其他正常连接channel
//书中给了几个例子
serverBootstrap
//表示系统用于临时存放已完成三次握手的请求的队列的最大长度,如果连接建立频繁,服务器处理创建新连接较慢,则可以适当调大这个参数。
.option(ChannelOption.SO_BACKLOG,  1024)
//ChannelOption.SO_KEEPALIVE表示是否开启TCP底层心跳机制,true表示开启。
.childOption(ChannelOption.SO_KEEPALIVE,  true)
//ChannelOption.TCP_NODELAY表示是否开启Nagle算法,true表示关闭,false表示开启。通俗地说,如果要求高实时性,有数据发送时就马上发送,就设置为关闭;如果需要减少发送次数,减少网络交互,就设置为开启。
.childOption(ChannelOption.TCP_NODELAY,  true)

Bootstarp

ServerBootstrap类似,不过Bootstrap是用于启动客户端的辅助类。

客户端启动

同样是指定线程模型(group),IO模型(NioSocketChannel),IO操作处理逻辑(handler)。
其实同服务器启动基本一致。
不过这里书里提到了连接失败的重试处理。我们来看看

NioEventLoopGroup  workerGroup  =  newNioEventLoopGroup()Bootstrap  bootstrap  =  newBootstrap();
bootstrap
//  1.指定线程模型
.group(workerGroup)
//  2.指定IO  类型为NIO
.channel(NioSocketChannel.class)
//  3.IO 处理逻辑
.handler(newChannelInitializer<SocketChannel>(){
	@Override
	publicvoid  initChannel(SocketChannel  ch){
	}
});
//  4.建立连接
bootstrap.connect("juejin.cn",  80).addListener(future  ->if(future.isSuccess()){
	System.out.println("连接成功!");
	}else{
	System.err.println("连接失败!");
	}
});
//我们见到与上面服务器创建时,我们要做端口递增绑定类似的部分。
//这里原理类似,因为connect与bind都是异步方法,所以我们要写好异步回调,表明在异步操作成功后做什么,失败后做什么
//connect失败说明连接失败。我们可以用重试,一定次数后放弃连接,并且不会在失败后立即重连,指数增长重试事件,如1秒,2秒,4秒,8秒等。默认次数设为5次下面是实现逻辑。
//也采用封装一个递归函数来递归调用的方法。
private static void connect(Bootstrap bootstrap,String host,int port,int retry){
	bootstrap.connect(host,  port).addListener(future ->{
	if(future.isSuccess()){
		System.out.println("连接成功!");
	}else if(retry  ==  0) {
		System.err.println("重试次数已用完,放弃连接!");
	}else{
		//  第几次重连
		int  order  =  (MAX_RETRY  -  retry)  +  1;
		//  本次重连的间隔
		int  delay  =  1  <<  order;
		System.err.println(newDate()  +  ":连接失败,第"  +  order  +  "次重连……");
		bootstrap.config().group().schedule(()  ->  connect(bootstrap,  host,  port,
		retry--),  delay,  TimeUnit.SECONDS);
	}
	});
} 

上面代码比较好懂,
不过这里等待一定时间后执行任务的操作,即定时任务操作,用了bootstrap.config().group().schedule()来做。
group()返回我们配置的workerGroupschedule()正是其中的方法,来定时执行一个任务。
在上面最后的else中,根据剩余重试次数retry,来确定本次重试之前要等多长时间,即delay,再通过schedule()递归调用函数本身,不过递归函数的参数retry要减1,因为它代表剩余重试次数。

其他方法

ServerBootstrap一样,Bootstrap也有attr方法来给Channel设置键值对。不再赘述。
也有option方法来设置TCP参数。
书中给了如下参数

.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,  5000)
.option(ChannelOption.SO_KEEPALIVE,  true)
.option(ChannelOption.TCP_NODELAY,  true)

ChannelOption.CONNECT_TIMEOUT_MILLIS表示连接的超时时间,超过这个时间,如果仍未连接到服务端,则表示连接失败。
ChannelOption.SO_KEEPALIVE表示是否开启TCP底层心跳机制,true表示开启。
ChannelOption.TCP_NODELAY表示是否开始Nagle算法,true表示关闭,false表示开启。通俗地说,如果要求高实时性,有数据发送时就马上发送,就设置为true;如果需要减少发送次数,减少网络交互,就设置为false

结语

本文内容均来自《跟着闪电侠学Netty》。
看了看书,感觉挺有帮助的。技术还是视频,书籍,源码,实践相辅相成好。

本篇仅是对ServerBootstrapBootstrap的补充说明,后续还会按视频课程的编排来写。中间穿插一些书中写到但是视频没有提到的东西。

下篇本来应该写PromiseFuture了。但是发现前面Future已经说不少了,这个Promise也并不是很重要,所以先放一放。
下面按顺序应该是handlerpipeline,还有ByteBuf

这里稍微说下,这本书里的内容其实和课程中很多部分都是一致的,可能仅仅一些内容不同。我尽量结合两边来写。

感谢阅读,欢迎批评指正。

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的 Spring Boot 应用程序中使用 NettyServerBootstrapBootstrap 来创建服务器和客户端的示例: 1. 首先,需要在 Maven 中添加以下依赖项: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.65.Final</version> </dependency> ``` 2. 创建 Netty 服务器的代码: ```java @Component public class NettyServer { private final EventLoopGroup bossGroup = new NioEventLoopGroup(); private final EventLoopGroup workerGroup = new NioEventLoopGroup(); @Autowired private NettyServerInitializer nettyServerInitializer; public void start() throws InterruptedException { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(nettyServerInitializer); ChannelFuture future = bootstrap.bind(8080).sync(); future.channel().closeFuture().sync(); } @PreDestroy public void stop() { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } ``` 3. 创建 Netty 服务器的初始化器: ```java @Component public class NettyServerInitializer extends ChannelInitializer<SocketChannel> { @Autowired private NettyServerHandler nettyServerHandler; @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(nettyServerHandler); } } ``` 4. 创建 Netty 服务器的处理器: ```java @Component public class NettyServerHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { // 处理接收到的数据 System.out.println("Received message: " + msg); ctx.writeAndFlush("Server response: " + msg); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 处理异常 cause.printStackTrace(); ctx.close(); } } ``` 5. 创建 Netty 客户端的代码: ```java @Component public class NettyClient { private final EventLoopGroup group = new NioEventLoopGroup(); @Autowired private NettyClientInitializer nettyClientInitializer; public String sendMessage(String host, int port, String message) throws InterruptedException, ExecutionException { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(nettyClientInitializer); ChannelFuture future = bootstrap.connect(host, port).sync(); future.channel().writeAndFlush(message).sync(); future.channel().closeFuture().sync(); return nettyClientInitializer.getResponse(); } @PreDestroy public void stop() { group.shutdownGracefully(); } } ``` 6. 创建 Netty 客户端的初始化器: ```java @Component public class NettyClientInitializer extends ChannelInitializer<SocketChannel> { private String response; @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new StringEncoder(), new StringDecoder(), new NettyClientHandler(this)); } public String getResponse() { return response; } public void setResponse(String response) { this.response = response; } } ``` 7. 创建 Netty 客户端的处理器: ```java public class NettyClientHandler extends SimpleChannelInboundHandler<String> { private final NettyClientInitializer nettyClientInitializer; public NettyClientHandler(NettyClientInitializer nettyClientInitializer) { this.nettyClientInitializer = nettyClientInitializer; } @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { // 处理接收到的数据 nettyClientInitializer.setResponse(msg); ctx.close(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 处理异常 cause.printStackTrace(); ctx.close(); } } ``` 在 Spring Boot 应用程序中使用 NettyServerBootstrapBootstrap 来创建服务器和客户端,需要将 Netty 服务器和客户端的 Bean 注入到 Spring 容器中,并在需要使用时使用它们。例如,在控制器中使用 Netty 客户端: ```java @RestController @RequestMapping("/netty") public class NettyController { @Autowired private NettyClient nettyClient; @GetMapping("/send") public String sendMessage() throws InterruptedException, ExecutionException { return nettyClient.sendMessage("localhost", 8080, "Hello from client"); } } ``` 这样,当访问 /netty/send 路径时,将会向 Netty 服务器发送消息并接收响应。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值