Tars-Java网络编程源码分析(上)

上一篇 / 下一篇  2023-03-17 16:30:22

  一、Tars框架基本介绍
  Tars是腾讯开源的支持多语言的高性能RPC框架,起源于腾讯内部2008年至今一直使用的统一应用框架TAF(Total Application Framework),目前支持C++、Java、PHP、Nodejs、Go语言。
  该框架为用户提供了涉及到开发、运维、以及测试的一整套解决方案,帮助一个产品或者服务快速开发、部署、测试、上线。它集可扩展协议编解码、高性能RPC通信框架、名字路由与发现、发布监控、日志统计、配置管理等于一体,通过它可以快速用微服务的方式构建自己的稳定可靠的分布式应用,并实现完整有效的服务治理。
  vivo推送平台也深度使用了该框架,部署服务节点超过一千个,经过线上每日一百多亿消息推送量的考验。
  Tars-java 最新稳定版1.7.2以及之前的版本都使用Java NIO进行网络编程;本文将分别详细介绍java NIO的原理和Tars 使用NIO进行网络编程的细节。
  二、Java NIO原理介绍
  从1.4版本开始,Java提供了一种新的IO处理方式:NIO (New IO 或 Non-blocking IO)  是一个可以替代标准Java IO 的API,它是面向缓冲区而不是字节流,它是非阻塞的,支持IO多路复用。
  2.1 Channels (通道) and Buffers (缓冲区)
  标准的IO基于字节流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作。数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中,下图是一个完整流程。
  Channel类型:
  1. 支持文件读写数据的FileChannel
  2. 能通过UDP读写网络中的数据的DatagramChannel 
  3. 能通过TCP读写网络数据的SocketChannel
  4. 可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个SocketChannel的ServerSocketChannel 。
  SocketChannel:
  ·打开 SocketChannel:SocketChannel socketChannel = SocketChannel.open();
  · 关闭 SocketChannel:socketChannel.close();
  · 从Channel中读取的数据放到Buffer: int bytesRead = inChannel.read(buf);
  · 将Buffer中的数据写到Channel: int bytesWritten = inChannel.write(buf);
  ServerSocketChannel:
  通过 ServerSocketChannel.accept() 方法监听新进来的连接,当accept()方法返回的时候,它返回一个包含新进来的连接的SocketChannel,因此accept()方法会一直阻塞到有新连接到达。
  通常不会仅仅只监听一个连接,在while循环中调用 accept()方法. 如下面的例子:
  代码1:
  while(true){
      SocketChannel socketChannel = serverSocketChannel.accept();
       //do something with socketChannel...
  }
  ServerSocketChannel可以设置成非阻塞模式。在非阻塞模式下,accept() 方法会立刻返回,如果还没有新进来的连接,返回的将是null。因此,需要检查返回的SocketChannel是否是null。
  代码2:
  ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  serverSocketChannel.socket().bind(new InetSocketAddress(8888));
  serverSocketChannel.configureBlocking(false);
  while(true){
      SocketChannel socketChannel = serverSocketChannel.accept();
      if(socketChannel != null){
          //do something with socketChannel...
      }
  }
  Buffer类型:
  ·ByteBuffer
  · CharBuffer
  · DoubleBuffer
  · FloatBuffer
  · IntBuffer
  · LongBuffer
  · ShortBuffer
  Buffer的分配:
  ByteBuffer buf = ByteBuffer.allocate(2048);
  Buffer的读写:
  一般是以下四个步骤:
  1. 写入数据到Buffer,最大写入量是capacity,写模式下limit值即为capacity值,position即为写到的位置。
  2. 调用flip()方法将Buffer从写模式切换到读模式,此时position移动到开始位置0,limit移动到position的位置。
  3. 从Buffer中读取数据,在读模式下可以读取之前写入到buffer的所有数据,即为limit位置。
  4. 调用clear()方法或者compact()方法。clear()方法将position设为0,limit被设置成capacity的值。compact()方法将所有未读的数据拷贝到Buffer起始处,然后将position设到最后一个未读元素后面。
  mark() 与 reset()方法
  通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position,之后可以通过调用Buffer.reset()方法恢复到这个position。
  duplicate()
  此方法返回承载先前字节缓冲区内容的新字节缓冲区。
  remaining()
  limit 减去 position的值。
  2.2 Selector(选择器)
  Java NIO引入了选择器的概念,选择器用于监听多个通道的事件。单个的线程可以监听多个数据通道。要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件。
线程使用一个selector处理多个channel
  代码3:
  channel.configureBlocking(false);
  SelectionKey key = channel.register(selector,Selectionkey.OP_READ);
  注意register()方法的第二个参数,这是一个监听的集合,即在通过Selector监听Channel时关注什么事件集合。
  SelectionKey包含:
  1) interest集合:selectionKey.interestOps()  可以监听四种不同类型的事件:OP_ACCEPT、OP_CONNECT、OP_WRITE、OP_READ
  2) ready集合:selectionKey.readyOps();  ready 集合是通道已经准备就绪的操作的集合,提供4个方便的方法:
  ·selectionKey.isAcceptable();
  · selectionKey.isConnectable();
  · selectionKey.isReadable();
  · selectionKey.isWritable();
  3) Channel:selectionKey.channel();
  4) Selector:selectionKey.selector();
  5) 可选的附加对象:
  selectionKey.attachment();  可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别特定的通道。
  提示:
  OP_ACCEPT和OP_CONNECT的区别:简单来说,客户端建立连接是connect,服务器准备接收连接是accept。一个典型的客户端服务器网络交互流程如下图:
  selectedKeys() 
  一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问已选择键集(selected key set)中的就绪通道。
  wakeUp()
  某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在阻塞线程调用select()方法的对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,下个调用select()方法的线程会立即wake up。
  close()
  用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。
  通过Selector选择通道:
  · int select() 阻塞直到至少有一个通道在你注册的事件上就绪了
  · int select(long timeout) 增加最长阻塞毫秒数
  · int selectNow() 不会阻塞,不管什么通道就绪都立刻返回

TAG: 软件开发 Java java

 

评分:0

我来说两句

Open Toolbar