BIO 阻塞io NIO非阻塞io
java Socket编程中的这两种方式 我的理解是 BIO在Server.accept接收到连接请求后,开启一个线程处理此请求,io操作(客户端写入)时,该线程阻塞,客户端写入完毕后,该线程读取信息开始处理。这里存在一个过程,若客户端写操作耗时长,服务端线程等待,白白浪费线程资源,若是并发量大,服务端可能承受不住,这里可以做线程池来优化,但是还是没有避免线程中需要等待客户端写操作完成。
NIO则不然,单线程中使用多路复用器Selector阻塞监听多个Channel的事件(包括Server端的监听连接事件、客户端的写入完毕事件),若存在就绪的Channel,开启线程处理消息,避免了客户端写入时的线程阻塞
以下是java BIO和NIO示例代码
BIO 阻塞监听连接,一个连接对应一个线程处理(可ThreadPoolExecutor线程池优化)
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
public class BIOTest {
public static void main(String[] args) throws IOException {
System.out.println("socket tcp服务器端启动....");
ServerSocket serverSocket = new ServerSocket(8080);
// 等待客户端请求
try {
while (true) {
System.out.println("服务器等待新连接。。。。。。。。。。");
Socket accept = serverSocket.accept();
System.out.println("服务器有了新连接。。。。。。。。。。");
new Thread(new Runnable() {
@Override
public void run() {
try {
InputStream inputStream = accept.getInputStream();
// 转换成string类型
byte[] buf = new byte[1024];
System.out.println("线程开始read。。。。。。。。。。");
int len = inputStream.read(buf);
System.out.println("线程结束read。。。。。。。。。。");
String str = new String(buf, 0, len);
System.out.println("服务器接受客户端内容:" + str);
} catch (Exception e) {
}
}
}).start();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
serverSocket.close();
}
}
}
class TcpClient {
public static void main(String[] args) throws UnknownHostException, IOException {
System.out.println("socket tcp 客户端启动....");
Socket socket = new Socket("127.0.0.1", 8080);
System.out.println("客户端线程getOutputStream。。。。。。。。。。");
OutputStream outputStream = socket.getOutputStream();
System.out.println("客户端线程write。。。。。。。。。。");
outputStream.write("我是客户端".getBytes());
socket.close();
}
}
启动服务端
客户端代码在写入数据行打上断点,模拟耗时操作
可以看到,客户端连接服务端已经接收到,阻塞在read方法
F8完毕
NIO代码 Selector监听,如果是连接操作,再注册对应channel的读事件继续监听,如果读就绪,开始处理,线程不需要等待客户端动作
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;
public class NIOTest {
public static void main(String[] args) throws IOException {
System.out.println("服务器端已经被启动。。。。。。");
ServerSocketChannel socketChannel = ServerSocketChannel.open();
// 非阻塞
socketChannel.configureBlocking(false);
// 绑定端口
socketChannel.bind(new InetSocketAddress(8080));
// 多路复用器
Selector selector=Selector.open();
//将socketChannel注册到选择器中 并且指定其监听连接事件
socketChannel.register(selector,SelectionKey.OP_ACCEPT);
// 轮询获取就绪的事件
while(selector.select()>0){
//获取当前选择器中所有已经注册的selectedKey
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
while(selectionKeys.hasNext()){
//获取就绪事件
SelectionKey selectionKey = selectionKeys.next();
// 连接就绪
if(selectionKey.isAcceptable()){
// 注册客户端clientChannel读事件
SocketChannel clientChannel = socketChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector,SelectionKey.OP_READ);
}
if(selectionKey.isReadable()){
// 客户端Channel读就绪
SocketChannel channel = (SocketChannel) selectionKey.channel();
// 读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 把channel中的数据读取到buffer
clientChannel.read(buffer);
// 开始处理
}
selectionKeys.remove();
}
}
}
}
class NIOClient {
public static void main(String[] args) throws IOException {
System.out.println("客户端已经启动。。。");
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8080));
socketChannel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("略略略".toString().getBytes());
buffer.flip();//切换到读取模式
socketChannel.write(buffer);
buffer.clear();
socketChannel.close();
}
}