03.13 Scalable TCP一个与众不同的网络编程

前几天我给大家分享了一些JAVA网络编程的知识,从基本的JAVA网络知识开始,我们学习了结合多线程管理的Socket编程,掌握了关于系统内核中的RecBuffer和SendBuffer的概念模型。然后我们开始了NIO编程模型的最佳搭档,Channel和Buffer,以及Channel的读写操作和Buffer的get、put的关系。因为我们有了前面知识的铺垫,所以今天我打算给大家分享一个不一样的网络编程模型,使用Selector实现多路复用网络编程。(实际上是JAVA NIO系列第五篇文章)

今天分享目录如下:

  • 1. TCP上的Channel有哪些

  • 2. TCP上Channel的读写操作

  • 3. 使用Selector实现TCP多路复用网络编程

(还是老话,如果对Selector的op_read、op_write很熟悉,就不要往下看了,去看对你更有帮助的文章)

1. TCP上的Channel

Scalable TCP一个与众不同的网络编程

先看下TCP的Channel继承图,注意SelectableChannel

1.1 Server Socket上的Channel:ServerSocketChannel

class ServerSocketChannel{

static ServerSocketChannel open();

ServerSocket socket();

SocketChannel accept();

}

从上面的API中我们可以看出,ServerSocketChannel是使用open这个静态方法中获得的。并且我们可以中该Channel中获得ServerSocket对象。

ServerSocketChannel channel = ServerSocketChannel.open();

ServerSocket serverSocket = channel.socket();

这里需要注意:

  • 上面代码ServerSocket没有绑定地址,需要绑定之后才可以使用,例如使用serverSocket.bind(port)

  • 另外我们要使用ServerSocketChannel的accept来获得SocketChannel,而不是使用ServerSocket的accept获得Socket。

  • 如果serverSocketChannel是non-blocking的,那么accept可能返回null,因为没有连接进来。

  • 使用accept获得的channel不是null,那么该channel默认是Blocking模式,需要手动指定成为Non-Blocking

SelectableChannel configureBlocking(boolean block);

1.2 客户端连接Socket的Channel:SocketChannel

class SocketChannel{

static SocketChannel open();

static SocketChannel open(SocketAddress address);

Socket socket();

}

从上面的API看到,大概和ServerSocketChannel类似,从Channel中获得的socket仍然没有连接到服务,需要使用带有参数的open方法才能打开连接。同样这里获得的channel默认是Blocking模式,需要手动指定成为Non-Blocking。

例如:我们指定ip和端口

SocketChannel channel = SocketChannel.open(new InetSocketAddress(ip,port));

2. Channel的操作

注意channel的Blocking模式,可以使用API设置

SelectableChannel configureBlocking(boolean block);

Channel的Read操作


在阻塞式编程中,TCP Channel的读操作会阻塞,一直等到有数据可用。而在非阻塞编程中,读操作不会被阻塞,如果recBuffer中有数据,则read返回数据,如果recBuffer空,就不会有数据返回。这里需要注意的地方是非阻塞编程读过程中,如果对方关闭了连接,或者本地关闭了read操作,会有异常抛出,否则read会立刻返回所读数据数,尽管数据可能是0。

代码示例稍后。

Channel的Write操作

在阻塞编程中,和读一样,写操作一直等待sendBuffer中有数据。在非阻塞编程中,写不会被阻塞。这里和读是一样的,如果没有异常抛出的话,这里的写操作会立即返回写的数据数,这里可以是0。什么时候抛出异常?连接被对端关闭了,或者write操作被关闭都会抛异常的。

代码示例稍后。

3. 多路复用网络编程模型

Scalable TCP一个与众不同的网络编程

终于到了今天重点的地方,希望读者慢慢看,吸收里面的知识点。

除了上面的读、写操作之外,在多路复用编程中还有两个准备就绪类的操作,一个是准备好了Accept,另一个是准备连接已就绪。完整的操作是:

  • OP_ACCEPT:只有ServerSocketChannel才可以有这个操作,如果有连接进来,这里就会有返回。

  • OP_CONNECT:SocketChannel在连接过程中的操作,已经出于连接完成的状态,下一步是finishConnect。

  • OP_READ:只有连接完成的SocketChannel才有读操作。返回数据见上面,除非有异常,否则就会有数据个数返回,即使数据返回0.

  • OP_WRITE:只有连接完成的SocketChannel才有写操作。除非有异常,否则返回写出的数据数,或0.

稍后在深入解释这4个操作。我们先看代码,混个面熟先。

如果下面代码你会了,那么JAVA的网络编程你就掌握了。

Scalable TCP一个与众不同的网络编程

片段01

Scalable TCP一个与众不同的网络编程

片段02

代码亮点,关键点:

  • 非阻塞配置。(nonblocking)

  • Selector.select方法的使用。(timeout)

  • 使用key的attachement管理每个连接的ByteBuffer。(attach)

好了,我们回到上面的4个操作上,op_accept,connect,read,wirte。实际上,上面代码中select()操作,返回的只有两种事件,可读的事件和可写的事件两种。

可读事件:有数据在recBuffer中或者accept方法有可用连接进来了,换句话说,OP_READ和OP_ACCETP是一个事件。

可写事件:sendBuffer中有数据了,或者客户端连接完成了。换句话说,OP_CONNECT 和OP_WRITE是一回事。

我们定义accept和read事件在一起为可读事件没有什么问题,因accept在server端可以时,read还不可用,所有监听是分开的,不会冲突。但是connect事件和write事件为可写事件不能同时监听,因为在client socket中,这俩可以是同时存在的,会出现歧义。因为在一个准备就绪的Socket中,op_write事件几乎是一直可用的,所有除非有数据明确了,需要写出了,我们才可以在socket中把write事件添加到select流程中,否则NIO调度线程总是选择报告可写事件,CPU压力很大,因此我们才规定,除非有数据可以写,我们不要订阅可写事件。

总结:

今天只是网络编程基础知识的分享,所以并没有结合实际业务需求,明天我打算结合实际业务需求,实现一个断点续传的Socket编程模式。

感谢你的阅读,希望本文的分享能给你带来提升,有问题在下方评论中讨论,我会及时反馈。


分享到:


相關文章: