功能介紹
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();
}
}
運行效果
服務器端效果
客戶一端效果
客戶端二效果