搭建一个一台服务端多台客户端连接的聊天室
首先梳理一下核心点:
服务端有一个本地Map,记录了所有连接的信息,以达到给所有人发送的效果
编码时的公共流程:
1.先打开各自的channel和selector,然后channel注册到selector上,注册事件C:CONNECT;S:ACCEPT
2.然后进行一个死循环,第一行是selector.select()该事件会阻塞自己知道感兴趣的事件发生。
3.事件发生后进行selector.selectedKeys(),返回一个Set<SelectionKey>,因为可能同时发生多个感兴趣的事件。
4.进行各自的操作
5.在操作完之后需要清空调用clear()对set集合清空。
客户端:
isConnectable()与isReadable()是处于同一级的,分别监听连接和读取
连接:isisConnectable->isConnectionPending->finishConnect才算正式建立好连接;再把建立连接成功的信息写到通道里.write(buffer);在开启一个线程一直监听本地键盘输入;最后把读事件注册到selector上
读取时:从管道中.read(buffer)即可读取到buffer中
服务端:
例子监听了Accept和Read事件:
当有连接的时候:吧该管道感兴趣的时间设为读并放进本地map中
当管道有数据可读的时候:先从管道读数据,然后遍历map向所有连接端写数据
源码:
server.java
package com.rikka.nio;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.*;
/**
* @Author Yan1less
* @Date 2019/4/21 12:55
* @Description 实现分发给所有客户端
**/
public class NioServer {
private static Map<String,SocketChannel> clientMap = new HashMap<>();
//服务器端只有一个线程
public static void main(String[] args) throws Exception{
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(8899));
Selector selector = Selector.open();
//把channel注册到selector上
//ServerSocketChannel关注连接事件
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
while (true){
try {
//这个方法会一直阻塞,直到发生了其感兴趣的事件
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
selectionKeys.forEach(selectionKey -> {
//SocketChannel关注读取事件
final SocketChannel client;
try{
if(selectionKey.isAcceptable()){
//这里为什么能强行向下转型呢?是因为上面只把serverSocketChannel注册到了OP_ACCEPT事件上
ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
//到这一步ServerSocketChannel的任务完成了,我们接下来只需要从client进行操作
client = server.accept();
client.configureBlocking(false);
client.register(selector,SelectionKey.OP_READ);
String key = "【"+UUID.randomUUID().toString() +"】";
clientMap.put(key,client);
}else if(selectionKey.isReadable()){
client = (SocketChannel)selectionKey.channel();
//一个不够用咋整
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int count = client.read(readBuffer);
if(count>0){
readBuffer.flip();
Charset charset = Charset.forName("utf-8");
String receivedMessage = String.valueOf(charset.decode(readBuffer).array());
System.out.println(client + ": " + receivedMessage);
String senderKey = null;
for(Map.Entry<String,SocketChannel> entry : clientMap.entrySet()){
if(client == entry.getValue()){
senderKey = entry.getKey();
break;
}
}
for(Map.Entry<String,SocketChannel> entry : clientMap.entrySet()){
SocketChannel value = entry.getValue();
ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
writeBuffer.put((senderKey+" : "+receivedMessage).getBytes());
//把数据放到buffer里称为读
writeBuffer.flip();
value.write(writeBuffer);
}
}
}
}catch (Exception e){
e.printStackTrace();
}
});
selectionKeys.clear();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
}
client.java
package com.rikka.nio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.time.LocalDateTime;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Author Yan1less
* @Date 2019/4/21 13:49
* @Description TODO
**/
public class NioClient {
public static void main(String[] args) throws Exception {
try{
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
socketChannel.register(selector,SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress("127.0.0.1",8899));
while (true){
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
selectionKeys.forEach(selectionKey -> {
if(selectionKey.isConnectable()){
SocketChannel client = (SocketChannel) selectionKey.channel();
//判断这个连接是不是正在进行的状态
if(client.isConnectionPending()){
try {
//这样连接才算建立好了
client.finishConnect();
ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
writeBuffer.put((LocalDateTime.now()+"连接成功").getBytes());
writeBuffer.flip();
client.write(writeBuffer);
ExecutorService executorService = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());
executorService.submit(()->{
while(true){
try{
writeBuffer.clear();
InputStreamReader input = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(input);
String sendMessage = br.readLine();
writeBuffer.put(sendMessage.getBytes());
writeBuffer.flip();
client.write(writeBuffer);
}catch (Exception ex){
ex.printStackTrace();
}
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
try {
client.register(selector,SelectionKey.OP_READ);
} catch (ClosedChannelException e) {
e.printStackTrace();
}
}else if(selectionKey.isReadable()){
SocketChannel client = (SocketChannel) selectionKey.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
try {
int read = client.read(readBuffer);
if(read>0){
String receivedMesage = new String(readBuffer.array(),0,read);
System.out.println(receivedMesage);
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
selectionKeys.clear();
}
}catch (Exception ex){
}
}
}
Tips:
1.每次新建一个channel的时候都要设置其configureBlocking(false)为非阻塞
2.无论是C或是S通过SocketChannel.open()方法获得的channel通道都只是为了链接,真正的数据传输通道都是通过selectionKey.channel()获得的(对于server来说需要在进行accept()方法来获得实际操作channel,注:ServerSocketChannel的作用也是为了获得SocketChannel),该方法需要强制类型转换,转换成SocketChannel
3.channel.read(buffer)是把buffer读取出来