代码存在缩减,只取核心类之间的关系如下
下面只关注Epoll和Poll拿到provider之后,开始执行openSelector,获取真正的selector。
file descriptor :unix设计哲学就是一切都是文件,它可能是一个网络连接、一个终端等等。它本身就是一个数值,在系统中会维护文件描述符和它对应文件的一个指针,从而找到对应的文件操作
IOUtil针对不同的操作系统有不同的实现,以solaris为例,它的实现在IOUtil.c中,主要实现即通过Linux pipe方法和Linux fcntl方法 (代码有删减)
int fd[2]; if (pipe(fd) < 0) // 获取读和写的文件符 if ((configureBlocking(fd[0], JNI_FALSE) < 0) //标注为非阻塞 || (configureBlocking(fd[1], JNI_FALSE) < 0)) return ((jlong) fd[0] << 32) | (jlong) fd[1]; //读的文件描述符放在高位,写的文件描述符放在低位复制代码configureBlocking本身的实现在IOUtil.c中
static int configureBlocking(int fd, jboolean blocking) //设置为非阻塞状态{ int flags = fcntl(fd, F_GETFL); int newflags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); return (flags == newflags) ? 0 : fcntl(fd, F_SETFL, newflags);}复制代码pipe实际是创建了一个进程间通信的单向数据管道,参数中的fd[0]表示管道读取端的结尾,fd[1]表示管道写端的结尾;fcntl则主要是根据第二个参数,如源码中的F_GETFL和F_SETFL,对第一个参数执行对应的操作;
epoll_create_func = (epoll_create_t) dlsym(RTLD_DEFAULT, "epoll_create"); epoll_ctl_func = (epoll_ctl_t) dlsym(RTLD_DEFAULT, "epoll_ctl"); epoll_wait_func = (epoll_wait_t) dlsym(RTLD_DEFAULT, "epoll_wait");复制代码
- epfd:通过Native方法去构建,对应的实现在EPollArrayWrapper.c中,方法为:Java_sun_nio_ch_EPollArrayWrapper_epollCreate,主要的实现逻辑是int epfd = (*epoll_create_func)(256);而epoll_create_func在Java_sun_nio_ch_EPollArrayWrapper_init执行的时候已经是执行了初始化,对应的是Linux epoll_create ,返回既是一个epoll实例,它实质也是一个文件描述符
- pollArray:一个用来存储从epoll_wait中得到结果的数组,它的大小为NUM_EPOLLEVENTS * SIZE_EPOLLEVENT,其中的NUM_EPOLLEVENTS则是去的文件描述符限制和8192相比的最小值Math.min(fdLimit(), 8192);详见Linux getrlimit,实质是AllocatedNativeObject
- initInterrupt:出了存储对应的文件描述符之外,还执行了epollCtl(epfd, EPOLL_CTL_ADD, fd0, EPOLLIN);,即把fd0注册到epfd上,将epfd上的EPOLLIN事件关联到fd0上,详见Linux epoll_ctl
pollArray:它的大小为(10+1)*SIZE_POLLFD(SIZE_POLLFD取值为8),实质是AllocatedNativeObject
NativeObject是用来操作本地内存的一个代理,所有的操作通过Unsafe来实现,它本身是一个单例2: 开启服务端socket的channel它还是会去获取系统级别的provider,由于已经在拿selector的时候初始化,不再新建。同样会通过PollSelectorProvider或者是EPollSelectorProvider来开启服务端的socket的channel,而二者的实现均是通过父类SelectorProviderImpl,创建一个ServerSocketChannelImpl实例
channel:代表与硬件、文件、网络socket或者是程序组件等能够进行一些I/O操作(读和写)的实体的连接
Closeable:是关闭与流相关的系统资源
AutoCloseable:从1.7开始的支持的语法糖try-with-resources结构,实现自动关闭资源
SelectableChannel:支持通过selector复用的Channel,提供对channel的注册,返回对应的SelectionKey,可以工作在阻塞(默认)和非阻塞模式下
NetworkChannel:对应网络socket的channel,提供将socket绑定到本机地址的bind方法fd是使用IOUtil.newFD创建,创建过程如下:
Net.scoket0 方法对应的实现为Net.c中的Java_sun_nio_ch_Net_socket0,从头文件的引入 #include <sys/socket.h> 可以看到,socket0的内部很多实现都依赖于操作系统本身,操作系统不一样,就会有不同的调用结果。关键实现如下
fd = socket(domain, type, 0);setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&arg,sizeof(arg))复制代码
- socket(family, type, protocol):其中family指的是要在解释名称时使用的地址格式(AF_INET6/AF_INET等),type指定的是通信的语义(SOCK_STREAM/SOCK_DGRAM等),protocol执行通信用的协议,0意味着使用默认的。它返回的就是socket file descriptor。详见Linux socketAPI [solaris 下存在两套实现,BSD风格socket库-3SOCKET和 GNU/Linux软件使用这个库 XNET ]
- setsockop:给文件描述符fd设置socket的选项,返回值小于0表示出了异常,详见Linux setsocketopt
在IOUtil.c中存在方法 Java_sun_nio_ch_IOUtil_setfdVal,它就是调用JNI的方法将获取的值存入到java对象FileDescriptor中取
FileDescriptor的实例是用来表示1个打开的文件,或者是一个打开的socket或者类似的字节源fdVal的赋值则是使用创建好的fd调用JNI中的(*env)->GetIntField(env, fdo, fd_fdID);实现
Socket类是两台机器之间通信的端点,端点(endpoint)指的是 服务IP和它的端口,它的实际操作还是由SocketImpl来实现。
SOCKS4(SOCKets缩写)是一个网络协议,它主要负责在防火墙上中继TCP会话,以便应用用户能够透过防火墙进行访问。它主要定义了两个操作:CONNECT和BIND。
- 需要CONNECT时,客户端发送一个CONNECT请求给SOCKS服务器,请求包含要连接的目的端口和目的主机等信息,SOCKS服务器会做一些服务权限的校验,验证成功SOCKS服务器建立与目标主机指定端口的连接(即应用服务器),然后发送反馈包给客户端,反馈包通过CD的值来标识CONNECT请求的结果,CONNECT成功,SOCKS就可以在两个方向上转发流量了
- BIND必须发生在CONNECT之后,它实际包括一系列的步骤:1 获取socket;2 拿到scoket对应的端口和ip地址;3 开始监听,准备接收来自应用服务器的调用 4:使用主连接通知应用服务器它需要连接的IP地址和端口 5:接收一个来自应用服务器的连接
SOCKS5相对于SOCKS4做了功能扩展,支持UDP、IPV6、鉴定的支持4:绑定服务器和它的端口ServerSocketChannelImpl的bind方法。
bind对于windows系统和linux系统有不同的实现,以Linux为例,它实际执行的就是Linux bind,所做的操作就是把指定的地址(SocketAddress)分配给socket文件描述符,对于Hello world的实现来说就是它的字段fd
合法的操作为SelectionKey.OP_READ、SelectionKey.OP_WRITE、SelectionKey.OP_CONNECT6:从selector获取任何已经注册好并发生的事件
这里新建的SocketChannelImpl,而之前使用的是ServerSocketChannelImpl。区别在于 SocketChannelImpl支持读写数据,而ServerSocketChannelImpl则更多的用于等待连接的到来,充当服务端接下来,获取的socket方式同第3步中新建socket
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) | 黑马程序员IT技术论坛 X3.2 |