netty源码之channel初始化
入口说明
ChannelFuture future = server.bind(port).sync();
AbstractBootstrap#bind(int inetPort)
AbstractBootstrap#bind(SocketAddress localAddress)
AbstractBootstrap#doBind(final SocketAddress localAddress)
AbstractBootstrap#initAndRegister(){
//本文主要分析这一行代码
channel = channelFactory.newChannel();
}
channel初始化
channel = channelFactory.newChannel();这行代码实际是反射调用NioServerSocketChannel的默认构造,如下
//provider
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
//无参
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
//有参
public NioServerSocketChannel(ServerSocketChannel channel) {
//只接受accept事件,设置非阻塞
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
//核心,jni调用scoket0
private static ServerSocketChannel newSocket(SelectorProvider provider) {
//https://github.com/netty/netty/issues/2308,issue主要就是说SelectorProvider.provider()要加锁,所以缓存起来
//核心就是这行代码
return provider.openServerSocketChannel();
}
provider.openServerSocketChannel();核心代码会执行到如下代码
ServerSocketChannelImpl(SelectorProvider sp) throws IOException {
//传到父类
super(sp);
//核心代码
this.fd = Net.serverSocket(true);
//jni获取文件描述符的值
this.fdVal = IOUtil.fdVal(fd);
//设置状态正在使用
this.state = ST_INUSE;
}
//核心代码调用这里
static FileDescriptor serverSocket(boolean stream) {
//socket0返回一个int类型的文件描述符,newFD把他包装成一个java文件描述符对象
//在linux上一切皆文件,scoket也不例外
return IOUtil.newFD(socket0(isIPv6Available(), stream, true, fastLoopback));
}
// Due to oddities SO_REUSEADDR on windows reuse is ignored
//ipv6, tcp|udp, 是否复用处于四次挥手TimeWait状态的连接
//fastLoopback是 Windows 特有的一项优化,可以在内核 IP 解析前拦截本地环回地址(127.0.0.1)的调用,
//加快本地调用速度,这个值在 Linux 的实现中已被忽略
private static native int socket0(boolean preferIPv6, boolean stream, boolean reuse,
boolean fastLoopback);
下面我们看看socket0的c语言实现
JNIEXPORT int JNICALL
//前两个参数是jni调用必须的参数,利用这两个参数可以实现c代码调用java代码,或者设置java类的某个属性
//后四个参数解释过了
Java_sun_nio_ch_Net_socket0(JNIEnv *env, jclass cl, jboolean preferIPv6,
jboolean stream, jboolean reuse, jboolean ignored)
{
//最后返回的文件描述符
int fd;
//tcp还是udp
int type = (stream ? SOCK_STREAM : SOCK_DGRAM);
//宏AF_INET6存在的话
#ifdef AF_INET6
//ipv6还是ipv4
int domain = (ipv6_available() && preferIPv6) ? AF_INET6 : AF_INET;
//否则
#else
int domain = AF_INET;
#endif //if结束
//指定网络层和传输层协议,第三个参数固定0,具体为什么可以执行man命令查阅linux手册
fd = socket(domain, type, 0);
//文件描述符不会小于0,通常0是标准输入,1是标准输出,2是标准出错
if (fd < 0) {
return handleSocketError(env, errno);
}
//宏AF_INET6存在的话
#ifdef AF_INET6
/* Disable IPV6_V6ONLY to ensure dual-socket support */
if (domain == AF_INET6) {
int arg = 0;
//setsockopt设置属性
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg,
sizeof(int)) < 0) {
JNU_ThrowByNameWithLastError(env,
JNU_JAVANETPKG "SocketException",
"Unable to set IPV6_V6ONLY");
close(fd);
return -1;
}
}
#endif
//复用TimeWait,设置SO_REUSEADDR 属性
if (reuse) {
int arg = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&arg,
sizeof(arg)) < 0) {
JNU_ThrowByNameWithLastError(env,
JNU_JAVANETPKG "SocketException",
"Unable to set SO_REUSEADDR");
close(fd);
return -1;
}
}
return fd;
}
如若不理解socket的话可以借鉴笔者并发编程里关于c语言和io模型的文章