3.1 ChannelConfig

2017-02-05 09:49:48 11,647 1

在Netty中,每种Channel都有对应的配置,用ChannelConfig来表示,ChannelConfig是一个接口,每个特定的Channel实现类都有自己对应的ChannelConfig实现类,如:

1、NioSocketChannel的对应的配置类为NioSocketChannelConfig

2、NioServerSocketChannel的对应的配置类为NioServerSocketChannelConfig

ChannelConfig的部分类图继承关系如下:

Image.png

在Channel接口中定义了一个方法config(),用于获取特定通道实现的配置,子类需要实现这个接口。

public interface Channel extends AttributeMap, Comparable<Channel> {
...
ChannelConfig config();
...
}

通常Channel实例,在创建的时候,就会创建其对应的ChannelConfig实例。例如NioServerSocketChannel和NioSocketChannel都是在构造方法中创建了其对应的ChannelConfig实现。

NioServerSocketChannel

public class NioServerSocketChannel extends AbstractNioMessageChannel
                             implements io.netty.channel.socket.ServerSocketChannel {
...
private final ServerSocketChannelConfig config;
...
public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());//构造方法中创建NioServerSocketChannelConfig实例
}
...
@Override
public ServerSocketChannelConfig config() {//覆写config方法,返回ServerSocketChannelConfig实例
    return config;
}
...
}

NioSocketChannel

public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel {
...
private final SocketChannelConfig config;
...
public NioSocketChannel(Channel parent, SocketChannel socket) {//构造方法中创建SocketChannelConfig实例
    super(parent, socket);
    config = new NioSocketChannelConfig(this, socket.socket());
}
...
@Override
public SocketChannelConfig config() {//覆写config方法,返回SocketChannelConfig实例
    return config;
}
...
}

Netty提供了一个ChannelOption类,其定义了ChannelConfig支持的所有参数类型,可以认为ChannelConfig中用了一个Map来保存参数,Map的key是ChannelOption,ChannelConfig 定义了相关方法来获取和修改Map中的值。

  public interface ChannelConfig {
...
    Map<ChannelOption<?>, Object> getOptions();//获取所有参数
   boolean setOptions(Map<ChannelOption<?>, ?> options);//替换所有参数
   <T> T getOption(ChannelOption<T> option);//获取以某个ChannelOption为key的参数值
   <T> boolean setOption(ChannelOption<T> option, T value);//替换某个ChannelOption为key的参数值
....
}

当我们想修改一个Map中的参数时,例如我们希望NioSocketChannel在工作过程中,使用PooledByteBufAllocator来分配内存,则可以使用类似以下方式来设置

Channel ch = ...;
   SocketChannelConfig cfg = (SocketChannelConfig) ch.getConfig();
   cfg.setOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

特别的,针对每个ChannelOption选项,还提供了一个便捷的方法来设置参数,例如下面的代码与上述代码是等价的

Channel ch = ...;
   SocketChannelConfig cfg = (SocketChannelConfig) ch.getConfig();
   cfg.setAllocator(PooledByteBufAllocator.DEFAULT);

因为在ChannelConfig的默认实现DefaultChannelConfig中,setOption方法内部还是通过判断ChanneOption方法来选择对应的方法设置的。

io.netty.channel.DefaultChannelConfig#setOption

public class DefaultChannelConfig implements ChannelConfig {
....
public <T> boolean setOption(ChannelOption<T> option, T value) {
    validate(option, value);

    if (option == CONNECT_TIMEOUT_MILLIS) {//判断ChannelOption类型,再调用对应的方法
        setConnectTimeoutMillis((Integer) value);
    } else if (option == MAX_MESSAGES_PER_READ) {
        setMaxMessagesPerRead((Integer) value);
    } else if (option == WRITE_SPIN_COUNT) {
        setWriteSpinCount((Integer) value);
    } else if (option == ALLOCATOR) {
        setAllocator((ByteBufAllocator) value);
    } else if (option == RCVBUF_ALLOCATOR) {
        setRecvByteBufAllocator((RecvByteBufAllocator) value);
    } else if (option == AUTO_READ) {
        setAutoRead((Boolean) value);
    } else if (option == AUTO_CLOSE) {
        setAutoClose((Boolean) value);
    } else if (option == WRITE_BUFFER_HIGH_WATER_MARK) {
        setWriteBufferHighWaterMark((Integer) value);
    } else if (option == WRITE_BUFFER_LOW_WATER_MARK) {
        setWriteBufferLowWaterMark((Integer) value);
    } else if (option == MESSAGE_SIZE_ESTIMATOR) {
        setMessageSizeEstimator((MessageSizeEstimator) value);
    } else {//如果不是上述类型,返回false,表示不支持此ChannelOpiton
        return false;
    }
    return true;
}
....
}

从上述代码中,我们还可以注意到另外一点,ChannelConfig接口的默认实现DefaultChannelConfig只支持了部分ChannelOption类型,这是所有的Channel都支持的ChannelOpiton。

ChannelConfig的特定子类在DefaultChannelConfig的基础上,支持更多的ChannelOption 。

ChannelConfig支持的通用ChannelOption

  • ChannelOption.CONNECT_TIMEOUT_MILLIS

  • ChannelOption.WRITE_SPIN_COUNT

  • ChannelOption.ALLOCATOR

  • ChannelOption.AUTO_READ

  • ChannelOption.MAX_MESSAGES_PER_READ

  • ChannelOption.RCVBUF_ALLOCATOR

  • ChannelOption.ALLOCATOR

  • ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK

  • ChannelOption.WRITE_BUFFER_LOW_WATER_MARK

  • ChannelOption.MESSAGE_SIZE_ESTIMATOR

  • ChannelOption.AUTO_CLOSE

事实上这里的每一种ChannelOption,除了可以使用setOption方法来进行设置,在ChannelConfig接口中都为其设置了对应的快捷set/get方法,不再赘述。

SocketChannelConfig在ChannelConfig基础上额外支持的ChannelOption

  • ChannelOption.SO_KEEPALIVE

  • ChannelOption.SO_REUSEADDR

  • ChannelOption.SO_LINGER

  • ChannelOption.TCP_NODELAY

  • ChannelOption.SO_RCVBUF

  • ChannelOption.SO_SNDBUF

  • ChannelOption.IP_TOS

  • ChannelOption.ALLOW_HALF_CLOSURE

ServerSocketChannelConfig在ChannelConfig基础上额外支持的ChannelOption

  • ChannelOption.SO_REUSEADDR

  • ChannelOption.SO_RCVBUF

  • ChannelOption.SO_BACKLOG

细心的读者会发现,除了ChannelConfig接口中定义的公共ChannelOption,SocketChannelConfig 和ServerSocketChannelConfig 支持的ChannelOption基本上都是TCP连接参数相关的,基本上每一个都对应java.net.StandardSocketOptions 定义的一个标准TCP参数,下面逐一进行讲解。

ChannelOption讲解

ChannelOption.SO_KEEPALIVE-->StandardSocketOptions.SO_KEEPALIVE

是否启用心跳机制,默认false。

套接字本身是有一套心跳保活机制的,不过默认的设置并不像我们一厢情愿的那样有效。在双方TCP套接字建立连接后(即都进入ESTABLISHED状态)并且在两个小时左右上层没有任何数据传输的情况下,这套机制才会被激活。

很多人认为两个小时的时间设置得很不合理。为什么不设置成为10分钟,或者更短的时间?(可以通过SO_KEEPALIVE选项设置。)但是这样做其实并不被推荐。实际上这套机制只是操作系统底层使用的一个被动机制,原理上不应该被上层应用层使用。当系统关闭一个由KEEPALIVE机制检查出来的死连接时,是不会主动通知上层应用的,只有在调用相应的IO操作在返回值中检查出来。

在《UNIX网络编程第1卷》中也有详细的阐述:

SO_KEEPALIVE 保持连接检测对方主机是否崩溃,避免(服务器)永远阻塞于TCP连接的输入。设置该选项后,如果2小时内在此套接口的任一方向都没有数据交换,TCP就自动给对方 发一个保持存活探测分节(keepalive probe)。这是一个对方必须响应的TCP分节.它会导致以下三种情况:对方接收一切正常:以期望的ACK响应。2小时后,TCP将发出另一个探测分节。对方已崩溃且已重新启动:以RST响应。套接口的待处理错误被置为ECONNRESET,套接口本身则被关闭。对方无任何响应:源自berkeley的TCP发送另外8个探测分节,相隔75秒一个,试图得到一个响应。在发出第一个探测分节11分钟 15秒后若仍无响应就放弃。套接口的待处理错误被置为ETIMEOUT,套接口本身则被关闭。如ICMP错误是“host unreachable(主机不可达)”,说明对方主机并没有崩溃,但是不可达,这种情况下待处理错误被置为 EHOSTUNREACH。

因此,忘记SO_KEEPALIVE,在应用层自己写一套保活机制比较靠谱。


ChannelOption.SO_REUSEADDR-->StandardSocketOptions.SO_REUSEADDR

是否重用处于TIME_WAIT状态的地址。默认为false。

ChannelOption.SO_LINGER-->StandardSocketOptions.SO_LINGER

优雅地关闭套接字,或者立刻关闭。当调用closesocket关闭套接字时,SO_LINGER将决定系统如何处理残存在套接字发送队列中的数据。处理方式无非两种:丢弃或者将数据继续发送至对端,优雅关闭连接。事实上,SO_LINGER并不被推荐使用,大多数情况下我们推荐使用默认的关闭方式。SO_LINGER 以秒为单位,最大取值为65535,也就是说,指定时间内残余数据尚未发送完成,那么也立即关闭。

ChannelOption.SO_SNDBUF-->StandardSocketOptions.SO_SNDBUF

发送缓冲区的大小设置,默认为8K。

ChannelOption.SO_RCVBUF-->StandardSocketOptions.SO_RCVBUF

接收缓冲区大小设置,默认为8K。该属性既可以在ServerSocket实例中设置,也可以在Socket实例中设置。

ChannelOption.TCP_NODELAY-->StandardSocketOptions.TCP_NODELAY

是否一有数据就马上发送。在TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。这里就涉及到一个名为Nagle的算法,该算法的目的就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。

  TCP_NODELAY选项,就是用于启用或关于Nagle算法。如果要求高实时性,有数据发送时就马上发送,就将该选项设置为true关闭Nagle算法;如果要减少发送次数减少网络交互,就设置为false等累积一定大小后再发送。默认为false。

ChannelOption.IP_TOS-->StandardSocketOptions.IP_TOS

ChannelOption#SO_BACKLOG

BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。在Netty中,这个值默认是去读取文件/proc/sys/net/core/somaxconn的值,如果没有读到,默认取值为3072(参见:NetUtil.SOMAXCONN)。


理解了这些参数之后,还有一点需要注意的是,由于Netty只是对java网络编程API的一层封装,因此表面上NioSocketChannelConfig、NioServerSocketChannelConfig 包含的参数分别设置给了NioSocketChannel,NioServerSocketChannel。但事实上这些参数必须要设置给java原生的Socket对象才能生效。

以NioSocketChannelConfig为例,NioSocketChannel创建的时候,就会创建其对应的NioSocketChannelConfig实例。

public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel {
...
private final SocketChannelConfig config;
...
public NioSocketChannel(Channel parent, SocketChannel socket) {//构造方法中传入了java原生socket对象参数
    super(parent, socket);
    config = new NioSocketChannelConfig(this, socket.socket());
}
...
}

可以看到 在创建NioSocketChannelConfig实例的时候,传递了2个参数,一个是this,即当前NioSocketChannel,另外一个是通过SocketChannel的socket方法获得的原生socket。

当NioSocketChannelConfig相关设置ChannelOption的方法被调用时,则会直接将参数设置给对应的socket。

public class DefaultServerSocketChannelConfig extends DefaultChannelConfig
                                              implements ServerSocketChannelConfig {
....
protected final Socket javaSocket;
.....
@Override
public SocketChannelConfig setTcpNoDelay(boolean tcpNoDelay) {
    try {
        javaSocket.setTcpNoDelay(tcpNoDelay);//直接将参数设置给了对应的java Socket
    } catch (SocketException e) {
        throw new ChannelException(e);
    }
    return this;
}
.....
}


上一篇:3.0 Channel 下一篇:3.2 ChannelHander