关于nio

ByteBuffer:

ByteBuffer类是在Java NIO中常常使用的一个缓冲区类,使用它可以进行高效的IO操作.

使用缓冲区有这么两个好处:

1、减少实际的物理读写次数

2、缓冲区在创建时就被分配内存,这块内存区域一直被重用,可以减少动态分配和回收内存的次数

创建ByteBuffer的方法:

使用allocate()静态方法

 ByteBuffer buffer=ByteBuffer.allocate(256);

以上方法将创建一个容量为256字节的ByteBuffer,如果发现创建的缓冲区容量太小,唯一的选择就是重新创建一个大小合适的缓冲区.

通过包装一个已有的数组来创建
如下,通过包装的方法创建的缓冲区保留了被包装数组内保存的数据.

ByteBuffer buffer=ByteBuffer.wrap(byteArray);

如果要将一个字符串存入ByteBuffer,可以如下操作:

String sendString="你好,服务器. ";
ByteBuffer sendBuffer=ByteBuffer.wrap(sendString.getBytes("UTF-16"));   
回绕缓冲区
 buffer.flip();

这个方法用来将缓冲区准备为数据传出状态,执行以上方法后,输出通道会从数据的开头而不是末尾开始.回绕保持缓冲区中的数据不变,只是准备写入而不是读取.

清除缓冲区
buffer.clear();

这个方法实际上也不会改变缓冲区的数据,而只是简单的重置了缓冲区的主要索引值.不必为了每次读写都创建新的缓冲区,那样做会降低性能.相反,要重用现在的缓冲区,在再次读取之前要清除缓冲区.

从套接字通道(信道)读取数据
int bytesReaded=socketChannel.read(buffer);

执行以上方法后,通道会从socket读取的数据填充此缓冲区,它返回成功读取并存储在缓冲区的字节数.在默认情况下,这至少会读取一个字节,或者返回-1指示数据结束.

向套接字通道(信道)写入数据
socketChannel.write(buffer);

此方法以一个ByteBuffer为参数,试图将该缓冲区中剩余的字节写入信道.

ByteBuffer俗称缓冲器, 是将数据移进移出通道的唯一方式,并且我们只能创建一个独立的基本类型缓冲器,或者使用“as”方法从 ByteBuffer 中获得。ByteBuffer 中存放的是字节,如果要将它们转换成字符串则需要使用 Charset , Charset 是字符编码,它提供了把字节流转换成字符串 ( 解码 ) 和将字符串转换成字节流 ( 编码) 的方法。

private byte[] getBytes (char[] chars) {//将字符转为字节(编码)
   Charset cs = Charset.forName ("UTF-8");
   CharBuffer cb = CharBuffer.allocate (chars.length);
   cb.put (chars);
   cb.flip ();
   ByteBuffer bb = cs.encode (cb)
   return bb.array();
         }

private char[] getChars (byte[] bytes) {//将字节转为字符(解码)
      Charset cs = Charset.forName ("UTF-8");
      ByteBuffer bb = ByteBuffer.allocate (bytes.length);
      bb.put (bytes);
      bb.flip ();
       CharBuffer cb = cs.decode (bb);
  
   return cb.array();
}
//主要通过读取文件内容,写到ByteBuffer里,然后再从ByteBuffer对象中获取数据,显示到控制台
	
	public static void  readFile(String fileName) {
		try {
			RandomAccessFile randomAccessFile = new RandomAccessFile(fileName, "rw");
			FileChannel fileChannel = randomAccessFile.getChannel();
			ByteBuffer byteBuffer = ByteBuffer.allocate(10);
			int size = fileChannel.read(byteBuffer);
			while(size>0){
				//把ByteBuffer从写模式,转变成读取模式
				byteBuffer.flip();
				Charset charset = Charset.forName("UTF-8");
				System.out.println(charset.newDecoder().decode(byteBuffer).toString());
				byteBuffer.clear();
				size = fileChannel.read(byteBuffer);
				
			}
			
			fileChannel.close();
			randomAccessFile.close();
		}  catch (Exception e) {
			e.printStackTrace();
		}
		
	}

SocketChannel:

SocketChannel是一种面向流连接只sockets套接字的可选择通道

  • SocketChannel是用来连接Socket套接字
  • SocketChannel主要用途用来处理网络I/O的通道
  • SocketChannel是基于TCP连接传输
  • SocketChannel实现了可选择通道,可以被多路复用的

SocketChannel具有以下的特征:

  1. 对于已经存在的socket不能创建SocketChannel
  2. SocketChannel中提供的open接口创建的Channel并没有进行网络级联,需要使用connect接口连接到指定地址
  3. 未进行连接的SocketChannle执行I/O操作时,会抛出NotYetConnectedException
  4. SocketChannel支持两种I/O模式:阻塞式和非阻塞式
  5. SocketChannel支持异步关闭。如果SocketChannel在一个线程上read阻塞,另一个线程对该SocketChannel调用shutdownInput,则读阻塞的线程将返回-1表示没有读取任何数据;如果SocketChannel在一个线程上write阻塞,另一个线程对该SocketChannel调用shutdownWrite,则写阻塞的线程将抛出AsynchronousCloseException
  6. SocketChannel支持设定参数
参数名作用描述
SO_SNDBUF套接字发送缓冲区大小
SO_RCVBUF套接字接收缓冲区大小
SO_KEEPALIVE保活连接
O_REUSEADDR复用地址
SO_LINGER有数据传输时延缓关闭Channel (只有在非阻塞模式下有用)
TCP_NODELAY禁用Nagle算法
创建SocketChannel
//方式1.
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("www.baidu.com", 80));

//方式2. 
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("www.baidu.com", 80));
连接校验
socketChannel.isOpen();      // 测试SocketChannel是否为open状态
socketChannel.isConnected();    //测试SocketChannel是否已经被连接
socketChannel.isConnectionPending();    //测试SocketChannel是否正在进行连接
socketChannel.finishConnect();    //校验正在进行套接字连接的SocketChannel是否已经完成连接
阻塞和非阻塞两种模式
socketChannel.configureBlocking(false);//false表示非阻塞,true表示阻塞。
读写
//以下为阻塞式读,当执行到read出,线程将阻塞,控制台将无法打印test end!。
SocketChannel socketChannel = SocketChannel.open(
new InetSocketAddress("www.baidu.com", 80));
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
socketChannel.read(byteBuffer);
socketChannel.close();
System.out.println("test end!");

//以下为非阻塞读,控制台将打印test end!。
SocketChannel socketChannel = SocketChannel.open(
new InetSocketAddress("www.baidu.com", 80));
socketChannel.configureBlocking(false);
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
socketChannel.read(byteBuffer);
socketChannel.close();
System.out.println("test end!");
设置和获取参数
//通过setOptions方法可以设置socket套接字的相关参数
socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, Boolean.TRUE)
    .setOption(StandardSocketOptions.TCP_NODELAY, Boolean.TRUE);
//可以通过getOption获取相关参数的值。如默认的接收缓冲区大小是8192byte。
socketChannel.getOption(StandardSocketOptions.SO_KEEPALIVE);
socketChannel.getOption(StandardSocketOptions.SO_RCVBUF);
public  static void startClient()throws Exception{
      
      SocketChannel socketChannel = SocketChannel.open();
      socketChannel.connect(new InetSocketAddress("localhost", 8999));
//    socketChannel.configureBlocking(false);
      
      String request = "hello mmmmm";
      ByteBuffer buf = ByteBuffer.wrap(request.getBytes("UTF-8"));
      socketChannel.write(buf);
      
      
       ByteBuffer rbuf = ByteBuffer.allocate(48);
          int size =  socketChannel.read(rbuf);
          while(size>0){
            rbuf.flip();
            Charset charset = Charset.forName("UTF-8");
            System.out.println(charset.newDecoder().decode(rbuf));
            rbuf.clear();
            size =  socketChannel.read(rbuf);
          }
          buf.clear();
          rbuf.clear();
      socketChannel.close();
      
      Thread.sleep(50000);
      
   }

ServerSocketChannel:

Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样。

打开 ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
监听新进来的连接(阻塞式)
//通过 ServerSocketChannel.accept() 方法监听新进来的连接。当 accept()方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。因此, accept()方法会一直阻塞到有新连接到达。
while(true){
    SocketChannel socketChannel =
            serverSocketChannel.accept();

    //do something with socketChannel...
}
监听新进来的连接(非阻塞式)
serverSocketChannel.configureBlocking(false);
while(true){
    SocketChannel socketChannel =
            serverSocketChannel.accept();
    if(socketChannel != null){
        //do something with socketChannel...
    }
}

Selector:

Selector(选择器)是Java NIO中能够检测一到多个NIO渠道,并能够知晓渠道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

创建Selector
 Selector selector = Selector.open();
向selector注册channel,和感兴趣的事件
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8999));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

注意register()方法的第二个参数, 这是一个interest集合,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听以下4中不同类型的事件:

1,服务端接收客户端连接事件 SelectionKey.OP_ACCEPT

2,客户端连接服务端事件 SelectionKey.OP_CONNECT

3,读事件 SelectionKey.OP_READ

4,写事件 SelectionKey.OP_WRITE

如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来如下:

 SelectionKey key = channel.register(selector,Selectionkey.OP_READ|SelectionKey.OP_WRITE); 
 //当向Selector注册Channel时,registor()方法会返回一个SelectorKey对象。
通过Selector选择通道

一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道。换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。

一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。完整代码如下。

public static void startServer() throws Exception{
		
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		serverSocketChannel.bind(new InetSocketAddress(8999));
		Selector selector = Selector.open();
		serverSocketChannel.configureBlocking(false);
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		
		while(true){
			//每一个连接,只会select一次
			int select = selector.select();
			//是否有可用的通道已接入
			if(select>0){
				for(SelectionKey key :selector.selectedKeys()){
					if(key.isAcceptable()){
						SocketChannel socketChannel = ((ServerSocketChannel)key.channel()).accept();
						ByteBuffer buf = ByteBuffer.allocate(40);
						int size = socketChannel.read(buf);
						while(size>0){
							buf.flip();
							Charset charset = Charset.forName("UTF-8");
							System.out.print(charset.newDecoder().decode(buf).toString());
							size = socketChannel.read(buf);
						}
						buf.clear();
						
						ByteBuffer response = ByteBuffer.wrap("hi,已经收到了请求!".getBytes("UTF-8"));
						socketChannel.write(response);
						socketChannel.close();
						selector.selectedKeys().remove(key);
						
					}
					
					
				}
				
				
			}
			
			
		}
		
		
	}

public static void startClient()throws Exception{
		
		SocketChannel socketChannel = SocketChannel.open();
		socketChannel.connect(new InetSocketAddress("localhost",8999));
		//主要是设置成非阻塞
		socketChannel.configureBlocking(false);
		Selector selector = Selector.open();
		socketChannel.register(selector, SelectionKey.OP_READ);
		new ClientThread(selector).start();
		
		ByteBuffer byteBuffer = ByteBuffer.wrap("hello 我是客户端".getBytes("UTF-8"));
		socketChannel.write(byteBuffer);
		byteBuffer.clear();
		
	}
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

public class ClientThread extends Thread{

	private Selector selector;
	
	public ClientThread(Selector selector) {
		super();
		this.selector = selector;
	}


	@Override
	public void run() {
		
		try {
			
			while(selector.select()>0){
				for(SelectionKey key : selector.selectedKeys()){
					SocketChannel socketChannel= (SocketChannel)key.channel();
					ByteBuffer buf = ByteBuffer.allocate(40);
					int size = socketChannel.read(buf);
					while(size>0){
						buf.flip();
						Charset charset = Charset.forName("UTF-8");
						System.out.print(charset.newDecoder().decode(buf).toString());
						 size = socketChannel.read(buf);
					}
					selector.selectedKeys().remove(key);
				}
				
				
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		
	}
	
	
	

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值