BIO与NIO
文章目录
解决Junit中无法Scanner的问题
一.BIO 阻塞式IO
使用java.io中的类
模拟一个服务器端(BIO),两个客户端
public class BIODemo {
@Test//客户端1
public void client1(){
//客户端与服务器端通信的socket
Socket socket = null;
OutputStream os = null;
try {
//去连接ip(本机)+端口(8007)的服务器
socket = new Socket("127.0.0.1",8007);
os = socket.getOutputStream();
Scanner scanner = new Scanner(System.in);
String msg = scanner.next();
os.write(msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test//客户端2
public void client2(){
//客户端与服务器端通信的socket
Socket socket = null;
OutputStream os = null;
try {
//去连接ip(本机)+端口(8007)的服务器
socket = new Socket("127.0.0.1",8007);
os = socket.getOutputStream();
Scanner scanner = new Scanner(System.in);
String msg = scanner.next();
os.write(msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
int len;
char[] c = new char[1024];
@Test // 服务端
public void server(){
//serverSocket对象,用来监听,如果监听到客户端的连接,则创建一个socket
ServerSocket serverSocket = null;
//接收到客户端连接之后,创建的服务器端与客户端通信的socket
Socket socket = null;
InputStream is = null;
InputStreamReader reader = null;
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8007));
//无线循环,一直处理客户端的连接
while(true) {
//阻塞,在接收到连接之前是阻塞的,一旦接收连接(客户端的socket)后,解阻塞,执行accept方法
System.out.println("服务器等待连接");
socket = serverSocket.accept();
System.out.println("成功连接");
System.out.println("等待数据");
is = socket.getInputStream();
reader = new InputStreamReader(is,"utf-8");
//阻塞,如果没有收到数据会一直阻塞,read表示读了多少字节
while((reader.read(c))!=-1) {
String content = new String(c);
System.out.println(content);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(reader!=null){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
这种服务端无法处理并发访问的情况
1.当开启服务端时,服务端阻塞在accept()方法前等待连接
2.当开启客户端1时,客户端1阻塞(等待用户输入),服务器根据监听socket检测到连接,并调用accept()返回一个socket响应客户端1socket,此时服务器端阻塞在read()方法等待客户端1发送数据
3.若此时开启客户端2,服务器已被阻塞在read()方法处,服务器无法监听客户端2的连接,就无法处理客户端的请求了
解决方案:修改服务端代码,启动线程处理read()
int len;
char[] c = new char[1024];
//serverSocket对象,用来监听,如果监听到客户端的连接,则创建一个socket
ServerSocket serverSocket = null;
//接收到客户端连接之后,创建的服务器端与客户端通信的socket
Socket socket = null;
InputStream is = null;
InputStreamReader reader = null;
@Test // 服务端
public void server(){
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8007));
//无线循环,一直处理客户端的连接
while(true) {
//阻塞,在接收到连接之前是阻塞的,一旦接收连接(客户端的socket)后,解阻塞,执行accept方法
System.out.println("服务器等待连接");
socket = serverSocket.accept();
System.out.println("成功连接");
System.out.println("等待数据");
is = socket.getInputStream();
//开启线程接收数据
Thread thread = new Thread(()->{
try {
reader = new InputStreamReader(is, "utf-8");
//阻塞,如果没有收到数据会一直阻塞,read表示读了多少字节
while ((reader.read(c)) != -1) {
String content = new String(c);
System.out.println(content);
}
}catch (Exception e){
}
});
thread.start();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(reader!=null){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
弊端:服务器端,如果不活跃的线程比较多(无用的线程),开启多线程是很浪费的一种情况,而且在不考虑多线程的情况下,BIO是无法处理并发的(阻塞),所以需要一个单线程就能解决的技术(NIO)
二.NIO 非阻塞式IO
模拟一个服务器端(NIO),两个客户端
NIO使用java.nio的类实现非阻塞(configureBlocking(false))
public class NIODemo {
@Test//客户端1
public void client1(){
//客户端与服务器端通信的socket
Socket socket = null;
OutputStream os = null;
try {
//去连接ip(本机)+端口(8007)的服务器
socket = new Socket("127.0.0.1",8007);
os = socket.getOutputStream();
Scanner scanner = new Scanner(System.in);
String msg = scanner.next();
os.write(msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test//客户端2
public void client2(){
//客户端与服务器端通信的socket
Socket socket = null;
OutputStream os = null;
try {
//去连接ip(本机)+端口(8007)的服务器
socket = new Socket("127.0.0.1",8007);
os = socket.getOutputStream();
Scanner scanner = new Scanner(System.in);
String msg = scanner.next();
os.write(msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//设置一个集合保存客户端的socket,方便管理与记录
List<SocketChannel> channels = new ArrayList<>();
//字节缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
@Test
public void server(){
//ServerSocketChannel默认为阻塞的,但是与BIO不同,它可以设置为非阻塞
ServerSocketChannel serverSocketChannel = null;
//SocketChannel默认为阻塞的,但是与BIO不同,它可以设置为非阻塞
SocketChannel socketChannel = null;
try {
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8007));
//serverSocketChannel设置为非阻塞,使得在accept()处不会阻塞
serverSocketChannel.configureBlocking(false);
while(true){
//循环遍历客户端Socket
for(SocketChannel channel:channels){
int read = channel.read(byteBuffer);
//如果读到数据,就打印数据
if(read>0) {
byteBuffer.flip();
byte[] b = byteBuffer.array();
System.out.println(new String(b));
}
}
socketChannel = serverSocketChannel.accept();
//如果没有收到客户端连接
if(socketChannel==null){
Thread.sleep(1500);
System.out.println("no connection");
}else{
//收到客户端连接
System.out.println("get connection");
//SocketChannel设置为非阻塞
socketChannel.configureBlocking(false);
//将客户端连接保存到集合中
channels.add(socketChannel);
}
}
} catch (Exception e) {
e.printStackTrace();
}finally{
if(serverSocketChannel!=null){
try {
serverSocketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
1.开启服务端,服务器端监听连接,先遍历客户端Socket的集合,如果接收到某一Socket传来的数据,就打印出来,没有数据,就继续,如果未收到连接,accept()不会阻塞,依然返回一个SocketChannel对象(null),并打印no connetion
2.开启一个客户端1,服务器端生成一个SocketChannel对象,将此对象放入集合中,并设置未非阻塞,这样代码执行到read()就不会阻塞
3.开启一个客户端2,与客户端1一致
4.客户端1发来数据,客户端遍历客户端Socket的集合,发现客户端1的socket有数据,则将这个数据打印出来
这种情况依然性能低下
假如集合中存了1000个客户端的Socket,只有200个发了数据,且这200也不是不停发数据,那么就有800一直没有发送数据,可是服务端还是要一直轮询1000个socket,去判断有没有数据发过来
解决方案:不让应用程序轮询集合,而是主动感知数据(操作系统层面而非编程语言),如果是Windows操作系统就调用select对Socket进行轮询,还是性能低下,如果是Linux操作系统就调用epoll函数,当有数据来了,可以通知是哪个Socket的数据,性能高