java基于NIO的socket编程实例

        众所周知,java的NIO模块想要传输数据需要依赖缓冲区和通道,二者缺一不可。

        补充一下缓冲区(buf)的几个基本方法:

        1.allocate():分配一个指定大小的缓冲区。

        2.put():向buf里写数据。

        3.get():从buf里读数据。

        4.flip():将缓冲区从写模式切换到读模式,position被重置为0。

        5.clear():从读模式切换到写模式,表面清空,里边的数据只是被遗忘,但是后续写的数据会覆盖原有的数据。

        好了,关于缓冲区、通道、选择器这些东西的定义就不细说了,直接上代码了,自认为注释非常详细,一行一注,首先我们先演示一下阻塞式的通信:

package niotest;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

import org.junit.Test;

public class TestBlockingNIO {
	
	@Test
	public void client() throws IOException {
		
		//获取通道,一个socketChannel用于tcp通信,一个FileChannel用于读取本地文件以备后续发送
		SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
		FileChannel fileChannel = FileChannel.open(Paths.get("D://1.tif"), StandardOpenOption.READ);
		
		//分配指定大小的缓冲区
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		//读取本地文件,发送到服务端
		while(fileChannel.read(buf)!=-1) {
			buf.flip();
			socketChannel.write(buf);
			buf.clear();
		}
		
		//表明客户端发送完毕
		socketChannel.shutdownOutput();
		
		//接受服务端的反馈
		int len = 0;
		while((len=socketChannel.read(buf))!=-1) {
			buf.flip();
			System.out.println(new String(buf.array(), 0, len));
			buf.clear();
		}
		
		socketChannel.close();
		fileChannel.close();
	}
	
	@Test
	public void server() throws IOException {
		
		//获取通道
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		FileChannel fileChannel = FileChannel.open(Paths.get("D://3.tif"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
		
		//绑定端口
		serverSocketChannel.bind(new InetSocketAddress(9999));
		
		//获取客户端连接的通道,这是阻塞式的,会阻塞等待远程连接
		SocketChannel socketChannel = serverSocketChannel.accept();
		
		//分配缓冲区
		ByteBuffer buf = ByteBuffer.allocate(1024);

		//接受客户端发送的数据,保存成本地文件
		while(socketChannel.read(buf)!=-1) {
			buf.flip();
			fileChannel.write(buf);
			buf.clear();
		}
			
		//给客户端发送反馈
		buf.put("服务端接受完毕!!!".getBytes());
		buf.flip();
		socketChannel.write(buf);
		
		socketChannel.close();
		serverSocketChannel.close();
		fileChannel.close();

	}

}

        上面这个阻塞式的远程传输虽然简单,但是显然发挥不出NIO的优势,下面就来个复杂点的非阻塞式的实现:

package niotest;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;

import org.junit.Test;

public class TestNoBlockingNIO {
	
	@Test
	public void client() throws IOException {
		
		//1.获取通道
		SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
		
		//2.切换非阻塞模式
		socketChannel.configureBlocking(false);
		
		//3.分配缓冲区
		ByteBuffer buf = ByteBuffer.allocate(2048);
		
		//4.发送数据给server端
		buf.put(new Date().toString().getBytes());
		buf.flip();
		socketChannel.write(buf);
		buf.clear();
		
		//5.关闭通道
		socketChannel.close();
	}
	
	@Test
	public void server() throws IOException, InterruptedException {
		
		//1.获取服务端socket通道
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		
		//2.切换非阻塞模式
		serverSocketChannel.configureBlocking(false);
		
		//3.绑定端口
		serverSocketChannel.bind(new InetSocketAddress(9999));
		
		//4.获取选择器
		Selector selector = Selector.open();
		
		//5.将通道注册到选择器上,并指定监听的事件类型(一个选择器上可以注册多个通道),attach可附带一个信息,使用时用selectionKey.attachment()方法获取
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT).attach("accept");
		
		//6.不断的循环获取选择器上已经准备就绪的事件,selector.select()>0表示至少有一个事件已经准备就绪
		while((selector.select()) > 0) {

			//7.获取当前选择器中所有注册的已经就绪的事件,iterator()方法是Set集合中的方法,生成一个迭代接口
			Set<SelectionKey> skSet = selector.selectedKeys();
			Iterator<SelectionKey> skIterator = skSet.iterator();
			
			while(skIterator.hasNext()) {
				//8.获取准备就绪的事件
				SelectionKey sk = skIterator.next();
				
				//9.判断是什么事件准备就绪
				if(sk.isAcceptable()) {
					//10.若是接受就绪,则获取客户端连接
					SocketChannel socketChannel = serverSocketChannel.accept();
					
					//11.切换非阻塞模式
					socketChannel.configureBlocking(false);
					
					//12.把这个通道也注册到选择器上
					socketChannel.register(selector, SelectionKey.OP_READ).attach("read");
				}else if(sk.isReadable()) {
					//13.获取选择器上读就绪的通道
					SocketChannel socketChannel = (SocketChannel)sk.channel();
					
					//14.读数据
					ByteBuffer buf = ByteBuffer.allocate(1024);
					int len = 0;
					while ((len=socketChannel.read(buf))!=-1) {
						buf.flip();
						System.out.println(new String(buf.array(), 0, len));
						buf.clear();
					}
				}
				
				//15.取消当前的selectionKey,不然会一直循环,比如会一直获取到连接准备就绪事件,然后重复获取新的客户端连接
				//remove删除实际上是删除的63行中的skSet中的selectionKey,不然skSet中的元素会越来越多,就出问题了,可以自己调一下试试(我一开始就忘了清空)
				skIterator.remove();
			}
			
		}
		
	}

}

猜你喜欢

转载自blog.csdn.net/u014627099/article/details/86481196