1、网络通信基础
网络通信就是客户端和服务端发送和接收数据的过程
计算机主机通信 |
---|
一个客户端要发起一次网络通信,必须先知道服务端的IP地址,然后 “网络基础设施” 通过 IP 地址将客户端发送的信息传递到 IP 地址对应的主机上。
通过 IP 可以找到主机,但将消息发送到主机中的具体应用程序需要通过端口号
2、JavaNIO 网络通信
上面提到的概念,在Java中都有其对应的抽象
比如:
1、InetSocketAddress
抽象了IP、端口号;
2、SocketChannel
抽象了 通信端点;
3、ServerSocketChannel
抽象了服务端功能,即在服务端创建 SocketChannel
与客户端进行通信;
Java中除了上述的比较几个抽象,其实还有一些内部处理部件的抽象,比如 Selector
、SelectionKey
…
2.1 服务端初始化
⭐️ ⭐️ 为了方便,下文很多地方会采用简写:
ssc
表示 ServerSocketChannel
sel
表示 Selector
sc
表示 SocketChannel
进行网络通信首先需要启动服务端,这样才能来接收客户端的连接请求、建立连接并进行通信。
1、创建 ServerSocketChannel
创建 ServerSocketChannel
来监听指定的通信端口
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(9000));
2、创建 Selector(选择器)
创建 Selector
(选择器)用来选择就绪的请求
Selector sel = Selector.open();
3、向 Selector 注册 ServerSocketChannel
调用 ServerSocketChannel.register(Selector,SelectionKey.OP_ACCEPT)
方法将 ServerSocketChannel
注册进 Selector
,注册方法的返回结果是SelectionKey
类型。
ssc.configureBlocking(false);// 设置为非阻塞模式
SelectionKey sk = ssc.register(sel, SelectionKey.OP_ACCEPT);
sk
中持有 ssc
和 sel
的引用,并且这个 sk
关注的操作是 OP_ACCEPT
从语义上看,是将Selector 注册到 ServerSocketChannel 中,实际上在 register 方法的源码中是将 二者封装成 SelectionKey 并放入 Selector中的 Set 中。
下图描述了初始化的过程:
初始化的过程 |
---|
注:有关Selector等内部原理的代码,篇幅所限,大家自行查看源码
我们这里提一下JavaNIO网络通信的基础知识。
熟悉Java基础知识的同学应该知道,Java API里有很多native方法,这些native方法使用 c语言来实现的,主要是因为OS大多采用C语言来写的,如果Java需要操作系统提供的功能支持,这就需要使用native方法,下图是Java网络应用 与 OS、硬件的交互示意图
Java应用 、OS、硬件的交互示意图 |
---|
网络通信需要OS网络通信相关的系统调用提供支持,比如绑定端口、监听端口、选择就绪请求等…
🐷🐷:
Java中为什么多线程和网络相关的内容比较难学,主要就是因为这部分内容涉及了OS底层提供的功能,如果对这些底层原理不了解,学起来确实很吃力
2.2 处理客户端连接请求
直接看下图,图中描述了服务端处理连接请求的过程:
处理连接请求 |
---|
1、客户端发送连接请求
客户端发送连接请求(192.168.211.111 + 9000),网络基础设置根据 192.168.211.111 找到主机,根据端口号9000找到应用程序
2、服务端处理连接请求
此时 ssc
正在监听着 9000 端口的连接请求,此时调用 ssc 对应的 sel 的 select 方法就会返回就绪请求的集合 Set<SelectionKey>
,这个 Set 中就有 “初始化”阶段注册的 sk
遍历Set ,拿到 sk
,然后获取ssc , 调用 ssc 的 accept 方法 创建 sc ;
最后向 sel 注册 sc 并设置关注的操作 ( interestOps ) “OP_READ” ;
示例代码如下:
ssc = (ServerSocketChannel) sk.channel();
SocketChannel sc = ssc.accept(); // 接受client連線請求
if (sc != null) {
sc.configureBlocking(false);
SelectionKey scsk = sc.register(selector, SelectionKey.OP_READ);
}
大家注意到,Selector 中有两种 SelectionKey,一种是 ssc + sel
,另一种是 sc + sel
。
2.3 处理客户端发送的消息
连接建立后,客户端就可以发送消息了,从上图可以看出服务端建立的 sc
也做好了准备,即 sc
对应的 sk
的状态为 “OP_READ”
从图中可以看出,与客户端建立连接后服务端有个 sc
与之对应,这个 sc
和 sel
封装成了 sk
放入sel
中,sel
调用 select
方法时会返回这个 sk
(其实是返回所有准备就绪的所有sk ,我们这里目前只有一个客户端,所以只有一个准备就绪的sk)
拿到 sk 后,就可以进行读数据和业务处理了,见下图
3、JavaNIO 内存模型
从上文的描述,我们来总结一下Java NIO的内存模型
1、ssc 用来监听特定端口上的连接、读、写等网络操作;
2、连接操作被抽象成 ServerSocketChannel
+ Selector
组成的 SelectionKey
,并存储在 Selector
的 Set 属性中,这个 sk
关注的操作一直都是 OP_ACCEPT(图中标注为 A);
3、读写操作被抽象成 SocketChannel
+ Selector
组成的 SelectionKey
,也存储在 Selector
的 Set 属性中。,这个 sk
关注的操作在 OP_READ(图中标注为 R)和 OP_WRITE 之间切换;
其实大家就记住下面这个图,NIO的操作基本都是基于Selector
中的 SelectionKey
进行的