JAVA NIO 聊天室

功能介紹

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();
	}

}

運行效果

服務器端效果
服務器端日誌打印
客戶一端效果
在這裏插入圖片描述
客戶端二效果
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章