上篇文章是关于BIO的实例,现在知道BIO的缺点就是在不考虑多线程的情况下,无法实现并发,主要原因是有两个阻塞状态:1、服务器阻塞等待客户端的连接;2、服务器阻塞等待客户端向socket中发送消息。因此一个BIO的连接通常情况下会对CPU资源造成很大的浪费。
针对BIO的缺点,java引进了NIO(Noblocking IO)即非阻塞IO。
首先,需要了解几个重要的概念:
ServerSocketChannel:与BIO中的ServerSocket意义是一样的,都是用来监听客户端的连接,只不过多了非阻塞状态的配置。
ByteBuffer:NIO专有的一个内存空间。
具体NIO服务端实例代码如下(注释中有详细介绍):
public class NioServer {
//用来存储服务端接受到的socket
private static List<SocketChannel> socketChannels = new ArrayList<>();
//nio数据缓冲区
private static ByteBuffer byteBuffer = null;
public static void main(String[] args) {
try {
ServerSocketChannel socketChannel = ServerSocketChannel.open();
socketChannel.bind(new InetSocketAddress(8080));
//设置非阻塞,即使没有连接,线程可以继续走下去
socketChannel.configureBlocking(false);
while (true) {
//进入循环等待连接
System.out.println("wait...conn...");
//由于设置了非阻塞,即使没有连接也会继续执行下去(BIO当没有连接时会阻塞在这)
SocketChannel socket = socketChannel.accept();
if (socket == null) {
Thread.sleep(2000);
System.out.println("no...conn...");
//循环socketChannels,如果不为空读取消息并打印
for (SocketChannel clientSocket : socketChannels) {
byteBuffer = ByteBuffer.allocate(1024);
//当客户端发送消息时循环读取消息
int read = clientSocket.read(byteBuffer);
if (read > 0) {
byteBuffer.flip();//ByteBuffer固定写法,这里就不解释了
System.out.println(Charset.forName("utf-8").decode(byteBuffer));
}
}
} else {
System.out.println("conn...success...");
//当有客户端的连接时,并将此socket设置非阻塞
socket.configureBlocking(false);
//将收到的socket添加到socketChannels中
socketChannels.add(socket);
for (SocketChannel clientSocket1 : socketChannels) {
byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.flip();//ByteBuffer固定写法,这里就不解释了
//当客户端发送消息时循环读取消息
int read = clientSocket1.read(byteBuffer);
if (read > 0) {
byteBuffer.flip();
System.out.println(Charset.forName("utf-8").decode(byteBuffer));
}
}
}
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
接下来是客户端的代码,跟BIO相同,此时可以多个客户端同时连接,无阻塞状态:
客户端1:
public class NioClient1 {
public static void main(String[] args) throws IOException {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", 8080));
Scanner sc = new Scanner(System.in);
while (true) {
String msg = sc.next();
socket.getOutputStream().write(msg.getBytes());
}
}
}
客户端2:
public class NioClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", 8080));
Scanner sc = new Scanner(System.in);
while (true) {
String msg = sc.next();
socket.getOutputStream().write(msg.getBytes());
}
}
}
测试:
启动服务端:
可见服务端一直会打印等待连接并且此时没有连接:
分别启动NioClient和NioClient1:
此时有两个客户端连接上,并未发生阻塞状态:
两个客户端发送消息:
服务端收到并打印结果:
补充:
服务端的代码可以优化下:
public class NioServer {
//用来存储服务端接受到的socket
private static List<SocketChannel> socketChannels = new ArrayList<>();
//nio数据缓冲区
private static ByteBuffer byteBuffer = null;
public static void main(String[] args) {
try {
ServerSocketChannel socketChannel = ServerSocketChannel.open();
socketChannel.bind(new InetSocketAddress(8080));
//设置非阻塞,即使没有连接,线程可以继续走下去
socketChannel.configureBlocking(false);
while (true) {
Thread.sleep(2000);
for (SocketChannel socketClient : socketChannels) {
byteBuffer = ByteBuffer.allocate(1024);
System.out.println("start...read...");
int read = socketClient.read(byteBuffer);
if (read > 0) {
byteBuffer.flip();
System.out.println(Charset.forName("utf-8").decode(byteBuffer));
}
}
SocketChannel socket = socketChannel.accept();
if (socket != null) {
System.out.println("conn...success...");
//接受到客户端的socket并将其设置为非阻塞
socket.configureBlocking(false);
socketChannels.add(socket);
System.out.println("socketChannels.size = " + socketChannels.size());
}
else {
System.out.println("no...conn...");
}
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}