(三)NIO-Channel

一、
ServerSocketChannel

对比 NIO与BIO的区别,
connect accept read write 四种阻塞情况

1.accept

服务端启动,等待 accept ,若无客户端请求,程序正常执行

	public static void main(String[] args) throws IOException {
		// 创建
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		// 绑定端口
		serverSocketChannel.socket().bind(new InetSocketAddress(9999));
		// 等待客户端连接
                // 程序运行到此,无法执行下去
		SocketChannel socketChannel = serverSocketChannel.accept();
}


参考:
JDK1.6中文文档
ServerSocketChannel 的 accept() 方法


如果此通道处于非阻塞模式,那么在不存在挂起的连接时,此方法将直接返回 null。否则,在新的连接可用或者发生 I/O 错误之前会无限期地阻塞它。

不管此通道的阻塞模式如何,此方法返回的套接字通道(如果有)将处于阻塞模式。


故,可设置阻塞与非阻塞

	public static void main(String[] args) throws IOException {
		// 创建
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		// 设置通道为非阻塞模式
		// 若不加,则处于阻塞模式,一直停留在accept
                // 该值默认为ture,若不进行设置,则为阻塞模式
		serverSocketChannel.configureBlocking(false);

		// 绑定端口
		serverSocketChannel.socket().bind(new InetSocketAddress(9999));
		// 等待客户端连接
		SocketChannel socketChannel = serverSocketChannel.accept();
                // 如果未接收到连接请求
		while (socketChannel == null){
			socketChannel = serverSocketChannel.accept();
		}
}


运行,程序可正常执行完毕,且处于等待状态

2.connect

客户端发起连接请求 connect ,而服务端未启动

	public static void main(String[] args) throws IOException, InterruptedException {

		SocketChannel socketChannel = SocketChannel.open();
		socketChannel.configureBlocking(false);
		// 开始连接
		socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
		// 完成连接
		socketChannel.finishConnect();
		while(!socketChannel.finishConnect()){
			Thread.sleep(10);
		}
		while(true){
			// 服务不停止
		}
}


3.read

服务端读取客户端发送的数据,而客户端发起连接但不发送数据,程序正常,未阻塞
public class ServerSocketChannelDemo {

	public static void main(String[] args) throws IOException {

		// 创建
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		// 设置通道为非阻塞模式
		// 若不加,则处于阻塞模式,一直停留在accept
		serverSocketChannel.configureBlocking(false);
		
		// 绑定端口
		serverSocketChannel.socket().bind(new InetSocketAddress(9999));
		// 等待客户端连接
		SocketChannel socketChannel = serverSocketChannel.accept();
		while (socketChannel == null){
			socketChannel = serverSocketChannel.accept();
		}

		socketChannel.configureBlocking(false);
		// 
		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
		socketChannel.read(byteBuffer);

	}

}


4.write

客户端写入数据 write,但服务端不读取,未发生阻塞

public class SocketChannelDemo {

	public static void main(String[] args) throws IOException, InterruptedException {

		SocketChannel socketChannel = SocketChannel.open();
		socketChannel.configureBlocking(false);
		// 开始连接
		socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
		// 完成连接
//		socketChannel.finishConnect();
		while(!socketChannel.finishConnect()){
			Thread.sleep(10);
		}
//		while(true){
//			// 服务不停止
//		}
		// 向缓冲区中写入数据
		for(int i = 0 ; i< Integer.MAX_VALUE ; i++){
			int outputDataCount = socketChannel.write(ByteBuffer.wrap("a".getBytes()));
			System.out.println("第"+i+"次输出,字节数:"+outputDataCount);
		}
	}

}


客户端输出:
字节数:1
..
字节数:0
...

字节数的变化原因:客户端不停写入,但服务端不取,数据会放入pipe管道中,但管道也是有容量大小的,达到极限则不写入。

二、channel通信

1.
客户端发送数据,服务端接收并输出客户端发送的数据

服务端
public class ServerSocketChannelDemo {

	public static void main(String[] args) throws IOException {

		// 创建
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		// 设置通道为非阻塞模式
		// 若不加,则处于阻塞模式,一直停留在accept
		serverSocketChannel.configureBlocking(false);
		
		// 绑定端口
		serverSocketChannel.socket().bind(new InetSocketAddress(9999));
		// 等待客户端连接
		SocketChannel socketChannel = serverSocketChannel.accept();
		while (socketChannel == null){
			socketChannel = serverSocketChannel.accept();
		}

		socketChannel.configureBlocking(false);
		// 非阻塞的,可能只读入了一部分数据
		ByteBuffer byteBuffer = ByteBuffer.allocate(30);//改为客户端发送的字符长度,若改为10或低于30,会丢失接收内容; 如何确认传递内容的长度--粘包问题
// 30 为定值根据客户端输入的数据决定,不灵活
		while(byteBuffer.hasRemaining()){
			socketChannel.read(byteBuffer);
		}
		String clientSendContent = new String(byteBuffer.toString());
		System.out.println("服务端接收客户端发送的数据:"+clientSendContent);
		while(true){
			// 让服务端服务不停止
		}
	}

}



客户端:
public class SocketChannelDemo {

	public static void main(String[] args) throws IOException, InterruptedException {

		SocketChannel socketChannel = SocketChannel.open();
		socketChannel.configureBlocking(false);
		// 开始连接
		socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
		// 完成连接
//		socketChannel.finishConnect();
		while(!socketChannel.finishConnect()){
			Thread.sleep(10);
		}
		// 客户端发送数据
		// 不保证数据一定发送成功
		String str = "hello nio , i am a boy , do you like me ?" ;
		ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
		while(byteBuffer.hasRemaining()){
			// 保证数据全部写入通道
			socketChannel.write(byteBuffer);
		}
		while(true){

		}
		// 向缓冲区中写入数据
//		for(int i = 0 ; i< Integer.MAX_VALUE ; i++){
//			int outputDataCount = socketChannel.write(ByteBuffer.wrap("a".getBytes()));
//			System.out.println("第"+i+"次输出,字节数:"+outputDataCount);
//		}
	}

}


运行结果:服务端输出正常

2.粘包问题
由于TCP传输是一种可靠的连续的数据传输,如果两次传输的数据时间间隔比较短,数据的接收方可能很难判断出两次数据的边界在哪里,感觉就好像两个数据黏着在了一次,无法区分。

TPC/IP协议:IP分发数据时,按包发送,小于包容量的多个内容会合在一起发送;大于包容量的内容拆分成多个包发送;
如何规定边界
方法 描述 缺点
发送定长的数据 若长度不足,空格补位 不灵活,浪费空间资源
发送指定的分割符 如规定#为分隔符 若发送内容包含#,还需要转译处理
带着发送数据的长度 发送内容前先将长度发送过去 发送内容变长


解决方式:
1.客户端
发送数据时带上此次发送数据的长度并用\r\n与真正需要发送的内容分割
格式为: str.length+\r\n+str
2.服务端
使用大小为1的缓冲区接收并解析头部信息,获取长度
根据长度设置内容缓冲区,读取指定内容

服务端
public class ServerSocketChannelDemo {

	public static void main(String[] args) throws IOException {

		// 创建
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		// 设置通道为非阻塞模式
		// 若不加,则处于阻塞模式,一直停留在accept
		serverSocketChannel.configureBlocking(false);
		
		// 绑定端口
		serverSocketChannel.socket().bind(new InetSocketAddress(9999));
		// 等待客户端连接
		SocketChannel socketChannel = serverSocketChannel.accept();
		while (socketChannel == null){
			socketChannel = serverSocketChannel.accept();
		}

		socketChannel.configureBlocking(false);

		// 计算客户端传送的内容的长度
		// 设置缓冲区长度为1,读取一个字符拼接一次,然后清空缓冲区,继续读取下一个
		String head = "";
		ByteBuffer temp = ByteBuffer.allocate(1);
		while(!head.endsWith("\r\n")){
			
			socketChannel.read(temp);
			head += new String(temp.array());
			temp.clear();
		}
		// 如:124\r\n 去掉字符串后的分割符号
		Integer len = Integer.valueOf(head.substring(0, head.length()-2));
		System.out.println("客户端发送的数据内容的长度:"+len);
		// 非阻塞的,可能只读入了一部分数据
		ByteBuffer byteBuffer = ByteBuffer.allocate(len.intValue());//改为客户端发送的字符长度
		while(byteBuffer.hasRemaining()){
			socketChannel.read(byteBuffer);
		}
		String clientSendContent = new String(byteBuffer.array());
		System.out.println("服务端接收客户端发送的数据:"+clientSendContent);
		while(true){
			// 让服务端服务不停止
		}
	}

}


客户端

public class SocketChannelDemo {

	public static void main(String[] args) throws IOException, InterruptedException {

		SocketChannel socketChannel = SocketChannel.open();
		socketChannel.configureBlocking(false);
		// 开始连接
		socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
		// 完成连接;一定要先确认连接完成,否则,执行到write时会异常
//		socketChannel.finishConnect();
		while(!socketChannel.finishConnect()){
			Thread.sleep(10);
		}
		// 客户端发送数据
		// 不保证数据一定发送成功
		String str = "hello" ;
		StringBuffer sb = new StringBuffer();
		sb.append(str.length() + "\r\n").append(str);
		ByteBuffer byteBuffer = ByteBuffer.wrap(sb.toString().getBytes());
		while(byteBuffer.hasRemaining()){
			// 保证数据全部写入通道
			socketChannel.write(byteBuffer);
		}
		while(true){

		}
		// 向缓冲区中写入数据
//		for(int i = 0 ; i< Integer.MAX_VALUE ; i++){
//			int outputDataCount = socketChannel.write(ByteBuffer.wrap("a".getBytes()));
//			System.out.println("第"+i+"次输出,字节数:"+outputDataCount);
//		}
	}

}


三、DataGramChannel

  • DataGramChannel 基于UDP
  • SocketChannel 基于TCP


TCP 传输慢 信息准确
UDP 传输块 信息不准确


发送:
	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {
		DatagramChannel server = DatagramChannel.open();
		String content = "hello , UDP SEND " ;
		server.send(ByteBuffer.wrap(content.getBytes()), new InetSocketAddress("127.0.0.1",8899));

	}


接收:
	public static void main(String[] args) throws IOException {

		DatagramChannel client = DatagramChannel.open();
		client.socket().bind(new InetSocketAddress(8899));
		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
		client.receive(byteBuffer);
		String content = new String(byteBuffer.array(),0,byteBuffer.position());
		System.out.println(content);
	}


四、FileChannel

阻塞式的,比BIO 中 File提供更多的方法


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 *	阻塞式
 *	比BIO提供更多的方法 
 */
public class FileChannelDemo {

	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {

		write();
		read();
	}

	/**
	 * 读取第三个位置起得三个字符
	 * @throws IOException
	 */
	public static void read() throws IOException {
		FileInputStream in = new FileInputStream(new File("1.txt"));
		FileChannel channel = in.getChannel();
		channel.position(3);
		ByteBuffer byteBuffer = ByteBuffer.allocate(3);
		channel.read(byteBuffer);
		String contentString = new String(byteBuffer.array());
		System.out.println(contentString);
		
		channel.close();
	}
	
	/**
	 * 修改第三个位置起的三个字符
	 * @throws IOException
	 */
	public static void write() throws IOException{
		// 使用FileOutPutStream 会覆盖原有信息
//		FileOutputStream file = new FileOutputStream(new File("1.txt"));
		RandomAccessFile file = new RandomAccessFile(new File("1.txt"), "rw");
		FileChannel channel = file.getChannel();
		ByteBuffer byteBuffer = ByteBuffer.wrap("xyz".toString().getBytes());
		channel.position(3);
		while(byteBuffer.hasRemaining()){
			channel.write(byteBuffer);
		}
		
		channel.close();
		file.close();
		
	}
}



1.txt
abcdefghijklmnopqrstuvwxyz

猜你喜欢

转载自mingyundezuoan.iteye.com/blog/2368516