server
public class NioChatServer {
// 端口
private int port;
// 设置缓冲区大小,1024字节
private final int BUFFER = 1024;
// 读缓冲区 写缓冲区
private ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER);
private ByteBuffer writeBuffer = ByteBuffer.allocate(BUFFER);
// 编码方式设置为utf-8,下面字符和字符串互转时用得到
private Charset charset = Charset.forName("UTF-8");
// 设置端口
public NioChatServer(int port) {
this.port = port;
}
private void start() throws IOException {
// 创建ServerSocketChannel和Selector并打开
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
Selector selector = Selector.open();
// 设置非阻塞
serverSocketChannel.configureBlocking(false);
// 绑定监听端口,这里不是给ServerSocketChannel绑定,而是给ServerSocket绑定,socket()就是获取通道原生的ServerSocket或Socket
serverSocketChannel.socket().bind(new InetSocketAddress(port));
// 把server注册到Selector并监听Accept事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("启动服务器,监听端口:" + port);
while (true) {
// select()会返回此时触发了多少个Selector监听的事件
if (selector.select() > 0) {
// 获取这些已经触发的事件,selectedKeys()返回的是触发事件的所有信息
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey selectionKey : selectionKeys) {
handles(selectionKey, selector);
}
// 清空 避免重复
selectionKeys.clear();
}
}
}
// 处理事件
private void handles(SelectionKey selectionKey, Selector selector) throws IOException {
// 当触发了Accept事件,也就是有客户端请求进来
if (selectionKey.isAcceptable()) {
// 获取ServerSocketChannel
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
// 然后通过accept()方法接收客户端的请求,这个方法会返回客户端的SocketChannel,这一步和原生的ServerSocket类似
SocketChannel client = serverSocketChannel.accept();
client.configureBlocking(false);
// 把客户端的SocketChannel注册到Selector,并监听Read事件
client.register(selector, SelectionKey.OP_READ);
System.out.println("客户端[" + client.socket().getPort() + "]上线啦!");
}
// 当触发了Read事件,也就是客户端发来了消息
if (selectionKey.isReadable()) {
// 获取客户端的SocketChannel
SocketChannel client = (SocketChannel) selectionKey.channel();
String msg = receive(client);
System.out.println("客户端[" + client.socket().getPort() + "]:" + msg);
// 把消息转发给其他客户端
sendMessage(client, msg, selector);
// 判断用户是否退出
if (msg.equals("quit")) {
// 解除该事件的监听
selectionKey.cancel();
// 更新Selector
selector.wakeup();
System.out.println("客户端[" + client.socket().getPort() + "]下线了!");
}
}
}
// 转发消息的方法
private void sendMessage(SocketChannel client, String msg, Selector selector) throws IOException {
msg = "客户端[" + client.socket().getPort() + "]:" + msg;
System.out.println(selector.keys().size());
for (SelectionKey selectionKey : selector.keys()) {
System.out.println(selectionKey.channel().toString());
if (!(selectionKey.channel() instanceof ServerSocketChannel) && !client.equals(selectionKey.channel())
&& selectionKey.isValid()) {
SocketChannel otherClient = (SocketChannel) selectionKey.channel();
writeBuffer.clear();
writeBuffer.put(charset.encode(msg));
writeBuffer.flip();
// 把消息写入到缓冲区后,再把缓冲区的内容写到客户端对应的通道中
while (writeBuffer.hasRemaining()) {
otherClient.write(writeBuffer);
}
}
}
}
private String receive(SocketChannel client) throws IOException {
// 用缓冲区之前先清空一下,避免之前的信息残留
readBuffer.clear();
// 把通道里的信息读取到缓冲区,用while循环一直读取,直到读完所有消息。因为没有明确的类似\n这样的结尾,所以要一直读到没有字节为止
while (client.read(readBuffer) > 0)
// 把消息读取到缓冲区后,需要转换buffer的读写状态,不明白的看看前面的Buffer的讲解
readBuffer.flip();
return String.valueOf(charset.decode(readBuffer));
}
public static void main(String[] args) {
try {
new NioChatServer(8888).start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
client
public class NioChatClient {
private static final String ADDR = "127.0.0.1";
private static final int PORT = 8888;
private static final int BUFFER = 1024;
private ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER);
private ByteBuffer writeBuffer = ByteBuffer.allocate(BUFFER);
private SocketChannel client;
private Selector selector;
private Charset charset = Charset.forName("UTF-8");
private void start() throws IOException {
client = SocketChannel.open();
selector = Selector.open();
client.configureBlocking(false);
// 注册channel,并监听SocketChannel的Connect事件
client.register(selector, SelectionKey.OP_CONNECT);
// 请求服务器建立连接
client.connect(new InetSocketAddress(ADDR, PORT));
while (true) {
int status = selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
for (SelectionKey selectionKey : selectedKeys) {
handle(selectionKey);
}
}
}
private void handle(SelectionKey selectionKey) throws IOException {
// 判断连接
if (selectionKey.isConnectable()) {
SocketChannel client = (SocketChannel) selectionKey.channel();
// finishConnect()返回true,说明和服务器已经建立连接。如果是false,说明还在连接中,还没完全连接完成
if (client.finishConnect()) {
// 新建一个新线程去等待用户输入
new Thread(new UserInputHandler(this)).start();
}
// 连接建立完成后,注册read事件,开始监听服务器转发的消息
client.register(selector, SelectionKey.OP_READ);
}
// 当触发read事件,也就是获取到服务器的转发消息
if (selectionKey.isReadable()) {
SocketChannel client = (SocketChannel) selectionKey.channel();
// 获取消息
String msg = receive(client);
System.out.println(msg);
// 判断用户是否退出
if (msg.equals("quit")) {
// 解除该事件的监听
selectionKey.cancel();
// 更新Selector
selector.wakeup();
}
}
}
private String receive(SocketChannel client) throws IOException {
readBuffer.clear();
while (client.read(readBuffer) > 0)
readBuffer.flip();
return String.valueOf(charset.decode(readBuffer));
}
public void send(String msg) throws IOException {
if (!msg.isEmpty()) {
writeBuffer.clear();
writeBuffer.put(charset.encode(msg));
writeBuffer.flip();
while (writeBuffer.hasRemaining()) {
client.write(writeBuffer);
}
if (msg.equals("quit")) {
selector.close();
}
}
}
public static void main(String[] args) {
try {
new NioChatClient().start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
UserInputHandler
public class UserInputHandler implements Runnable {
NioChatClient client;
public UserInputHandler(NioChatClient chatClient) {
this.client = chatClient;
}
@Override
public void run() {
BufferedReader read = new BufferedReader(new InputStreamReader(System.in));
while (true) {
try {
String input = read.readLine();
client.send(input);
if (input.equals("quit"))
break;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}