NIO实现网络聊天室
1. Selector
在进入代码之前,我们先了解一下选择器:Selector,网络编程的大哥大,服务器可以执行一个线程,运行Selector程序,进行监听操作。它的作用是检查一个或者多个通道的状态是否可读。
Selector 中常用的方法:
- public static Selector Open();
得到一个选择器对象 - public int select(long timeout);
监听所有注册通道,如果存在IO流操作,会将对应的信息SelectionKey存入到内部的集合中,参数是一个超时时间 - public Set selectionKeys();
返回当前Selector内部集合中保存的所有SelectionKey
1.1 SelectionKey
SelectionKey 表示 SelectionKey 和网络通道直接的关系
int OP_ACCEPT; 16 需要连接
int OP_CONNECT; 8 已经连接
Int OP_WRITE; 4 写入操作
int OP_READ; 1 读取操作
方法:
- public abstract Selector selector();
得到与之关联的 Selector 对象 - public abstract SelectableChannel channel();
得到与之关联的通道 - public final Object attachment();
得到与之关联的共享数据 - public abstract SelectionKey interestOps(int ops);
设置或改变监听事件 - public final boolean isAcceptable();
是否可以 accept - public final boolean isReadable();
是否可以读 - public final boolean isWritable();
是否可以写
1.2 ServerSocketChannel
服务端Socket程序对应的Channel通道
方法 | 作用 |
---|---|
public static ServerSocketChannel open() | 开启服务器ServerSocketChannel通道 |
public final ServerSocketChannel bind(SocketAddress local); | 设置服务器端端口号 |
public final SelectableChannel configureBlocking(boolean block); | 设置阻塞或非阻塞模式,false 表示非阻塞 |
public SocketChannel accept() | 获取一个客户端连接,并且得到对应的操作通道 |
public final SelectionKey register(Selector sel, int ops) | 注册当前选择器,并且选择监听什么事件 |
1.3 SocketChannel
客户端Socket对应的Channel对象
方法 | 作用 |
---|---|
static SocketChannel open() | 打开一个Socket客户端Channel对象 |
final SelectableChannel configureBlocking(boolean block) | 设置是阻塞状态,还是非阻塞状态 |
boolean connect(SocketAddress remote) | 连接服务器 |
boolean finishConnect() | 如果connect连接失败,可以通过finishConnect继续连接 |
final SelectionKey register(Selector sel, int ops, Object attechment); | 注册当前SocketChannel,选择对应的监听操作,并且可以带有Object attachment参数 |
final void close() | 关闭SocketChannel |
2. NIO 完成一个客户端和服务器
2.1 服务器部分
public static void main(String[] args) throws IOException {
// 1. 开启服务器
ServerSocketChannel serverSocket = ServerSocketChannel.open();
// 2. 开启Selector
Selector selector = Selector.open();
// 3. 服务端代码绑定端口号
serverSocket.bind(new InetSocketAddress(8848));
// 4. 设置非阻塞状态
serverSocket.configureBlocking(false);
//5. ServerSocketChannel 注册--> Selector 返回值是一个SelectionKey
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
if (0 == selector.select(1000)) {
// 0 == selector.select(1000) 表示没有连接到客户端
continue;
}
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
while (selectionKeys.hasNext()) {
SelectionKey selectionKey = selectionKeys.next();
//连接请求
if (selectionKey.isAcceptable()) {
System.out.println("客户端请求连接!!!");
SocketChannel socket = serverSocket.accept();
socket.configureBlocking(false);
socket.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024 * 4));
}
//可读
if (selectionKey.isReadable()) {
SocketChannel socket = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
socket.read(buffer);
System.out.println("客户端发送数据:" + new String(buffer.array()));
}
selectionKeys.remove();
}
}
}
2.2 客户端部分
//main方法内
SocketChannel socket = SocketChannel.open();
socket.configureBlocking(false);
InetSocketAddress address = new InetSocketAddress("192.168.1.110", 8848);
//l连接服务器
if (!socket.connect(address)) {
while (!socket.finishConnect()) {
System.out.println("保持呼叫服务器状态,但是我还能做点别的事情~~~ 等待2s继续申请连接~~~");
Thread.sleep(2000);
}
}
//准备一个数据存入到缓冲区
ByteBuffer buffer = ByteBuffer.wrap("你好,服务器,我在等你...".getBytes());
socket.write(buffer);
new Scanner(System.in).nextLine();