网络通信基本常识
socket
Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口,一般由操作 系统提供。在设计模式中,Socket 其实就是一个门面模式,它把复杂的 TCP/IP 协议处理和 通信缓存管理等等都隐藏在 Socket 接口后面,对用户来说,使用一组简单的接口就能进行网络应用编程,让Socket去组织数据,以符合指定的协议。主机 A 的应用程序要能和主机 B 的 应用程序通信,必须通过 Socket 建立连接。客户端连接上一个服务端,就会在客户端中产生一个socket 接口实例,服务端每接受一个客户端连接,就会产生一个 socket 接口实例和客户端的 socket 进行通信,有多个客户端连接自然就有多个 socket 接口实例。
短连接
传统 HTTP 是无状态的,浏览器和服务器每进行一次 HTTP 操作,就建立一次连接,但任务结束就中断连接。
也可以这样说:短连接是指 SOCKET 连接后发送后接收完数据后马上断开连接。
长连接
长连接指建立 SOCKET 连接后不管是否使用都保持连接,http1.1使用了长连接的传输方式。
短连接与长连接的选择
长连接多用于操作频繁,点对点的通讯。每个 TCP 连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都 不断开,下次处理时直接发送数据包就OK了,不用建立 TCP 连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socke 创建也是对资源的浪费。
而像WEB网站的http服务按照Http协议规范早期一般都用短链接,因为长连接对于 服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源。但是现在的 Http 协议Http1.1,尤其是 Http2、Http3 已经开始向长连接演化。
网络通信通用常识
在通信编程里,我们关注的其实也就是三个事情:连接(客户端连接服务器,服务器等 待和接收连接)、读网络数据、写网络数据,所有模式的通信编程都是围绕着这三件事情进行的。服务端提供 IP 和监听端口,客户端通过连接操作想服务端监听的地址发起连接请求,通过三次握手连接,如果连接成功建立,双方就可以通过套接字进行通信。
BIO
BIO,意为 Blocking I/O,即阻塞的 I/O。 阻塞主要体现在两个方面:
- 若一个服务器启动就绪,那么主线程就一直在等待着客户端的连接,这个等待过程中主线程就一直在阻塞。
- 在连接建立之后,在读取到 socket 信息之前,线程也是一直在等待,一直处于阻塞的状态下的。
BIO 模型中 ServerSocket 负责绑定 IP 地址,启动监听端口,等待客户连接;客户端 Socket 类的实例发起连接操作,ServerSocket 接受连接后产生一个新的服务端 socket 实例负责和客户端 socket 实例通过输入和输出流进行通信。
传统 BIO 通信模型,通常由一个独立的 Acceptor 线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成后,通过输出流返回应答给客户端,线程销毁。即典型的一请求一应答模型,同时数据的读取写入也必须阻塞在一个线程内等待其完成。当并发量较大时,服务端的线程个数和客户端并发访问数呈 1:1 的正比关系,系统性能将会急剧下降。
NIO
NIO 库是在 JDK 1.4 中引入的。NIO 弥补了原来的 BIO 的不足,它在标准Java 代码中提供了高速的、面向块的 I/O。NIO 被称为 no-blocking io 或者 new io 。
NIO 之 Reactor(反应器) 模式
“反应”即“倒置”,“控制逆转”,具体事件处理程序不调用反应器,而向反应器注册一个事件处理器,表示自己对某些事件感兴趣,有时间来了,具体事件处理程序通过事件 处理器对某个指定的事件发生做出反应;这种控制逆转又称为“好莱坞法则”(不要调用我, 让我来调用你)。
例如:A去美发,前台小姐姐负责服务,A只对王托尼的手艺满意但是王托尼现在没有上班,于是A就告诉前台小姐姐等王托尼上班了通知我。等A接到通知后呢做出了反应将王托尼就占住了。
A就是具体事件处理程序,前台小姐姐就是所谓的反应器,王托尼上班了就是事件,A只对这个事件感兴趣,其他,比如 李托尼上班了也是事件,但是A不感兴趣。
前台小姐姐不仅仅服务A这个人,他还可以同时服务路人B、C……..,每个人所感兴 趣的事件是不一样的,前台小姐姐会根据每个人感兴趣的事件通知对应的每个人。
NIO 三大核心组件
NIO 有三大核心组件:Selector 选择器、Channel 管道、buffer 缓冲区。
Selector
Java NIO 的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器(Selectors),然后使用一个单独的线程来操作这个选择器,进而“选择”通道: 这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个 单独的线程很容易来管理多个通道。
应用程序将向 Selector 对象注册需要它关注的 Channel,以及具体的某一个 Channel 会 对哪些 IO 事件感兴趣。Selector 中也会维护一个“已经注册的 Channel”的容器。
Channel
通道,被建立的一个应用程序和操作系统交互事件、传递内容的渠道(注意是连接到操作系统)。那么既然是和操作系统进行内容的传递,那么说明应用程序可以通过通道读取数 据,也可以通过通道向操作系统写数据,而且可以同时进行读写。
• 所有被 Selector(选择器)注册的通道,只能是继承了 SelectableChannel 类的子类。
• ServerSocketChannel:应用服务器程序的监听通道。只有通过这个通道,应用程序才 能向操作系统注册支持“多路复用 IO”的端口监听。同时支持 UDP 协议和 TCP 协议。
• ScoketChannel:TCP Socket 套接字的监听通道,一个 Socket 套接字对应了一个客户 端 IP:端口 到 服务器 IP:端口的通信连接。 通道中的数据总是要先读到一个 Buffer,或者总是要从一个 Buffer 中写入。
buffer
我们前面说过 JDK NIO 是面向缓冲的。Buffer 就是这个缓冲,用于和 NIO 通道进行交互。 数据是从通道读入缓冲区,从缓冲区写入到通道中的。以写为例,应用程序都是将数据写入 缓冲,再通过通道把缓冲的数据发送出去,读也是一样,数据总是先从通道读到缓冲,应用 程序再读缓冲的数据。 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存(其实就是数组)。
这块内存被包装成 NIO Buffer 对象,并提供了一组方法,用来方便的访问该块内存。