netty中Pipeline的ChannelHandler执行顺序案例详解
一、netty的Pipeline模型
netty的Pipeline模型用的是责任链设计模式,当boss线程监控到绑定端口上有accept事件,此时会为该socket连接实例化Pipeline,并将InboundHandler和OutboundHandler按序加载到Pipeline中,然后将该socket连接(也就是Channel对象)挂载到selector上。一个selector对应一个线程,该线程会轮询所有挂载在他身上的socket连接有没有read或write事件,然后通过线程池去执行Pipeline的业务流。selector如何查询哪些socket连接有read或write事件,主要取决于调用操作系统的哪种IO多路复用内核,如果是select(注意,此处的select是指操作系统内核的select IO多路复用,不是netty的seletor对象),那么将会遍历所有socket连接,依次询问是否有read或write事件,最终操作系统内核将所有IO事件的socket连接返回给netty进程,当有很多socket连接时,这种方式将会大大降低性能,因为存在大量socket连接的遍历和内核内存的拷贝。如果是epoll,性能将会大幅提升,因为他基于完成端口事件,已经维护好有IO事件的socket连接列表,selector直接取走,无需遍历,也少掉内核内存拷贝带来的性能损耗。
Pipeline的责任链是通过ChannelHandlerContext对象串联的,ChannelHandlerContext对象里封装了ChannelHandler对象,通过prev和next节点实现双向链表。Pipeline的首尾节点分别是head和tail,当selector轮询到socket有read事件时,将会触发Pipeline责任链,从head开始调起第一个InboundHandler的ChannelRead事件,接着通过fire方法依次触发Pipeline上的下一个ChannelHandler,如下图:
ChannelHandler分为InbounHandler和OutboundHandler,InboundHandler用来处理接收消息,OutboundHandler用来处理发送消息。head的ChannelHandler既是InboundHandler又是OutboundHandler,无论是read还是write都会经过head,所以head封装了unsafe方法,用来操作socket的read和write。tail的ChannelHandler只是InboundHandler,read的Pipleline处理将会最终到达tail。
二、通过六组实验验证InboundHandler和OutboundHandler的执行顺序
在做实验之前,先把实验代码贴出来。
EchoServer类:
package com.wisdlab.nettylab; 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; /**
* @ClassName EchoServer
* @Description TODO
* @Author felix
* @Date 2019/9/26 10:37
* @Version 1.0
**/
public class EchoServer {
private int port; public EchoServer(int port) {
this.port = port;
} private void run() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup(); try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//outboundhandler一定要放在最后一个inboundhandler之前
//否则outboundhandler将不会执行到
socketChannel.pipeline().addLast(new EchoOutboundHandler3());
socketChannel.pipeline().addLast(new EchoOutboundHandler2());
socketChannel.pipeline().addLast(new EchoOutboundHandler1()); socketChannel.pipeline().addLast(new EchoInboundHandler1());
socketChannel.pipeline().addLast(new EchoInboundHandler2());
socketChannel.pipeline().addLast(new EchoInboundHandler3());
}
})
.option(ChannelOption.SO_BACKLOG, 10000)
.childOption(ChannelOption.SO_KEEPALIVE, true);
System.out.println("EchoServer正在启动."); ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
System.out.println("EchoServer绑定端口" + port); channelFuture.channel().closeFuture().sync();
System.out.println("EchoServer已关闭.");
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
} public static void main(String[] args) {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (Exception e) {
e.printStackTrace();
}
} EchoServer server = new EchoServer(port);
server.run();
}
}
EchoInboundHandler1类:
package com.wisdlab.nettylab; 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; /**
* @ClassName EchoInboundHandler1
* @Description TODO
* @Author felix
* @Date 2019/9/26 11:15
* @Version 1.0
**/
public class EchoInboundHandler1 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("进入 EchoInboundHandler1.channelRead"); String data = ((ByteBuf)msg).toString(CharsetUtil.UTF_8);
System.out.println("EchoInboundHandler1.channelRead 收到数据:" + data);
ctx.fireChannelRead(Unpooled.copiedBuffer("[EchoInboundHandler1] " + data, CharsetUtil.UTF_8)); System.out.println("退出 EchoInboundHandler1 channelRead");
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("[EchoInboundHandler1.channelReadComplete]");
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("[EchoInboundHandler1.exceptionCaught]" + cause.toString());
}
}
EchoInboundHandler2类:
package com.wisdlab.nettylab; 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; /**
* @ClassName EchoInboundHandler2
* @Description TODO
* @Author felix
* @Date 2019/9/27 15:35
* @Version 1.0
**/
public class EchoInboundHandler2 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("进入 EchoInboundHandler2.channelRead"); String data = ((ByteBuf) msg).toString(CharsetUtil.UTF_8);
System.out.println("EchoInboundHandler2.channelRead 接收到数据:" + data);
//ctx.writeAndFlush(Unpooled.copiedBuffer("[第一次write] [EchoInboundHandler2] " + data, CharsetUtil.UTF_8));
ctx.channel().writeAndFlush(Unpooled.copiedBuffer("测试一下channel().writeAndFlush", CharsetUtil.UTF_8));
ctx.fireChannelRead(Unpooled.copiedBuffer("[EchoInboundHandler2] " + data, CharsetUtil.UTF_8)); System.out.println("退出 EchoInboundHandler2 channelRead");
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("[EchoInboundHandler2.channelReadComplete]读取数据完成");
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("[EchoInboundHandler2.exceptionCaught]");
}
}
EchoInboundHandler3类:
package com.wisdlab.nettylab; 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; /**
* @ClassName EchoInboundHandler3
* @Description TODO
* @Author felix
* @Date 2019/10/23 13:43
* @Version 1.0
**/
public class EchoInboundHandler3 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("进入 EchoInboundHandler3.channelRead"); String data = ((ByteBuf)msg).toString(CharsetUtil.UTF_8);
System.out.println("EchoInboundHandler3.channelRead 接收到数据:" + data);
//ctx.writeAndFlush(Unpooled.copiedBuffer("[第二次write] [EchoInboundHandler3] " + data, CharsetUtil.UTF_8));
ctx.fireChannelRead(msg); System.out.println("退出 EchoInboundHandler3 channelRead");
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("[EchoInboundHandler3.channelReadComplete]读取数据完成");
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("[EchoInboundHandler3.exceptionCaught]");
} }
EchoOutboundHandler1类:
package com.wisdlab.nettylab; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.util.CharsetUtil; /**
* @ClassName EchoOutboundHandler1
* @Description TODO
* @Author felix
* @Date 2019/9/27 15:36
* @Version 1.0
**/
public class EchoOutboundHandler1 extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("进入 EchoOutboundHandler1.write"); //ctx.writeAndFlush(Unpooled.copiedBuffer("[第一次write中的write]", CharsetUtil.UTF_8));
ctx.channel().writeAndFlush(Unpooled.copiedBuffer("在OutboundHandler里测试一下channel().writeAndFlush", CharsetUtil.UTF_8));
ctx.write(msg); System.out.println("退出 EchoOutboundHandler1.write");
}
}
EchoOutboundHandler2类:
package com.wisdlab.nettylab; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.util.CharsetUtil; /**
* @ClassName EchoOutboundHandler2
* @Description TODO
* @Author felix
* @Date 2019/9/27 15:36
* @Version 1.0
**/
public class EchoOutboundHandler2 extends ChannelOutboundHandlerAdapter { @Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("进入 EchoOutboundHandler2.write"); //ctx.writeAndFlush(Unpooled.copiedBuffer("[第二次write中的write]", CharsetUtil.UTF_8));
ctx.write(msg); System.out.println("退出 EchoOutboundHandler2.write");
}
}
EchoOutboundHandler3类:
package com.wisdlab.nettylab; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise; /**
* @ClassName EchoOutboundHandler3
* @Description TODO
* @Author felix
* @Date 2019/10/23 23:23
* @Version 1.0
**/
public class EchoOutboundHandler3 extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("进入 EchoOutboundHandler3.write"); ctx.write(msg); System.out.println("退出 EchoOutboundHandler3.write");
} }
实验一:在InboundHandler中不触发fire方法,后续的InboundHandler还能顺序执行吗?
如上图所示,InboundHandler2没有调用fire方法:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("进入 EchoInboundHandler1.channelRead"); String data = ((ByteBuf)msg).toString(CharsetUtil.UTF_8);
System.out.println("EchoInboundHandler1.channelRead 收到数据:" + data);
//ctx.fireChannelRead(Unpooled.copiedBuffer("[EchoInboundHandler1] " + data, CharsetUtil.UTF_8)); System.out.println("退出 EchoInboundHandler1 channelRead");
}
那么InboundHandler中的代码还会被执行到吗?看一下执行结果:
由上图可知,InboundHandler2没有调用fire事件,InboundHandler3没有被执行。
结论:InboundHandler是通过fire事件决定是否要执行下一个InboundHandler,如果哪个InboundHandler没有调用fire事件,那么往后的Pipeline就断掉了。
实验二:InboundHandler和OutboundHandler的执行顺序是什么?
加入Pipeline的ChannelHandler的顺序如上图所示,那么最后执行的顺序如何呢?执行结果如下:
由上图可知,执行顺序为:
InboundHandler1 => InboundHandler2 => OutboundHandler1 => OutboundHander2 => OutboundHandler3 => InboundHandler3
所以,我们得到以下几个结论:
1、InboundHandler是按照Pipleline的加载顺序,顺序执行。
2、OutboundHandler是按照Pipeline的加载顺序,逆序执行。
实验三:如果把OutboundHandler放在InboundHandler的后面,OutboundHandler会执行吗?
执行结果如下:
由此可见,OutboundHandler没有执行,为什么呢?因为Pipleline是执行完所有有效的InboundHandler,再返回执行在最后一个InboundHandler之前的OutboundHandler。注意,有效的InboundHandler是指fire事件触达到的InboundHandler,如果某个InboundHandler没有调用fire事件,后面的InboundHandler都是无效的InboundHandler。为了印证这一点,我们继续做一个实验,我们把其中一个OutboundHandler放在最后一个有效的InboundHandler之前,看看这唯一的一个OutboundHandler是否会执行,其他OutboundHandler是否不会执行。
执行结果如下:
由此可见,只执行了OutboundHandler1,其他OutboundHandler没有被执行。
所以,我们得到以下几个结论:
1、有效的InboundHandler是指通过fire事件能触达到的最后一个InboundHander。
2、如果想让所有的OutboundHandler都能被执行到,那么必须把OutboundHandler放在最后一个有效的InboundHandler之前。
3、推荐的做法是通过addFirst加载所有OutboundHandler,再通过addLast加载所有InboundHandler。
实验四:如果其中一个OutboundHandler没有执行write方法,那么消息会不会发送出去?
我们把OutboundHandler2的write方法注掉
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("进入 EchoOutboundHandler3.write"); //ctx.write(msg); System.out.println("退出 EchoOutboundHandler3.write");
}
执行结果如下:
可以看到,OutboundHandler3并没有被执行到,另外,客户端也没有收到发送的消息。
所以,我们得到以下几个结论:
1、OutboundHandler是通过write方法实现Pipeline的串联的。
2、如果OutboundHandler在Pipeline的处理链上,其中一个OutboundHandler没有调用write方法,最终消息将不会发送出去。
实验五:ctx.writeAndFlush 的OutboundHandler的执行顺序是什么?
我们设定ChannelHandler在Pipeline中的加载顺序如下:
OutboundHandler3 => InboundHandler1 => OutboundHandler2 => InboundHandler2 => OutboundHandler1 => InboundHandler3
在InboundHander2中调用ctx.writeAndFlush:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("进入 EchoInboundHandler2.channelRead"); String data = ((ByteBuf) msg).toString(CharsetUtil.UTF_8);
System.out.println("EchoInboundHandler2.channelRead 接收到数据:" + data);
ctx.writeAndFlush(Unpooled.copiedBuffer("[第一次write] [EchoInboundHandler2] " + data, CharsetUtil.UTF_8));
//ctx.channel().writeAndFlush(Unpooled.copiedBuffer("测试一下channel().writeAndFlush", CharsetUtil.UTF_8));
ctx.fireChannelRead(Unpooled.copiedBuffer("[EchoInboundHandler2] " + data, CharsetUtil.UTF_8)); System.out.println("退出 EchoInboundHandler2 channelRead");
}
执行结果如下:
由上图可知,依次执行了OutboundHandler2和OutboundHandler3,为什么会这样呢?因为ctx.writeAndFlush是从当前的ChannelHandler开始,向前依次执行OutboundHandler的write方法,所以分别执行了OutboundHandler2和OutboundHandler3:
OutboundHandler3 => InboundHandler1 => OutboundHandler2 => InboundHandler2 => OutboundHandler1 => InboundHandler3
所以,我们得到如下结论:
1、ctx.writeAndFlush是从当前ChannelHandler开始,逆序向前执行OutboundHandler。
2、ctx.writeAndFlush所在ChannelHandler后面的OutboundHandler将不会被执行。
实验六:ctx.channel().writeAndFlush 的OutboundHandler的执行顺序是什么?
还是实验五的代码,不同之处只是把ctx.writeAndFlush修改为ctx.channel().writeAndFlush。
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("进入 EchoInboundHandler2.channelRead"); String data = ((ByteBuf) msg).toString(CharsetUtil.UTF_8);
System.out.println("EchoInboundHandler2.channelRead 接收到数据:" + data);
//ctx.writeAndFlush(Unpooled.copiedBuffer("[第一次write] [EchoInboundHandler2] " + data, CharsetUtil.UTF_8));
ctx.channel().writeAndFlush(Unpooled.copiedBuffer("测试一下channel().writeAndFlush", CharsetUtil.UTF_8));
ctx.fireChannelRead(Unpooled.copiedBuffer("[EchoInboundHandler2] " + data, CharsetUtil.UTF_8)); System.out.println("退出 EchoInboundHandler2 channelRead");
}
执行结果如下:
由上图可知,所有OutboundHandler都执行了,由此我们得到结论:
1、ctx.channel().writeAndFlush 是从最后一个OutboundHandler开始,依次逆序向前执行其他OutboundHandler,即使最后一个ChannelHandler是OutboundHandler,在InboundHandler之前,也会执行该OutbondHandler。
2、千万不要在OutboundHandler的write方法里执行ctx.channel().writeAndFlush,否则就死循环了。
三、总结
1、InboundHandler是通过fire事件决定是否要执行下一个InboundHandler,如果哪个InboundHandler没有调用fire事件,那么往后的Pipeline就断掉了。
2、InboundHandler是按照Pipleline的加载顺序,顺序执行。
3、OutboundHandler是按照Pipeline的加载顺序,逆序执行。
4、有效的InboundHandler是指通过fire事件能触达到的最后一个InboundHander。
5、如果想让所有的OutboundHandler都能被执行到,那么必须把OutboundHandler放在最后一个有效的InboundHandler之前。
6、推荐的做法是通过addFirst加载所有OutboundHandler,再通过addLast加载所有InboundHandler。
7、OutboundHandler是通过write方法实现Pipeline的串联的。
8、如果OutboundHandler在Pipeline的处理链上,其中一个OutboundHandler没有调用write方法,最终消息将不会发送出去。
9、ctx.writeAndFlush是从当前ChannelHandler开始,逆序向前执行OutboundHandler。
10、ctx.writeAndFlush所在ChannelHandler后面的OutboundHandler将不会被执行。
11、ctx.channel().writeAndFlush 是从最后一个OutboundHandler开始,依次逆序向前执行其他OutboundHandler,即使最后一个ChannelHandler是OutboundHandler,在InboundHandler之前,也会执行该OutbondHandler。
12、千万不要在OutboundHandler的write方法里执行ctx.channel().writeAndFlush,否则就死循环了。
netty中Pipeline的ChannelHandler执行顺序案例详解的更多相关文章
- java 多线程Callable和Runable执行顺序问题详解
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt125 毫无疑问 Runnable会进行异步执行,此处不多说,主要说明Call ...
- SQL 中 SELECT 语句的执行顺序
好像自已在书写 SQL 语句时由于不清楚各个关键字的执行顺序, 往往组织的 SQL 语句缺少很好的逻辑, 凭感觉 "拼凑" ( 不好意思, 如果您的 SQL 语句也经常 " ...
- SQLServer2005中查询语句的执行顺序
SQLServer2005中查询语句的执行顺序 --1.from--2.on--3.outer(join)--4.where--5.group by--6.cube|rollup--7.havin ...
- 容易被忽略的事----sql语句中select语句的执行顺序
关于Sql中Select语句的执行顺序,一直很少注意这个问题,对于关键字的使用也很随意,至于效率问题,因为表中的数据量都不是很大,所以也不是很在意. 今天在一次面试的时候自己见到了,感觉没一点的印象, ...
- MySQL-SQL语句中SELECT语句的执行顺序
SELECT语句的执行顺序大家比较少关注,下面将为您详细介绍SQL语句中SELECT语句的执行顺序,供您参考,希望对您能够有所帮助. SELECT语句的执行的逻辑查询处理步骤: (8)SELECT ( ...
- mysql 中sql语句的执行顺序
今天突然想起来,之前面试一个很牛逼的公司(soho)的时候,一个美眉面试官,面试的时候问到了很多之前都没有意识到的问题,回想起来那美眉看着年纪不大,技术那是真666啊.好了说一下人家问的这个有关mys ...
- 在Spring Bean的生命周期中各方法的执行顺序
Spring 容器中的 Bean 是有生命周期的,Spring 允许在 Bean 在初始化完成后以及 Bean 销毁前执行特定的操作,常用的设定方式有以下十种: 通过实现 InitializingBe ...
- 第7.18节 案例详解:Python类中装饰器@staticmethod定义的静态方法
第7.18节 案例详解:Python类中装饰器@staticmethod定义的静态方法 上节介绍了Python中类的静态方法,本节将结合案例详细说明相关内容. 一. 案例说明 本节定义了类Sta ...
- 第7.16节 案例详解:Python中classmethod定义的类方法
第7.16节 案例详解:Python中classmethod定义的类方法 上节介绍了类方法定义的语法以及各种使用的场景,本节结合上节的知识具体举例说明相关内容. 一. 案例说明 本节定义的一个 ...
随机推荐
- Jenkins 持续集成安装及使用简介
博客地址:http://www.moonxy.com 一.前言 持续集成(Continuous integration,简称CI)指的是,频繁地(一天多次)将代码集成到主干. 持续集成的目的,就是让产 ...
- 关于读写APP.config文件能读却写不了的问题
今天要求用winform写一个窗口用来读写一个App.config,要对 <appSettings>里面的add key和value进行添加和修改.要实现的效果图如下: -------- ...
- 不知道如何实现服务的动态发现?快来看看 Dubbo 是如何做到的
上篇文章如果有人问你 Dubbo 中注册中心工作原理,就把这篇文章给他大致了解了注册中心作用以及 Dubbo Registry 模块源码,这篇文章将深入 Dubbo ZooKeeper 模块,去了解如 ...
- java -PDF添加文本水印与图片水印
java pdf添加水印文本及图片文本 PDF文件添加文本水印: private static int interval = 30; public static void waterMark(Stri ...
- 23种设计模式之工厂方法(Factory Method Pattern)
工厂方法 前面我们学习了简单工厂,发现一个问题就是简单工厂集合了矛盾,为了解决这个问题我们针对每一种产品提供一个工厂类.通过不同的工厂实例来创建不同的产品实例.在同一等级结构中,支持增加任意产品这种设 ...
- 死磕 java同步系列之Phaser源码解析
问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...
- mysql 5.7 Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated column ...报错
使用mysql在执行一条插入语句时 , ', "hhh"); 报错:Expression #1 of ORDER BY clause is not in GROUP BY clau ...
- Django之使用redis缓存session,历史浏览记录,首页数据实现性能优化
Redis缓存session 配置Django缓存数据到redis中 # diango的缓存配置 CACHES = { "default": { "BACKEND&quo ...
- .netcore+vue+elementUI 前后端分离---支持前端、后台业务代码扩展的快速开发框架
框架采用.NetCore + Vue前后端分离,并且支持前端.后台代码业务动态扩展,框架内置了一套有着20多种属性配置的代码生成器,可灵活配置生成的代码,代码生成器界面配置完成即可生成单表(主表)的增 ...
- redis服务打不开--解决办法
D:\>redis-server[11896] 04 Dec 19:20:05.122 # Warning: no config file specified, using the defaul ...