BIO即blocking IO,顾名思义是一种阻塞模型。当没有客户端连接时,服务端会一直阻塞,当有客户端新建连接时,服务端会新开一个线程去响应(不用多线程的话服务端同一时刻最多只能接收一个连接)。但不断的新开线程对服务器的压力是巨大的,为了缓解压力可以采用线程池技术实现线程复用,但这种做法治标不治本,本质还是一个连接一个线程。代码如下:
//服务端
public class BIOServer {
private final static int PORT = 8888;
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(PORT);
ExecutorService pool = Executors.newFixedThreadPool(20);
while(true) {
Socket socket = server.accept();
pool.execute(()->{
handler(socket);
});
}
}
public static void handler(Socket socket) {
try {
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
while(true) {
int len = inputStream.read(bytes);
if(len != -1) {
System.out.println("收到来自客户端的消息:" + new String(bytes,0, len));
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//客户端
public class BIOClient {
private final static String IP = "localhost";
private final static int PORT = 8888;
public static void main(String[] args) throws IOException {
Socket socket = new Socket(IP,PORT);
Scanner sc = new Scanner(System.in);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while(sc.hasNext()) {
String str = sc.nextLine();
bw.write(str);
bw.flush();
}
}
}
NIO即non-blocking IO,顾名思义是一种非阻塞模型。NIO的目的就是实现一个线程处理多个连接,故引入了几个重要的核心概念:
三者之间的关系如下图所示(图比较乱…序号表示NIO流程执行的大概步骤,部分连线只为了便于理解,不等于实际调用):
下面是用NIO实现的简易多人聊天室代码:
//服务端
public class NIOServer {
private final String IP = "localhost";
private final int PORT = 8888;
private ServerSocketChannel serverChannel;
private Selector selector;
private int count;
public NIOServer init() {
try {
serverChannel = ServerSocketChannel.open();
selector = Selector.open();
serverChannel.bind(new InetSocketAddress(IP,PORT));
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
count = 0;
} catch (IOException e) {
System.out.println("初始化失败...");
} finally {
return this;
}
}
public void start() {
while(true) {
try {
//阻塞直到有任意通道发生任一事件
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
//监听到新用户连接
if (key.isAcceptable()) {
userConnect(key);
}
//监听到有用户发消息
if (key.isReadable()) {
userSendMsg(key);
}
iterator.remove();
}
} catch (IOException e) {
System.out.println("未知的bug...");
}
}
}
public void userConnect(SelectionKey key) throws IOException {
SocketChannel channel = serverChannel.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
String msg = "有新的用户接入:" + channel.socket().getRemoteSocketAddress() +
",在线用户总数:" + ++count + "个";
System.out.println(msg);
transfer(msg, channel);
}
public void userSendMsg(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
String msg = null;
try {
//用户非正常关闭会直接在read这里抛IO异常
int len = channel.read(buffer);
//用户正常关闭连接需要手动抛IO异常,不然会一直空轮询分发读就绪事件
if (len == -1)
throw new IOException();
msg = channel.socket().getPort() + ":" + new String(buffer.array(), 0, len);
} catch (IOException e) {
//有用户断开连接
msg = channel.socket().getPort() + "断开连接,在线用户总数:" + --count + "个";
key.cancel();
channel.socket().close();
channel.close();
} finally {
System.out.println(msg);
transfer(msg, channel);
}
}
public void transfer(String msg, SocketChannel self) throws IOException{
Iterator<SelectionKey> iterator = selector.keys().iterator();
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
while(iterator.hasNext()) {
SelectionKey key = iterator.next();
if(key.channel() instanceof ServerSocketChannel)
continue;
SocketChannel channel = (SocketChannel) key.channel();
//转发用户消息
if(self != key.channel()) {
channel.write(buffer);
buffer.clear();
}
}
}
public static void main(String[] args) {
new NIOServer().init().start();
}
}
public class NIOClient {
private final static String IP = "localhost";
private final static int PORT = 8888;
private Selector selector;
private SocketChannel channel;
public void init() {
try {
selector = Selector.open();
InetSocketAddress address = new InetSocketAddress(IP, PORT);
channel = SocketChannel.open();
channel.connect(address);
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
System.out.println("初始化失败...");
}
}
public void sendMsg(String msg) {
try {
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
channel.write(buffer);
} catch (IOException e) {
System.out.println("服务器异常...");
}
}
public void readMsg(ByteBuffer buffer) {
try {
//只绑定了一个channel且监听的读事件...
if(selector.select() > 0) {
int len = channel.read(buffer);
if (len != -1) {
buffer.flip();
System.out.println(new String(buffer.array(), 0, len));
}
//这一步一定不能少,不然只能读一次消息
selector.selectedKeys().clear();
}
} catch (IOException e) {
System.out.println("服务器异常...");
}
}
public static void main(String[] args) {
NIOClient client = new NIOClient();
client.init();
ByteBuffer buffer = ByteBuffer.allocate(1024);
new Thread(() -> {
while (true) {
buffer.clear();
client.readMsg(buffer);
}
}).start();
Scanner sc = new Scanner(System.in);
while(sc.hasNext()) {
client.sendMsg(sc.nextLine());
}
try {
client.channel.socket().close();
client.channel.close();
} catch (IOException e) {
System.out.println("退出异常...");
}
}
}