NIO编程
1、流
1.1、 java.io下的字符流和字节流
1.1.1、字符流
字符流更加的方便我们使用,一般字符都是由多个字节来形成的,若我们使用字节流传输,则还需要我们自己将其转换为字符,若我们直接使用字符流的话,这样就能直接读取与输出字符。
1.1.2、字节流
字节流则是对一个个字节进行读取
1.2、装饰器模式
在字符流中BufferedReader、InputStreamReader和FilterReader,字节流中的BufferedInputStream、DataInputStreamReader与DataOutputStream都运用到了装饰器模式,因为它们本身不能进行实例化,都需要传入基本的Reader或InputStream来进行升级,这里便体现的是装饰器模式。
2、Socket
Socket认为是网络传输的端点,它也是一种数据源,将特定的ip地址与端口号与其进行绑定,这样它就能够实现通信的功能,如下图中,服务器中的socket绑定了对应的ip与端口号,与客户端间进行通信。
2.1、通过Socket发送数据
- 创建Socket,绑定特定的ip地址与端口号
- 将Socket与网卡驱动程序绑定
- 通过Socket我们就能够发送数据了
- 网卡驱动程序对Socket数据进行读取
2.2、通过Socket读取数据
- 我们还是要先创建Socket
- 将Socket与网卡驱动程序进行绑定
- Socket接收来自网卡驱动程序的数据
- 从Socket中读取数据
3、BIO模型
简述BIO模型中服务端与客户端的响应过程
- 服务器serverSocket先要和端口进行绑定
- 绑定完成后,执行accept方法,等待客户端的连接,这个方法是阻塞式调用,也就是说,要一直等待客户端的连接响应,不做其他事情,一直等,(被阻塞的还有InputStream.read()、OutputStream.write(),这两个也会一直等待客户端的响应)
- 客户端创建Socket对象,绑定服务器的ip地址与端口号,与服务器进行连接
- 服务器接收到客户端的连接请求,accept方法获取到客户端的socket信息,连接成功
- 服务器与客户端创建各自的io流,实现全双工通信
- 之后便可以随时结束连接
4、NIO模型
NIO:非阻塞IO
NIO和BIO有以下的区别:
- 使用Channel代替Stream
- 使用Selector监控多条Channel
- 在一个线程里处理多个Channel I/O
Channel是双向的,即可以读又可以写,相比于Stream,它并不区分出输入流和输出流,而且Channel可以完成非阻塞的读写,也可以完成阻塞的读写。
Channel间的数据交换,都需要依赖Buffer,Buffer实际上内存上一块用来读写的区域。
NIO模型分析:
- 在服务器端创建一个Selector,将ServerSocketChannel注册到Selector上,被Selector监听的事件为Accept。
- Client1请求与服务器建立连接,Selector接收到Accept事件,服务器端对其进行处理(handles),服务器与客户端连接成功。
- 建立连接过程中,服务器通道(ServerSocketChannel)调用accept方法,获取到与客户端进行连接的通道(SocketChannel),也将其注册到Selector上,监听READ事件,这样,客户端向服务器发送消息,就能触发该READ事件进行响应,读取该消息。
- 同样,两个客户连接过来也是一个线程在起作用,将Client2的SocketChannel注册到服务器的Selector,并监听READ事件,随时响应随时处理。即一个客户端有一个SocketChannel,两个客户端就有两个SocketChannel,这个就是我们使用nio编程模型来用一个selector对象在一个线程里边监听以及处理多个通道的io的操作。
5、NIO多人聊天室
服务端:
package chatroom.nio.server;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Set;
public class ChatServer {
//默认监听端口
private final int DEFAULT_SERVER_PORT = 8888;
private final String QUIT = "quit";
private static final int BUFFER = 1024;
//服务端的channel和selector
private ServerSocketChannel serverSocketChannel;
private Selector selector;
private ByteBuffer rBuffer = ByteBuffer.allocate(BUFFER);
private ByteBuffer wBuffer = ByteBuffer.allocate(BUFFER);
private Charset charset = Charset.forName("UTF-8");
private int port;
public ChatServer(int port) {
this.port = port;
}
/*
检查用户是否准备退出
*/
public boolean readyToQuit(String msg) {
return QUIT.equals(msg);
}
public void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void start() {
try {
//创建ServerSocketChannel通道
serverSocketChannel = ServerSocketChannel.open();
//默认是阻塞模式,需要改为非阻塞 *** 重点 ***
serverSocketChannel.configureBlocking(false);
//绑定监听端口
serverSocketChannel.socket().bind(new InetSocketAddress(port));
//创建selector
selector = Selector.open();
//将accept事件注册到selector上 SelectionKey对象可以取出监听事件的相关信息。
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器启动成功,监听端口号:" + port + "...");
//不断的监听响应
while(true) {
//阻塞式调用,需要不停的调用select,故放入while循环中
selector.select();
//获取监听到触发的所有事件的SelectionKey
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for(SelectionKey selectionKey : selectionKeys) {
//处理事件
handlers(selectionKey);
}
//注意要清空事件,否则会重复响应
selectionKeys.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
close(selector);
}
}
//处理事件
private void handlers(SelectionKey selectionKey) throws IOException {
// ACCEPT事件 - 和客户端建立了连接
if(selectionKey.isAcceptable()) {
//处理accpet事件
//先获取ServerSocketChannel
ServerSocketChannel serverSocketChannel = (ServerSocketChannel)selectionKey.channel();
//获取对应的客户端通道
SocketChannel clientSocketChannel = serverSocketChannel.accept();
//默认是阻塞模式,需要改为非阻塞
clientSocketChannel.configureBlocking(false);
//将客户端通道绑定到selector上,监听read事件
clientSocketChannel.register(selector, SelectionKey.OP_READ);
System.out.println(getClientName(clientSocketChannel) + "已连接");
}
//READ事件 - 客户端发送了消息给服务端就会触发read事件
else if(selectionKey.isReadable()) {
//处理read事件
//通过selection获取对应的客户端通道
SocketChannel clientSocketChannel = (SocketChannel)selectionKey.channel();
//获取通道中的消息
String fwdMsg = receive(clientSocketChannel);
if(fwdMsg.isEmpty()) {
//接受不到消息,那么把该通道移除
selectionKey.channel();
//通知selector有注册的通道被移除了,更新状态
selector.wakeup();
} else {
//转发消息
forwardMessage(clientSocketChannel, fwdMsg);
System.out.println(getClientName(clientSocketChannel) + ":" + fwdMsg);
if(readyToQuit(fwdMsg)) {
selectionKey.channel();
selector.wakeup();
System.out.println(getClientName(clientSocketChannel) + "已断开");
}
}
}
}
private String getClientName(SocketChannel client) {
return "客户端[" + client.socket().getPort() + "]";
}
/**
* 接收方法
*/
private String receive(SocketChannel clientSocketChannel) throws IOException {
rBuffer.clear();
//一直读数据
while(clientSocketChannel.read(rBuffer) > 0);
//flip将buffer的读模式转换为写模式
rBuffer.flip();
return String.valueOf(charset.decode(rBuffer));
}
/**
* 转发消息
*/
private void forwardMessage(SocketChannel clientSocketChannel, String fwdMsg) throws IOException {
//keys()方法区别于selectedKeys(),selectedKeys()返回的是接下来需要被处理的通道key
//而keys()返回的是与selector绑定的所有通道key
for (SelectionKey selectionKey : selector.keys()) {
Channel connectedClient = selectionKey.channel();
if (selectionKey.channel() instanceof ServerSocketChannel) {
continue;
}
//有效且不是发送者
if (selectionKey.isValid() && !clientSocketChannel.equals(connectedClient)) {
wBuffer.clear();
//写入消息
wBuffer.put(charset.encode(getClientName(clientSocketChannel) + ":" + fwdMsg));
//wBuffer由写状态转换为读状态
wBuffer.flip();
//有数据有一直读
while (wBuffer.hasRemaining()) {
((SocketChannel)connectedClient).write(wBuffer);
}
}
}
}
public static void main(String[] args) {
ChatServer chatServer = new ChatServer(7777);
chatServer.start();
}
}
客户端:
package chatroom.nio.client;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Set;
public class ChatClient {
//默认主机地址
private static final String DEFAULT_SERVER_HOST = "127.0.0.1";
//默认监听端口
private static final int DEFAULT_SERVER_PORT = 8888;
private static final String QUIT = "quit";
private static final int BUFFER_SIZE = 1024;
private String host;
private int port;
private SocketChannel clientSocketChannel;
private ByteBuffer rBuffer = ByteBuffer.allocate(BUFFER_SIZE);
private ByteBuffer wBuffer = ByteBuffer.allocate(BUFFER_SIZE);
private Selector selector;
private Charset charset = Charset.forName("UTF-8");
public ChatClient() {
this.host = DEFAULT_SERVER_HOST;
this.port = DEFAULT_SERVER_PORT;
}
public ChatClient(String host, int port) {
this.host = host;
this.port = port;
}
/*
检查用户是否准备退出
*/
public boolean readyToQuit(String msg) {
return QUIT.equals(msg);
}
public void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void start() {
try {
// 创建用户通道
clientSocketChannel = SocketChannel.open();
clientSocketChannel.configureBlocking(false);
//创建selector,并且将用户通道的connect请求注册上去
selector = Selector.open();
clientSocketChannel.register(selector, SelectionKey.OP_CONNECT);
//尝试与服务器建立连接
clientSocketChannel.connect(new InetSocketAddress(host, port));
while(selector.isOpen()) {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey selectionKey : selectionKeys) {
//响应请求
handler(selectionKey);
}
selectionKeys.clear();
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClosedSelectorException e) {
// 用户正常退出
} finally {
close(selector);
}
}
private void handler(SelectionKey key) throws IOException {
// connect事件 - 连接就绪事件
if (key.isConnectable()) {
SocketChannel client = (SocketChannel) key.channel();
// 如果能够与服务器响应
if(client.isConnectionPending()) {
// 正式建立连接
client.finishConnect();
// 处理用户输入的信息,由于该线程会被用户输入所阻塞,故需要新建一个线程
new Thread(new UserInputHandler(this)).start();
}
client.register(selector, SelectionKey.OP_READ);
}
// read事件 - 服务器转发消息
else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
String msg = receive(client);
if (msg.isEmpty()) {
// 服务端异常
close(selector);
} else {
// TODO
System.out.println(msg);
}
}
}
private String receive(SocketChannel client) throws IOException {
rBuffer.clear();
while (client.read(rBuffer) > 0);
rBuffer.flip();
return String.valueOf(charset.decode(rBuffer));
}
public void send(String msg) throws IOException {
if (msg.isEmpty()) {
return;
}
wBuffer.clear();
// put方法写入缓冲区
wBuffer.put(charset.encode(msg));
// 转换为读模式
wBuffer.flip();
while (wBuffer.hasRemaining()) {
clientSocketChannel.write(wBuffer);
}
// 检查用户是否退出
if (readyToQuit(msg)) {
close(selector);
}
}
public static void main(String[] args) {
ChatClient client = new ChatClient(DEFAULT_SERVER_HOST, 7777);
client.start();
}
}
客户端UserInputHandler:
package chatroom.nio.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class UserInputHandler implements Runnable{
private ChatClient chatClient;
public UserInputHandler(ChatClient chatClient) {
this.chatClient = chatClient;
}
@Override
public void run() {
try {
//等待用户输入消息
BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String input = consoleReader.readLine();
// 向服务器发送消息
chatClient.send(input);
//检查用户是否准备退出
if (chatClient.readyToQuit(input)) {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}