netty(二)---客户端连接
概述
先了解一下 netty 大概框架图 ,可以看到客户端的创建和服务端最大的区别 - 服务端传入两个 EventLoopGroup,客户端传入一个 EventLoopGroup - channel 的类型也不同,服务端传入的是 NioServerSocketChannel ,客户端传入的是 NioSocketChannel - 服务端存在 childHandler 的设置,客户端没有,
客户端连接过程 : - 和服务端一样先创建 EventLoopGroup (只有一个,内部多个保持多个EventLoop线程,执行处理事务) - connect 方法 ,和服务端一样,使用group里的EventLoop创建一个 channel ,然后注册到 select 中去(这个过程都在channel 中进行) - (异步执行)当连接不上就会交给 EventLoop线程中执行监听的任务。 - 而一旦监听到了就交给 channel 执行。 - selectKey 可以attack一个object,刚好可以用来放channel ,然后在某个线程监听到某个实现的时候再把 channel 拿出来用
源码分析
实际上客户端只有一个 Reactor . 那么重点的逻辑就到了 connect 那里
/**
* Connect a {@link Channel} to the remote peer.
*/
public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
if (remoteAddress == null) {
throw new NullPointerException("remoteAddress");
}
validate();
return doConnect(remoteAddress, localAddress);
} /**
* @see {@link #connect()}
*/
private ChannelFuture doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
//创建channel ,注册到 select , initAndRegister方法是父类的方法我们在分析服务端
//的时候已经分析过了
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
//一开始就连接上了,
if (regFuture.cause() != null) {
return regFuture;
} final ChannelPromise promise = channel.newPromise();
if (regFuture.isDone()) {
//链路成功后,异步连接 TCP
doConnect0(regFuture, channel, remoteAddress, localAddress, promise);
} else {
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
doConnect0(regFuture, channel, remoteAddress, localAddress, promise);
}
});
} return promise;
} private static void doConnect0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation.
// 到了执行连接的操作就转到了Netty 的 NIO线程执行,此刻客户端返回,连接异步执行。
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
if (localAddress == null) {
//没传 localAddress 会传到 TailHandler的connect方法
channel.connect(remoteAddress, promise);
} else {
//正常情况下到 HeaderHandler的connect 方法
channel.connect(remoteAddress, localAddress, promise);
}
promise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
我们看一下 HeaderHandler的connect 方法。
@Override
public void connect(
ChannelHandlerContext ctx,
SocketAddress remoteAddress, SocketAddress localAddress,
ChannelPromise promise) throws Exception {
//执行 HeaderHandler内的unsafe字段的 connect 方法
unsafe.connect(remoteAddress, localAddress, promise);
}
unsafe会执行AbstractNioChannel(这个类是NioServerSocketChannel和NioSocketChannel的共同父类)的connect 方法
@Override
public void connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
if (!ensureOpen(promise)) {
return;
} try {
if (connectPromise != null) {
throw new IllegalStateException("connection attempt already made");
} boolean wasActive = isActive();
//doConnect 方法是个抽象方法
if (doConnect(remoteAddress, localAddress)) {
fulfillConnectPromise(promise, wasActive);
} else {
connectPromise = promise;
requestedRemoteAddress = remoteAddress; // Schedule connect timeout.
int connectTimeoutMillis = config().getConnectTimeoutMillis();
if (connectTimeoutMillis > 0) {
connectTimeoutFuture = eventLoop().schedule(new Runnable() {
@Override
public void run() {
ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
ConnectTimeoutException cause =
new ConnectTimeoutException("connection timed out: " + remoteAddress);
if (connectPromise != null && connectPromise.tryFailure(cause)) {
close(voidPromise());
}
}
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);
} promise.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isCancelled()) {
if (connectTimeoutFuture != null) {
connectTimeoutFuture.cancel(false);
}
connectPromise = null;
close(voidPromise());
}
}
});
}
} catch (Throwable t) {
if (t instanceof ConnectException) {
Throwable newT = new ConnectException(t.getMessage() + ": " + remoteAddress);
newT.setStackTrace(t.getStackTrace());
t = newT;
}
promise.tryFailure(t);
closeIfClosed();
}
}
NioSocketChannel 的 doConnect 方法
@Override
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
if (localAddress != null) {
javaChannel().socket().bind(localAddress);
}
boolean success = false;
try {
boolean connected = javaChannel().connect(remoteAddress);
if (!connected) {
//如果绑定不成功,注册连接事件
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
success = true;
return connected;
} finally {
if (!success) {
doClose();
}
}
}
后续更新...
netty(二)---客户端连接的更多相关文章
- netty 处理客户端连接
Netty如何处理连接事件 上文讲了Netty如何绑定端口,现在我们来阅读下netty如何处理connect事件.上文我们说了NioEventLoop启动后不断去调用select的事件,当客户端连接时 ...
- 【ActiveMQ】之安全机制(二)客户端连接安全
配置完管控台的安全之后,我们还要配置客户端连接安全,否则大家都可以往MQ上发送消息,这样太危险! 根据官方文档,http://activemq.apache.org/security.html Act ...
- Netty 多客户端连接与通信
实现场景: 聊天 服务端,客户端A,客户端B,客户端C.当客户端发送消息给服务端后,服务端在将这条消息广播个所有客户端户端A,客户端B,客户端C. 需求1: 客户端上线后,会通知所有客户端上线. 如客 ...
- Netty源码分析 (六)----- 客户端连接接入accept过程
通读本文,你会了解到1.netty如何接受新的请求2.netty如何给新请求分配reactor线程3.netty如何给每个新连接增加ChannelHandler netty中的reactor线程 ne ...
- java socket通讯(二)处理多个客户端连接
通过java socket通讯(一) 入门示例,就可以实现服务端和客户端的socket通讯,但是上一个例子只能实现一个服务端和一个客户端之间的通讯,如果有多个客户端连接服务端,则需要通过多线程技术来实 ...
- Memcache的客户端连接系列(二) Python
关键词: Memcached Python 客户端 声明:本文并非原创,转自华为云帮助中心的分布式缓存服务(Memcached)的用户指南.客户端连接方法通用,故摘抄过来分享给大家. Python ...
- 一个I/O线程可以并发处理N个客户端连接和读写操作 I/O复用模型 基于Buf操作NIO可以读取任意位置的数据 Channel中读取数据到Buffer中或将数据 Buffer 中写入到 Channel 事件驱动消息通知观察者模式
Tomcat那些事儿 https://mp.weixin.qq.com/s?__biz=MzI3MTEwODc5Ng==&mid=2650860016&idx=2&sn=549 ...
- Redis基础知识之————如何处理客户端连接
redis 连接建立 Redis Redis 通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自客户端的连接,当一个连接建立后,Redis 内部会进行以下一些操作: 首先,客户端 ...
- PostgreSQL数据库服务端监听设置及客户端连接方法教程
众所周知,PostgreSQL 是一个自由的对象-关系数据库服务器(数据库管理系统),是一个可以免费使用的开放源代码数据库系统.本文详细介绍了PostgreSQL数据库服务端监听设置及客户端连接方法, ...
随机推荐
- EF的预先加载--Eager Loading
预先加载 在对一种类型的实体进行查询时,将相关的实体作为查询的一部分一起加载.预先加载可以使用Include()方法实现. 在此需要说明的是:EF中有两种表关联的方法,一种是Join()方法,一种是I ...
- jQuery添加/删除元素
jQuery append() 方法 jQuery append() 方法在被选元素的结尾插入内容(仍然该元素的内部). 追加前:<p>这是一个文本段落</p> $(" ...
- Openstack 简单梳理,(自用 慎点)
这个图里面的彩色方块,就是OpenStack最核心的组件. 推荐几个大咖,大家可以百度找他们的博客来看:陈沙克.何明桂.孔令贤,Cloudman.
- 一键安装各个版本boost库(无需编译)
1.NuGet 最简单的,用VS自带的NuGet包管理器安装,一般比较常用的上面都有 2.下载exe安装包 在这里https://sourceforge.net/projects/boost/file ...
- Spring事物白话文
脏读:一个事物读到了另外一个事物中尚未提交的数据: 幻读:A事物读到了B事物新增的数据:白话一点就是读到了别人insert的语句: 不可重复读:A事物读取了B事物更改的数据:白话一点就是我两次sele ...
- UES
Spring-core-4.3.16 ObjectUtils.java public static boolean isEmpty(Object obj) { if (obj == null) { r ...
- 操作系統3-內存管理(Linux內存管理)
操作系統3-內存管理(Linux系統的內存管理方法) 9.Linux系統的內存管理方法 Linux採用"按需調頁"算法,支持三層管理策略.由於Intel CPU在硬件級提供了段式存 ...
- WPF学习笔记一之布局
1.Canvas 布局控件Canvas主要用来画图,注意Canvas.Left/Right/Top/Bottom <Canvas Margin="10,10,10,10" B ...
- jmeter的使用--添加自定义函数和导入自定义jar
1.添加自定义函数,增加 号码生成函数 MobileGenerator和身份证生成函数IdCardGenerator 在package org.apache.jmeter.functions;中增加 ...
- 二叉树的详细实现 (C++)
二叉树的定义 以递归形式给出的:一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根结点加上两棵分别称为左子树和右子树的.互不相交的二叉树组成.二又树的特点是每个结点最多有两个子女, ...