BIO编程
- 传统的TCP和UDP编程通讯:Blocking I/O
- 一方在写入或读 数据时过慢,另一方需要等待
NIO编程,同步非阻塞
- Non-Blocking I/O
- 提供非阻塞通讯等方式
- 避免同步I/O通讯效率过低
- 一个线程可以管理多个连接
- 减少线程多的压力
- JDK1.4引入,1.7升级为NIO2.0(包括AIO)
- 主要在java.nio包中
- 主要类
– Buffer 缓冲区
– Channel 通道
– Selector 多路选择器
Selector 在这里就选择的作用,哪一个数据通道有响应,就处理哪一个通道。
Buffer缓冲区,一个可以读写的内存区域
- ByteBuffer,CharBuffer,DoubleBuffer,IntBuffer,LongBuffer,ShortBuffer
- StringBuffer 不是Buffer缓存区,是在原地进行字符串修改的一种数据类型
- 四个主要属性
– capacity 容量
– position 读写位置
– limti 界限
– mark 标记,用于重复一个读/写操作
Channel 通道
- 全双工的,支持读/写(Stream 流是单向的)
- 支持异步读写
- 和Buffer配合,提高效率
- ServerSocketChannel :服务器TCP Socket 接入通道,接收客户端
- SocketChannel :TCP Socket通道,可支持阻塞/非阻塞通讯
- DatagramChannel :UDP通道
- FileChannel: 文件通道
Selector 多路选择器
- 每隔一段时间,不断轮询注册在其上的Channel
- 如果有一个Channel 有接入,读,写操作,就会被轮询出来
- 根据SelectionKey 可以获取相应的Channel ,进行后续的I/O操作
- 避免过多的线程
- SelectionKey 四种类型
– OP_CONNECT 有人连接过来
–OP_ACCEPT 连接成功
– OP_READ 读
–OP_WRITE 写
package nio;
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.Iterator;
import java.util.Set;
public class NioServer {
public static void main(String[] args) throws IOException {
int port = 8001; //定义端口
Selector selector = null;
ServerSocketChannel servChannel = null;
try {
selector = Selector.open();//多路选择器
servChannel = ServerSocketChannel.open();
servChannel.configureBlocking(false); //配置非阻塞
servChannel.socket().bind(new InetSocketAddress(port), 1024);
servChannel.register(selector, SelectionKey.OP_ACCEPT); //选择器 和Channel1绑定
System.out.println("服务器在8001端口守候");
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
//轮询所以的channel,获取到有数据交换的channel放到selectedKsys,然后拿出来 给handleInput函数处理
while(true)
{
try {
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
handleInput(selector,key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}
}
catch(Exception ex)
{
ex.printStackTrace();
}
try
{
Thread.sleep(500);
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
}
public static void handleInput(Selector selector, SelectionKey key) throws IOException {
if (key.isValid()) {
// 处理新接入的请求消息
if (key.isAcceptable()) {
// Accept the new connection
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
// Add the new connection to the selector
sc.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
// Read the data
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String request = new String(bytes, "UTF-8"); //接收到的输入
System.out.println("client said: " + request);
String response = request + " 666";
doWrite(sc, response);
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else
; // 读到0字节,忽略
}
}
}
public static void doWrite(SocketChannel channel, String response) throws IOException {
if (response != null && response.trim().length() > 0) {
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
}
}
}
package nio;
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.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;
public class NioClient {
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 8001;
Selector selector = null;
SocketChannel socketChannel = null;
try
{
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false); // 非阻塞
// 如果直接连接成功,则注册到多路复用器上,发送请求消息,读应答
if (socketChannel.connect(new InetSocketAddress(host, port)))
{
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel);
}
else
{
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
//读取通道返回回来的值,对通道进行遍历
while (true)
{
try
{
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
while (it.hasNext())
{
key = it.next();
it.remove();
try
{
//处理每一个channel
handleInput(selector, key);
}
catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
// 多路复用器关闭后,所有注册在上面的Channel资源都会被自动去注册并关闭
// if (selector != null)
// try {
// selector.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
//
// }
}
// 把随机字符串放到缓冲区里面,然后把缓冲区送到通道里面去
public static void doWrite(SocketChannel sc) throws IOException {
byte[] str = UUID.randomUUID().toString().getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(str.length);
writeBuffer.put(str);
writeBuffer.flip();
sc.write(writeBuffer);
}
public static void handleInput(Selector selector, SelectionKey key) throws Exception {
if (key.isValid()) {
// 判断是否连接成功
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
sc.register(selector, SelectionKey.OP_READ);
}
}
if (key.isReadable()) {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("Server said : " + body);
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else
; // 读到0字节,忽略
}
Thread.sleep(3000);
doWrite(sc);
}
}
}
服务器读到客户端发来的一句话,读取看客户端发来的数据,然后加上666,发回给客户端
客户端发送一个数据,然后等到读取了服务端的数据后睡眠三秒钟继续发送数据
不管是Server端或者是Client端,她它们三个组件 Selector,Channel,Buffer之间的配合关系,通过Selector轮询 获取到有数据操作的Channel通道的集合,然后对于每一个selectionKey,拿出里面内容交给函数处理,读取的数据一般放在Buffer里面