Socket网络编程笔记

1.1 Socket基本概念

Socket又称”套接字”,应用程序通常通过”套接字”向网络发出请求或应答网络请求。

SocketServerSocket类库位于java.net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。

套接字之间的连接过程可以分四步:服务器监听,客户端请求服务器,服务器确认,客户端确认,进行通信。

  1. 服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
  2. 客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后,就向服务器端套接字提出连接请求。
  3. 服务器端连接确认:是指当服务器端套接字监听到或接收到客户端套接字的连接请求,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端。
  4. 客户端连接确认:一旦客户端确认了此描述,连接就建立好了。双方开始进通信。而服务器端套接字继续处于监听状态,继续接收其它客户端套接字的连接请求。

1.2 BIO和NIO

IO(BIO)和NIO的区别:其本质就是阻塞和非阻塞的区别

  • 阻塞概念:应用程序在获取网络数据时,如果网络传输数据很慢,那么程序就一直等着,直到传输完毕为止。
  • 非阻塞概念:应用程序直接可以获取已经准备就绪好的数据,无需等待。
  • BIO为同步阻塞形式,NIO为同步非阻塞形式。NIO并没有实现异步,在JDK1.7之后,升级了NIO库包,支持异步非阻塞通信模型即NIO2.0(AIO)

同步和异步:同步和异步一般是面向操作系统与应用程序对IO操作的层面上来区别的

  • 同步时,应用程序会直接参与IO读写操作,并且应用程序会直接阻塞到某一个方法上,直到数据准备就绪;或采用轮询的策略实时检查数据的就绪状态,如果就绪则获取数据。
  • 异步时,则所有的IO读写操作交给操作系统处理,与应用程序没有直接关系,程序不需要关心IO读写,当操作系统完成了IO读写操作时,会给应用程序发送通知,应用程序直接拿走数据即可。
  • 同步说的是Server服务器端的执行方式
  • 阻塞说的是具体的技术,接收数据的方式、状态(IO、NIO)

1.3 传统的BIO编程

网络编程的基本模型是Client/Server模型,也就是两个进程直接进行相互通信,其中服务端提供配置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接成功,则双方即可以进行通信(网络套接字Socket)。

示例

  • BioServerBioClient

1.4 伪异步IO

采用线程池和任务队列可以实现一种伪异步的IO通信框架。其实,就是将客户端的Socket封装成一个task任务(实现runnable接口的类)然后,投递到线程池中去,配置相应的队列进行实现。

https://s0.wailian.download/2019/01/31/bioAsyn-min.pngbioAsyn-min

示例

  • BioPoolServerBioPoolClient

2.1 NIO编程介绍

先澄清一个概念,有的人叫NIO为new IO,有的人把NIO叫做Non-block IO,这里还是习惯说后者,即非阻塞IO

  • Buffer(缓冲区)、Channel(管道、通道)、Selector(选择器、多路复用器)
  • NIO的本质就是避免原始的TCP建立连接使用3次握手的操作,减少连接的开销。

2.2 Buffer

Buffer是一个对象,包含一些要写入或要读取的数据。在NIO类库中加入Buffer对象,体现了新库与原IO的一个重要的区别。在面向流的IO中,可以将数据直接写入或读取到Stream对象中。

在NIO库中,所有数据都是用缓冲区处理的(读写)。缓冲区实质上是一个数组,通常是一个字节数组(ByteBuffer),也可以使用其它类型的数组。这个数组为缓冲区提供了数据的访问读写等操作属性,如位置、容量、上限等概念,参考api文档。

Buffer类型:最常用的就是ByteBuffer,实际上每一种java基本类型都对应了一种缓存区(除了Boolean类型)

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

示例

  • BufferTest

2.3 Channel

通道(Channel),就像自来水管道一样,网络数据通过Channel读取和写入,通道与流不同之处在于通道是双向的,而流只是一个方向上移动(一个流必须是InputStreamOutputStream的子类),而通道可以用于读、写或同时进行,最关键的是可以与多路复用器结合起来,有多种的状态位,方便多路复用器去识别。

事实上通道分为两大类:一类是网络读写的(SelectableChannel),一类是用于文件操作的(FileChannel),使用的SocketChannelServerSockerChannel都是SelectableChannel的子类

2.4 Selector

多路复用器(Selector)是NIO编程的基础,非常重要。多路复用器提供选择已经就绪的任务的能力。

简单说,就是Selector会不断地轮询注册在其上的通道(Channel),如果某个通道发生了读写操作,这个通道就处于就绪状态,会被Selector轮询出来,然后,通过SelectionKey可以取得就绪的Channel集合,从而进行后续的IO操作。

一个多路复用器(Selector)可以负责成千上万Channel通道,没有上限,这也是JDK使用了epoll代替了传统的select实现,获得连接句柄没有限制。这也就意味着只要一个线程负责Selector的轮询,就可以接入成千上万个客户端,这是JDK NIO库的巨大进步。

Selector线程就类似一个管理者(Master),管理了成千上万个管道,然后,轮询那个管道的数据已经准备好,通知cpu执行IO的读取或写入操作。

Selector模式:当IO事件(管道)注册到选择器以后,Selector会分配给每个管道一个key值,相当于标签。Selector选择器是以轮询的方式进行查找注册的所有IO事件(管道),当IO事件(管道)准备就绪后,select就会识别,会通过key值来找到相应的管道,进行相关的数据处理操作(从管道里读或写数据,写到数据缓冲区中)。

每个管道都会对选择器进行注册不同的事件状态,以便选择器查找。

  • SelectionKey.OP_CONNECT
  • SelectionKey.OP_ACCEPT
  • SelectionKey.OP_READ
  • SelectionKey.OP_WRITE

示例

  • NioServerNioClient

2.5 Tips

客户端和服务器端双向通信

  • NioEchoServerNioEchoClient

3.1 AIO

AIO编程,在NIO基础之上引入了异步通道的概念,并提供了异步文件和异步套接字通道的实现,从而在真正意义上实现了异步非阻塞,NIO只是非阻塞而并非异步。而AIO不需要通过多路复用器对注册的通道进行轮询操作即可实现异步读写,从而简化了NIO编程模型。也可以称之为NIO2.0,这种模式才真正的属于异步非阻塞的模型。

  • AsynchronousServerScoketChannel
  • AsynchronousScoketChanel

示例

  • AioServerAioClient

References

  • 尚学堂互联网架构师课程