實現的界面
客戶端
張飛用戶
劉備用戶
關羽用戶:
聊天室的具體功能如下:
1: 羣聊
2: 顯示在線人數
3: 用戶名信息
4: 用戶離線, 通知其他在線的用戶
聊天室代碼的github: https://github.com/victor9309/ChatRoom
參考: http://ifeve.com/selectors/
代碼如下:
服務端代碼
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
public class chatServer {
private int port;
private Selector selector;
private ByteBuffer readBuffer = ByteBuffer.allocate(1024);//調整緩衝區大小爲1024字節
private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
private static final String USER_NAME_TAG = "$%%^&*()!@#$^%#@*()*";
private HashSet<String> users = new HashSet<String>();
private HashMap<String, String> Users = new HashMap<>();
private String user_msg;
public chatServer(int port){
this.port = port;
}
public static void main(String[] args){
new chatServer(8081).start();
}
public void start() {
ServerSocketChannel ssc = null;
try {
ssc = ServerSocketChannel.open();
ssc.configureBlocking(false); //服務器配置爲非阻塞 即異步IO
ssc.socket().bind(new InetSocketAddress(port)); //綁定本地端口
selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT); //ssc註冊到selector準備連接
System.out.println("ChatServer started ......");
}catch (Exception e){
e.printStackTrace();
}
while(true){
try {
int events = selector.select();
if (events > 0) {
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
while (selectionKeys.hasNext()) {
SelectionKey key = selectionKeys.next();
selectionKeys.remove(); //移除當前的key
if (key.isValid()) {
if (key.isAcceptable()) {
accept(key);
}
if(key.isReadable()){
read(key);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void accept(SelectionKey key) throws IOException {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = ssc.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("a new client connected "+clientChannel.getLocalAddress());
}
private void read(SelectionKey key) throws IOException{
SocketChannel socketChannel = (SocketChannel) key.channel();
this.readBuffer.clear();//清除緩衝區,準備接受新數據
System.out.println("===============read");
int numRead;
try{
numRead = socketChannel.read(this.readBuffer);
}catch (IOException e){ // 客戶端斷開連接,這裏會報錯提示遠程主機強迫關閉了一個現有的連接。
offlineUser(key);
key.cancel();
socketChannel.close();
return;
}
user_msg = new String(readBuffer.array(),0, numRead);
for (String s: users) System.out.println("在線用戶: " + s);
if (user_msg.contains(USER_NAME_TAG)){ // 用戶第一次登陸, 輸入登錄名
String user_name = user_msg.replace(USER_NAME_TAG, "");
user_msg = "歡迎: " + user_name + " 登錄聊天室";
users.add(socketChannel.getRemoteAddress().toString() + "===" + user_name); // 客戶端地址和用戶名拼接在一起作爲唯一標識
brodcast(socketChannel, user_msg);
}
else if (user_msg.equals("1")){ // 顯示在線人數
user_msg = onlineUser();
write(socketChannel, user_msg);
}
else { // 羣聊
String user = "";
for (String s: users) {
if (s.contains(socketChannel.toString())){
String[] s1 = s.split("===");
if (s1.length == 2){
user = "用戶" + s1[1] + "對大家說:";
}else{
continue;
}
}
}
brodcast(socketChannel, user + user_msg);
}
}
private void write(SocketChannel channel, String content) throws IOException, ClosedChannelException {
sendBuffer.clear();
sendBuffer.put(content.getBytes());
sendBuffer.flip();
channel.write(sendBuffer);
//註冊讀操作 下一次進行讀
channel.register(selector, SelectionKey.OP_READ);
}
/**
* 用戶下線,同時通知線上用戶哪些用戶下線了。
*/
public void offlineUser(SelectionKey key) throws IOException{
SocketChannel socketChannel = (SocketChannel) key.channel();
for (String user: users){
String[] s1 = user.split("===");
if (s1.length == 2){
String user_name = s1[1];
if (user.contains(socketChannel.getRemoteAddress().toString())){
users.remove(user);
String message = "用戶: " + user_name + " 下線了, 拜拜";
brodcast(socketChannel, message);
}
}else{
continue;
}
}
}
/**
* 在線用戶
*/
private String onlineUser(){
String online_users = "在線用戶:\n";
String user = "";
for (String s: users) {
String[] s1 = s.split("===");
if (s1.length == 2){
user = s1[1];
}else{
continue;
}
online_users += "\t" + user + "\n";
}
System.out.println(" " + online_users);
return online_users;
}
/**
* 羣聊
*/
public void brodcast(SocketChannel except, String content) throws IOException{
for (SelectionKey key: selector.keys()) {
Channel targetchannel = key.channel();
System.out.println("broadcast write:" + content);
if(targetchannel instanceof SocketChannel && targetchannel != except) {
SocketChannel channel = (SocketChannel) key.channel();
write(channel, content);
}
}
}
}
客戶端代碼
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
public class chatClient {
private static final String host = "127.0.0.1";
private static final int port = 8081;
private Selector selector;
private SocketChannel sc;
private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
private static final String USER_NAME_TAG = "$%%^&*()!@#$^%#@*()*";
volatile boolean running = true;
private static final Logger LOG = LoggerFactory.getLogger(chatClient.class);
public chatClient() throws IOException{
connect(host, port);
// // 讀寫分離
listen();
Reader reader = new Reader();
reader.start();
}
public static void main(String[] args) throws IOException{
System.out.println("===================================================================================");
System.out.println("輸入1: 顯示在線用戶");
System.out.println("===================================================================================");
new chatClient();
}
public void connect(String host, int port) {
try {
sc = SocketChannel.open();
sc.configureBlocking(false);
sc.connect(new InetSocketAddress(host, port));
this.selector = Selector.open();
sc.register( selector, SelectionKey.OP_CONNECT); //將channel註冊到selector中
} catch (IOException e) {
e.printStackTrace();
}
}
public void listen() {
Scanner scanner = new Scanner(System.in);
while (true) {
try {
int events = selector.select();
if (events > 0) {
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
while (selectionKeys.hasNext()) {
SelectionKey selectionKey = selectionKeys.next();
selectionKeys.remove();
//連接事件
if (selectionKey.isConnectable()) {
sc.finishConnect();
System.out.println("server connected...");
// 人員登錄
login(scanner);
//註冊寫操作
sc.register(selector, SelectionKey.OP_WRITE);
break;
}
else if (selectionKey.isWritable()){
String message = scanner.nextLine();
writeBuffer.clear();
writeBuffer.put(message.getBytes());
//將緩衝區各標誌復位,因爲向裏面put了數據標誌被改變要想從中讀取數據發向服務器,就要復位
writeBuffer.flip();
sc.write(writeBuffer);
sc.register(selector, SelectionKey.OP_WRITE);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void login(Scanner scanner) throws IOException{
System.out.println("請輸入登錄名: ");
String message = scanner.nextLine();
writeBuffer.clear();
writeBuffer.put((USER_NAME_TAG + message).getBytes());
//將緩衝區各標誌復位,因爲向裏面put了數據標誌被改變要想從中讀取數據發向服務器,就要復位
writeBuffer.flip();
sc.write(writeBuffer);
}
protected class Reader extends Thread {
private final Selector writeSelector;
Reader() throws IOException {
this.setName("Reader");
this.setDaemon(true);
writeSelector = Selector.open(); // create a selector
sc.register(writeSelector, SelectionKey.OP_READ);
}
@Override
public void run() {
try {
doRunLoop();
} finally {
LOG.info(getName() + ": stopping");
try {
writeSelector.close();
} catch (IOException ioe) {
LOG.error(getName() + ": couldn't close write selector", ioe);
}
}
}
private void doRunLoop() {
while (running) {
try {
int keyCt = writeSelector.select();
if (keyCt == 0) {
continue;
}
Set<SelectionKey> keys = writeSelector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
try {
if (key.isValid() && key.isReadable()) {
Thread.sleep(1);
SocketChannel client = (SocketChannel) key.channel();
//將緩衝區清空以備下次讀取
readBuffer.clear();
int num = client.read(readBuffer);
System.out.println(new String(readBuffer.array(),0, num));
//註冊讀操作,下一次讀
sc.register(selector, SelectionKey.OP_READ);
}
} catch (IOException e) {
LOG.debug(getName() + ": Reader", e);
}
}
} catch (Exception e) {
LOG.warn(getName() + ": exception in Reader " + e);
}
}
}
}
}