netty(五) channel
问题
- channel 是如何处理发送一半中断后继续重发的
- channel 具体作用是什么
概述
这一节我们将介绍 Channel 和内部接口 Unsafe .其中Unsafe 是内部接口,聚合在Channel 中协助网络读写操作相关的操作,设计初衷就是 Channel 的内部辅助类,不应该被用户使用。
继承类分析
继承关系链 :
AbstractChannel -> AbstractNioChannel -> AbstractNioByteChannel -> NioSocketChannel 如下图
从以上的类结构我们也要学习一下类的构建,各个类实现应该实现的功能,最后生成的具体类具有不同的功能。
AbstractChannel ,保存以下重要的字段 ,主要
- EventLoop
- localAddress
- remoteAddress
- unsafe
- DefaultChannelPipleline
- Future类 和 Promise类 等
AbstractNioChannel,从类名可以看出和nio 中 Channel 相关,注册,监听
private final SelectableChannel ch;
protected final int readInterestOp;
private volatile SelectionKey selectionKey;
private volatile boolean inputShutdown; /**
* The future of the current connection attempt. If not null, subsequent
* connection attempts will fail.
*/
private ChannelPromise connectPromise;
private ScheduledFuture<?> connectTimeoutFuture;
private SocketAddress requestedRemoteAddress;
AbstractNioByteChannel 这个类是Channel对Byte进行操作,对ByteBuff的读写。
源码分析
AbstractChannel
AbstractChannel 的读写方法都是交由 ChannelPiple 来解决的
@Override
public Channel read() {
pipeline.read();
return this;
} @Override
public ChannelFuture write(Object msg) {
return pipeline.write(msg);
}
eventLoop方法,直接返回持有的 eventloop对象
@Override
public EventLoop eventLoop() {
return eventLoop;
}
AbstractNioChannel
public abstract class AbstractNioChannel extends AbstractChannel { private static final InternalLogger logger =
InternalLoggerFactory.getInstance(AbstractNioChannel.class); // No.1 注册监听相关的字段
private final SelectableChannel ch;
protected final int readInterestOp;
private volatile SelectionKey selectionKey;
private volatile boolean inputShutdown; // No.2 异步执行的字段,或是回调相关的字段
/**
* The future of the current connection attempt. If not null, subsequent
* connection attempts will fail.
*/
private ChannelPromise connectPromise;
private ScheduledFuture<?> connectTimeoutFuture;
private SocketAddress requestedRemoteAddress; ... //核心方法
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
//拿父类的channel对象(父类的channel对象是java原生channel 对象)
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
eventLoop().selectNow();
selected = true;
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
} //开始read的操作
@Override
protected void doBeginRead() throws Exception {
if (inputShutdown) {
return;
} final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
} //就是改变监听的事件
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
AbstractNioByteChannel
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
int writeSpinCount = -1; //循环
for (;;) {
Object msg = in.current(true); if (msg == null) {
// Wrote all messages.写完了(发送完了)所有的消息,清除标志,结束
clearOpWrite();
break;
} if (msg instanceof ByteBuf) {
//加入是ByteBuf类型
ByteBuf buf = (ByteBuf) msg;
int readableBytes = buf.readableBytes();
//判断当前的可读字节是否为 0 ,为 0 丢弃掉
if (readableBytes == 0) {
in.remove();
continue;
} boolean setOpWrite = false;
boolean done = false;
long flushedAmount = 0;
//循环发送次数
if (writeSpinCount == -1) {
writeSpinCount = config().getWriteSpinCount();
}
for (int i = writeSpinCount - 1; i >= 0; i --) {
//doWriteBytes 子类实现
int localFlushedAmount = doWriteBytes(buf);
if (localFlushedAmount == 0) {
setOpWrite = true;
break;
} flushedAmount += localFlushedAmount;
//一直到不可读
if (!buf.isReadable()) {
done = true;
break;
}
}
//发送完,更新发送的进度(有可能没发完)
in.progress(flushedAmount); if (done) {
in.remove();
} else {
//没发完,设置写半包标识,启动刷新线程继续发送之前没有发送完成的半包消息
incompleteWrite(setOpWrite);
break;
}
} else if (msg instanceof FileRegion) {
FileRegion region = (FileRegion) msg;
boolean setOpWrite = false;
boolean done = false;
long flushedAmount = 0;
if (writeSpinCount == -1) {
writeSpinCount = config().getWriteSpinCount();
} //循环发送
for (int i = writeSpinCount - 1; i >= 0; i --) {
long localFlushedAmount = doWriteFileRegion(region);
if (localFlushedAmount == 0) {
setOpWrite = true;
break;
} flushedAmount += localFlushedAmount;
if (region.transfered() >= region.count()) {
done = true;
break;
}
}
//发送完(有可能发送了一半)更新进度
in.progress(flushedAmount); if (done) {
in.remove();
} else {
//没法完,创建一个任务扔到EventLoop
incompleteWrite(setOpWrite);
break;
}
} else {
throw new UnsupportedOperationException("unsupported message type: " + StringUtil.simpleClassName(msg));
}
}
} //没写完(没发送完)
protected final void incompleteWrite(boolean setOpWrite) {
// Did not write completely.
if (setOpWrite) {
setOpWrite();
} else {
// Schedule flush again later so other tasks can be picked up in the meantime
//创建任务扔到 eventLoop执行
Runnable flushTask = this.flushTask;
if (flushTask == null) {
flushTask = this.flushTask = new Runnable() {
@Override
public void run() {
flush();
}
};
}
eventLoop().execute(flushTask);
}
}
循环发送次数是指一次发送没有完成时(写半包),程序就继续尝试循环写操作,此时IO线程是不能处理其他事件的,例如读新的消息或者执行定时任务和 NioTask 等, 如果网络IO阻塞或者对方接收消息太慢,可能会导致线程假死,于是就要循环发送。
AbstractNioMessageChannel
我们再来看一下AbstractNioChannel 的另外一个子类 AbstractNioMessageChannel,直接看doWrite方法
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
final SelectionKey key = selectionKey();
final int interestOps = key.interestOps(); for (;;) {
Object msg = in.current();
if (msg == null) {
// Wrote all messages.
if ((interestOps & SelectionKey.OP_WRITE) != 0) {
key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
}
break;
} boolean done = false;
for (int i = config().getWriteSpinCount() - 1; i >= 0; i --) {
if (doWriteMessage(msg, in)) {
done = true;
break;
}
} if (done) {
in.remove();
} else {
// Did not write all messages.
//没发送完,设置标志,交给 select 多路复用器轮询对应的channel重新发送尚未发送完成的半包信息
if ((interestOps & SelectionKey.OP_WRITE) == 0) {
key.interestOps(interestOps | SelectionKey.OP_WRITE);
}
break;
}
}
}
AbstractNioMessageChannel 和 AbstractNioByteChannel的区别在于
NioServerSocketChannel 和 NioServerChannel 的分析
NioSocketChannel 和 NioServerSocketChannel 的区别到底是什么?后者是服务端当中负责绑定端口,读取数据功能,连接和断开,写消息都不支持,这些功能都在NioSocketChannel中实现
AbstractNioMessageServerChannel 的具体子类是 NioServerSocketChannel(该类是服务器端接受处理客户端的channel),它的doReadMessages方法(被对应的unsafe类read方法,这里可能有点饶,具体看代码实现)分析如下
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = javaChannel().accept(); try {
if (ch != null) {
//构建一个NioSocketChannel放进数组中
buf.add(new NioSocketChannel(this, childEventLoopGroup().next(), ch));
return 1;
}
} catch (Throwable t) {
logger.warn("Failed to create a new channel from an accepted socket.", t); try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
} return 0;
}
NioServerChannel的源码分析
public class NioServerSocketChannel extends AbstractNioMessageServerChannel
implements io.netty.channel.socket.ServerSocketChannel { private static final ChannelMetadata METADATA = new ChannelMetadata(false); private static final InternalLogger logger = InternalLoggerFactory.getInstance(NioServerSocketChannel.class); private static ServerSocketChannel newSocket() {
try {
return ServerSocketChannel.open();
} catch (IOException e) {
throw new ChannelException(
"Failed to open a server socket.", e);
}
} private final ServerSocketChannelConfig config; /**
* Create a new instance
*/
public NioServerSocketChannel(EventLoop eventLoop, EventLoopGroup childGroup) {
super(null, eventLoop, childGroup, newSocket(), SelectionKey.OP_ACCEPT);
config = new DefaultServerSocketChannelConfig(this, javaChannel().socket());
} @Override
public InetSocketAddress localAddress() {
return (InetSocketAddress) super.localAddress();
} @Override
public ChannelMetadata metadata() {
return METADATA;
} @Override
public ServerSocketChannelConfig config() {
return config;
} @Override
public boolean isActive() {
return javaChannel().socket().isBound();
} @Override
public InetSocketAddress remoteAddress() {
return null;
} @Override
protected ServerSocketChannel javaChannel() {
return (ServerSocketChannel) super.javaChannel();
} @Override
protected SocketAddress localAddress0() {
return javaChannel().socket().getLocalSocketAddress();
} @Override
protected void doBind(SocketAddress localAddress) throws Exception {
javaChannel().socket().bind(localAddress, config.getBacklog());
} @Override
protected void doClose() throws Exception {
javaChannel().close();
} @Override
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = javaChannel().accept(); try {
if (ch != null) {
buf.add(new NioSocketChannel(this, childEventLoopGroup().next(), ch));
return 1;
}
} catch (Throwable t) {
logger.warn("Failed to create a new channel from an accepted socket.", t); try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
} return 0;
} // Unnecessary stuff
@Override
protected boolean doConnect(
SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
throw new UnsupportedOperationException();
} @Override
protected void doFinishConnect() throws Exception {
throw new UnsupportedOperationException();
} @Override
protected SocketAddress remoteAddress0() {
return null;
} @Override
protected void doDisconnect() throws Exception {
throw new UnsupportedOperationException();
} @Override
protected boolean doWriteMessage(Object msg, ChannelOutboundBuffer in) throws Exception {
throw new UnsupportedOperationException();
}
}
可以看到 NioServerChannel 的主要都是 override 父类的方法,即是说大部分的逻辑都在父类 Abstract中进行了一层层的封装,给我们一个启发,好的类结构在
在一开始就已经设计好,最终的具体实现交由尾端实现。
总结
本文介绍了channel的主要功能作用。
参考资料
- 《Netty权威指南》
netty(五) channel的更多相关文章
- Netty 源码解析(二):Netty 的 Channel
本文首发于微信公众号[猿灯塔],转载引用请说明出处 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty源码解析(一):开始 当前:Netty 源码解析(二): Netty 的 Channel ...
- Netty之Channel*
Netty之Channel* 本文内容主要参考**<<Netty In Action>> ** 和Netty的文档和源码,偏笔记向. 先简略了解一下ChannelPipelin ...
- spark2.1源码分析3:spark-rpc如何实现将netty的Channel隐藏在inbox中
class TransportServer bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Overri ...
- spark-rpc是如何实现将netty的Channel隐藏在inbox中的
class TransportServer bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Overri ...
- Netty:Channel 建立后消息发送失败
1. 问题现象 Channel 建立后消息发送失败: ChannelFuture future = DeviceManager.getBootstrap().connect(); deviceChan ...
- Netty Associated -- Channel
A nexus to a network socket or a component which is capable of I/O operations such as read, write, c ...
- Netty的Channel
Channel是一个网络端口连接,或者是可以进行读,写,链接,绑定端口的组件的连接. Channel就是一个链接,它提供了如下的功能. 1:获取当前链接的状态 2:配置当前链接参数 3:进行read ...
- netty笔记-:Channel与ChannelHandlerContext执行write方法的区别
在netty中有我们一般有两种发送数据的方式,即使用ChannelHandlerContext或者Channel的write方法,这两种方法都能发送数据,那么其有什么区别呢.这儿引用netty文档 ...
- Netty:Channel
上一篇我们通过一个简单的Netty代码了解到了Netty中的核心组件,这一篇我们将围绕核心组件中的Channel来展开学习. Channel的简介 Channel代表着与网络套接字或者能够进行IO操作 ...
- 项目系统Netty的Channel和用户之间的关系绑定正确做法,以及Channel通道的安全性方案
前言 考虑一个功能业务,在web程序中向指定的某个用户进行实时通讯 在Web运用的Socket通讯功能中(如在线客服),为保证点对点通讯.而这个看似简单的根据用户寻到起channel通道实际会碰到不少 ...
随机推荐
- [UOJ228] 基础数据结构练习题 - 线段树
考虑到一个数开根号 \(loglog\) 次后就会变成1,设某个Node的势能为 \(loglog(maxv-minv)\) ,那么一次根号操作会使得势能下降 \(1\) ,一次加操作最多增加 \(l ...
- NPOI _导出exl(简单应用)
1. 导出exl表格,创建表格导出到客户端 public static MemoryStream Export_Table<T>(List<T> datalist) { Mem ...
- C++-怎样写程序(面向对象)
使用编程语言写好程序是有技巧的. 主要编程技术: 1. 编程风格 2. 算法 3. 数据结构 4. 设计模式 5. 开发方法 编程风格指的是编程的细节,比如变量名的选择方法.函数的写法等. 算法是解决 ...
- vs2015编译各种库
1.编译libcurl https://www.jianshu.com/p/f82d3d18da93
- 如何去官网上下载tomcat的linux版本
1.首先进入官网,进入如下位置 2.进入bin文件夹中下载: 原文链接:https://blog.csdn.net/zdb292034/article/details/82433072
- 使用NSIS制作可执行程序的安装包
使用NSIS制作可执行程序的安装包: 1,NSIS下载地址:https://pan.baidu.com/s/1GzzQNXgAlJPJWgjBzVwceA 下载完成之后解压缩,打开安装程序,默认安装即 ...
- Windows下Go安装&环境配置&编译运行
Go下载安装 官方Go下载站点:https://golang.google.cn/ 也可以选择:https://studygolang.com/dl 配置环境变量 常用环境变量 GOROOT GORO ...
- LOJ138 类欧几里得算法
类欧几里得算法 给出 \(T\) 组询问,每组用 \(n, a, b, c, k_1, k_2\) 来描述.对于每组询问,请你求出 \[ \sum_{x = 0} ^ {n} x ^ {k_1} {\ ...
- java_基础_注解
注解(annotation),不是注释(comment) 注解可以对程序做说明,这一点和注释一样但是,注解还可以被其他程序读取,这是注释所不具备的 内置注解:@Override(表示重写父类方法)—— ...
- PAT-链表-A1032 Sharing
题意:给出两条链表的首地址以及若干个节点的的地址.数据.下一个节点的地址,求两条链表的首个共用节点的地址.如果两条链表没有共用节点,则输出-1. 思路:使用静态链表,首先遍历一遍第一个链表并进行标记. ...