功能介绍
JAVA NIO 聊天室功。服务器端实现了 客户端上线、下线状态监控及消息转发功能。客户端写了两个,第一个是主线程写另外写了一个Thread去接受输入。第二是使用了Selector监听消息,输入在一个子Thread中。本人喜欢程序中使用线程池去管理线程,所以就算有一个线程我也使用了线程池去管理这个线程了,当然线程池用的是Executors.newSingleThreadExecutor(); 单线程线程池。
服务器端代码
package nio.test.groupChat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class ChatServer {
private Selector selector;
private ServerSocketChannel ssc;
private void init(){
try {
//创建serverChannel
ssc = ServerSocketChannel.open();
//设置非阻塞
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(9090));
//打开选择器
selector = Selector.open();
//注册监听器
ssc.register(selector,SelectionKey.OP_ACCEPT);
//监听器监听
monitor(ssc);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* selector监听事件方法
* @throws IOException
* @throws ClosedChannelException
*/
private void monitor(ServerSocketChannel ssc) throws IOException, ClosedChannelException {
System.out.println("服务器已经启动...");
// selector.select();会阻塞 直到有事件产生
while(selector.select()>0){
//获取所有有事件的selectionKey
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while(it.hasNext()){
SelectionKey key = it.next();
//判断如果是accept事件 进行操作
if(key.isAcceptable()){
//从selectionKey中获取channel
SocketChannel socketChannel = ssc.accept();
//将连接进来的客户端 设置为非阻塞 并注册到seletor上
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println(socketChannel.getRemoteAddress().toString()+"上线");
// it.remove();
// printOnlineEvent(socketChannel);
}
//监听读事件 也就是从客户端发来的数据
if(key.isReadable()){
//打印数据
handle(key);
}
//将该socketChannel事件处理完以后要从selector中移除掉,防止死循环
it.remove();
}
}
}
/**
* 获取客户地址
* @param key
* @return
* @throws IOException
*/
private String getRemoteAddress(SelectionKey key){
String str = null;
try {
SocketChannel sc = (SocketChannel)key.channel();
str = sc.getRemoteAddress().toString();
} catch (Exception e) {
// TODO: handle exception
}
return str;
}
/**
* 处理从客户端发来的消息
* 并排除自己
* @param key
*/
private void handle(SelectionKey key) {
SocketChannel sc = (SocketChannel)key.channel();
String addr = getRemoteAddress(key);
try {
StringBuilder sb = new StringBuilder();
sb.append(addr+" :");
ByteBuffer bf = ByteBuffer.allocate(1024);
int len = 0;
while((len=sc.read(bf)) >0){
bf.flip();
sb.append(new String(bf.array(),0,len));
bf.clear();
}
if(sb.toString().length()>0){
System.out.println(sb.toString());
//转发数据
sendHandle(key,sb.toString());
}
} catch (Exception e) {
//如果接受消息失败表示离线
key.cancel();
System.out.println(addr+"客户端关闭");
try {
sc.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
/**
* 给客户端分发数据 排除自己
* @param key
* @param msg
*/
private void sendHandle(SelectionKey key,String msg){
try {
Set<SelectionKey> set = selector.keys();
for(SelectionKey clientKey : set){
Channel channel = clientKey.channel();
if(channel instanceof SocketChannel && clientKey != key){
SocketChannel socketChannel = (SocketChannel)channel;
System.out.println("给这个客户端转发消息:"+socketChannel.getRemoteAddress());
socketChannel.write(ByteBuffer.wrap(String.valueOf(msg).getBytes()));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args){
ChatServer cs = new ChatServer();
cs.init();
}
}
客户端代码
我写了两种方式
第一种是用线程监听消息的方式
第二种是用selector监听消息、另起一个线程写消息。
第一种方式
package nio.test.groupChat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ChatClient {
private final String HOST = "127.0.0.1";
private final int PORT = 9090;
ExecutorService pool = Executors.newSingleThreadExecutor();
private void init() throws IOException{
final SocketChannel clientChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));
clientChannel.configureBlocking(false);
System.out.println(clientChannel.getLocalAddress().toString());
//监听发送过来的消息
pool.execute(new Runnable() {
@Override
public void run() {
while (true) {
ByteBuffer bf = ByteBuffer.allocate(1024);
StringBuilder sb = new StringBuilder();
int len = 0;
try {
while ((len = clientChannel.read(bf))>0){
bf.flip();
sb.append(new String(bf.array(),0,len));
bf.clear();
}
if(0!=sb.length()){
System.out.println(sb.toString());
}
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextLine()){
String str = scanner.nextLine();
ByteBuffer bf = ByteBuffer.wrap(str.getBytes());
clientChannel.write(bf);
}
}
public static void main(String[] args){
ChatClient cc = new ChatClient();
try {
cc.init();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
第二种方式
package nio.test.groupChat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 聊天室 client 使用selector监听read消息
* @author zhang
*
*/
public class ChatClient2 {
private Selector selector;
private final String HOST = "127.0.0.1";
private final int PORT = 9090;
ExecutorService pool = Executors.newSingleThreadExecutor();
private void init(){
try {
//创建channel
final SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(HOST,PORT));
socketChannel.configureBlocking(false);
selector = Selector.open();
//注册监听器 监听读消息
socketChannel.register(selector, SelectionKey.OP_READ);
//异步输入
pool.execute(new Runnable() {
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextLine()){
String str = scanner.nextLine();
try {
socketChannel.write(ByteBuffer.wrap(str.getBytes()));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
System.out.println("我是:"+socketChannel.getLocalAddress());
while(selector.select()>0){
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while(it.hasNext()){
SelectionKey key = it.next();
if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
StringBuilder sb = new StringBuilder();
while(channel.read(buf)>0){
buf.flip();
sb.append(new String(buf.array(),0,buf.limit()));
buf.clear();
}
System.out.println(sb.toString());
}
}
it.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ChatClient2 cc = new ChatClient2();
cc.init();
}
}
运行效果
服务器端效果
客户一端效果
客户端二效果