BIO
BIO角色划分:服务端+客户端
server端代码
public class BIOServer {
static byte[] bytes = new byte[1024];
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
System.out.println("BIO-----等待client连接");
Socket clientSocket = serverSocket.accept();
System.out.println("BIO-----client连接成功");
System.out.println("BIO-----等待client发送数据");
clientSocket.getInputStream().read(bytes);
System.out.println("BIO-----server数据接收成功:" + new String(bytes));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
client端代码
public class BIOClient {
public static void main(String[] args) {
try {
Socket clientSocket = new Socket("127.0.0.1", 8080);
Scanner scanner = new Scanner(System.in);
String next = scanner.next();
clientSocket.getOutputStream().write(next.getBytes());
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
演示步骤
- 启动server端,发现打印“BIO-----等待client连接”后线程阻塞
- 启动client端,server端打印“BIO-----等待client发送数据”后线程再次阻塞
- 在client端输入“xxx”后server端接收到“xxx”,并再次线程阻塞,等待新的client端连接
演示结果
BIO-----等待client连接
BIO-----client连接成功
BIO-----等待client发送数据
BIO-----server数据接收成功:xxx
BIO-----等待client连接
小结
- serverSocket.accept()方法会造成线程阻塞
- clientSocket.getInputStream().read(bs)方法会造成线程阻塞
- 步骤3server端等待新的client端连接时,原client端再次发送数据server端无法读取
因为serverSocket.accept()和clientSocket.getInputStream().read(bs)方法会造成线程阻塞,所以BIO技术在并发场景下必须由多线程支持。
NIO
NIO的设计目标:用一个线程解决IO并发场景。
假设serverSocket.accept()和clientSocket.getInputStream().read(bs)方法不会造成线程阻塞,那么我们要做的就是把连接缓存起来,然后遍历读取数据。
NIO角色划分:客户端+服务端
server端代码
public class NIOServer {
static ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
static List<SocketChannel> cachedSocketChannelList = new ArrayList<>();
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 9090);
serverSocketChannel.bind(socketAddress);
serverSocketChannel.configureBlocking(false);// 等待client连接的动作 设置为非阻塞
while (true) {
SocketChannel clientSocketChannel = serverSocketChannel.accept();
if (clientSocketChannel != null) {
System.out.println("NIO-----client连接成功");
clientSocketChannel.configureBlocking(false);// 等待client发送数据的动作 设置为非阻塞
cachedSocketChannelList.add(clientSocketChannel);
}
// 遍历缓存的连接,接收数据
for (SocketChannel cachedSocketChannel : cachedSocketChannelList) {
int read = cachedSocketChannel.read(byteBuffer);
if (read > 0) {
byteBuffer.flip();// 写模式切换到读模式
byte[] bytes = new byte[read];
byteBuffer.get(bytes);
String content = new String(bytes);
System.out.println("NIO-----server数据接收成功");
System.out.println(content);
byteBuffer.flip();// 读模式切换到写模式
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
client端代码
public class BIOClient {
public static void main(String[] args) {
try {
Socket clientSocket = new Socket("127.0.0.1", 9090);
while(true){
Scanner scanner = new Scanner(System.in);
String next = scanner.next();
clientSocket.getOutputStream().write(next.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
为解决serverSocket.accept()和clientSocket.getInputStream().read(bs)方法会造成线程阻塞问题,server端使用ServerSocketChannel代替ServerSocket,用configureBlocking(false)来设置为非阻塞。
演示步骤
- 启动server端,线程while (true)循环,没有阻塞
- 启动client端,server端打印“NIO-----client连接成功”,并缓存clientSocketChannel
- client端可多次输入
演示结果
NIO-----client连接成功
NIO-----server数据接收成功:
aaa
NIO-----server数据接收成功:
bbb
NIO-----server数据接收成功:
ccc
小结
NIO实现用一个线程解决IO并发场景的设计目标,但仍有优化空间。
源码地址:IODemo模块