服务端:
package cn.zhangxueliang.herostory.chatroom;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* @ProjectName herostory3
* @ClassName ChatRoomServer
* @Desicription TODO
* @Author Zhang Xueliang
* @Date 2019/12/11 13:58
* @Version 1.0
*
* * 网络多客户端聊天室
* * 功能1: 客户端通过Java NIO连接到服务端,支持多客户端的连接
* * 功能2:客户端初次连接时,服务端提示输入昵称,如果昵称已经有人使用,提示重新输入,如果昵称唯一,则登录成功,之后发送消息都需要按照规定格式带着昵称发送消息
* * 功能3:客户端登录后,发送已经设置好的欢迎信息和在线人数给客户端,并且通知其他客户端该客户端上线
* * 功能4:服务器收到已登录客户端输入内容,转发至其他登录客户端。
* *
* * TODO 客户端下线检测
*
**/
public class ChatRoomServer {
private Selector selector=null;
static final int port=9999;
private Charset charset=Charset.forName("UTF-8");
private static HashSet<String> users = new HashSet<>();
private static String USER_EXIST="system message : user exist, please change a name用户名已存在,请更换一个名称";
private static String USER_CONTENT_SPLIT="#@#";
private static boolean flag=false;
public void init() throws Exception{
selector=Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(port));
server.configureBlocking(false);
server.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server is listening now...服务端正在监听中");
while (true){
int readyChannels = selector.select();
if (readyChannels==0){
continue;
}
Set<SelectionKey> selectionKeys = selector.selectedKeys(); //可以通过这个方法,知道可用通道的集合
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()){
SelectionKey sk = keyIterator.next();
keyIterator.remove();
dealWithSelectionKey(server,sk);
}
}
}
private void dealWithSelectionKey(ServerSocketChannel server, SelectionKey sk) throws IOException {
if (sk.isAcceptable()){
SocketChannel sc = server.accept();
sc.configureBlocking(false);
sc.register(selector,SelectionKey.OP_READ);
sk.interestOps(SelectionKey.OP_ACCEPT);
System.out.println("Server is listening fron client: "+sc.getRemoteAddress());
sc.write(charset.encode("please input your name."));
}
if (sk.isReadable()){
SocketChannel sc = (SocketChannel) sk.channel();
ByteBuffer buff = ByteBuffer.allocate(1024);
StringBuilder content = new StringBuilder();
try{
while (sc.read(buff)>0){
buff.flip();
content.append(charset.decode(buff));
}
System.out.println("Server is listening from client: "+sc.getRemoteAddress()+" data rev is: "+content);
sk.interestOps(SelectionKey.OP_READ);
}catch (IOException io){
sk.cancel();
if (sk.channel()!=null){
sk.channel().close();
}
}
if (content.length()>0){
String[] arrayContent = content.toString().split(USER_CONTENT_SPLIT);
if (arrayContent!=null && arrayContent.length==1){
String name = arrayContent[0];
if (users.contains(name)){
sc.write(charset.encode(USER_EXIST));
}else {
users.add(name);
int num = OnlineNum(selector);
String message = "welcome "+name+" to char room! Online numbers is :"+num;
BroadCast(selector,null,message);
}
}else if (arrayContent!=null && arrayContent.length>1){
String name = arrayContent[0];
String message = content.substring(name.length()+USER_CONTENT_SPLIT.length());
message=name+" say "+message;
if (users.contains(name)){
BroadCast(selector,sc,message);
}
}
}
}
}
private void BroadCast(Selector selector, SocketChannel except, String content) throws IOException {
for (SelectionKey key:selector.keys()){
Channel targetchannel = key.channel();
if (targetchannel instanceof SocketChannel && targetchannel!=except){
SocketChannel dest = (SocketChannel) targetchannel;
dest.write(charset.encode(content));
}
}
}
private int OnlineNum(Selector selector) {
int res=0;
for (SelectionKey key:selector.keys()){
SelectableChannel targetChannel = key.channel();
if (targetChannel instanceof SocketChannel){
res++;
}
}
return res;
}
public static void main(String[] args) throws Exception {
new ChatRoomServer().init();
}
}
客户端:
package cn.zhangxueliang.herostory.chatroom;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
import java.nio.ByteBuffer;
/**
* @ProjectName herostory3
* @ClassName ChatRoomClient
* @Desicription TODO
* @Author Zhang Xueliang
* @Date 2019/12/11 14:33
* @Version 1.0
**/
public class ChatRoomClient {
private Selector selector = null;
static final int port = 9999;
private Charset charset = Charset.forName("UTF-8");
private SocketChannel sc = null;
private String name = "";
private static String USER_EXIST = "system message: user exist, please change a name";
private static String USER_CONTENT_SPILIT = "#@#";
public void init() throws IOException
{
selector = Selector.open();
//连接远程主机的IP和端口
sc = SocketChannel.open(new InetSocketAddress("127.0.0.1",port));
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
//开辟一个新线程来读取从服务器端的数据
new Thread(new ClientThread()).start();
//在主线程中 从键盘读取数据输入到服务器端
Scanner scan = new Scanner(System.in);
while(scan.hasNextLine())
{
String line = scan.nextLine();
if("".equals(line)) continue; //不允许发空消息
if("".equals(name)) {
name = line;
line = name+USER_CONTENT_SPILIT;
} else {
line = name+USER_CONTENT_SPILIT+line;
}
sc.write(charset.encode(line));//sc既能写也能读,这边是写
}
}
private class ClientThread implements Runnable
{
public void run()
{
try
{
while(true) {
int readyChannels = selector.select();
if(readyChannels == 0) continue;
Set selectedKeys = selector.selectedKeys(); //可以通过这个方法,知道可用通道的集合
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey sk = (SelectionKey) keyIterator.next();
keyIterator.remove();
dealWithSelectionKey(sk);
}
}
}
catch (IOException io)
{}
}
private void dealWithSelectionKey(SelectionKey sk) throws IOException {
if(sk.isReadable())
{
//使用 NIO 读取 Channel中的数据,这个和全局变量sc是一样的,因为只注册了一个SocketChannel
//sc既能写也能读,这边是读
SocketChannel sc = (SocketChannel)sk.channel();
ByteBuffer buff = ByteBuffer.allocate(1024);
String content = "";
while(sc.read(buff) > 0)
{
buff.flip();
content += charset.decode(buff);
}
//若系统发送通知名字已经存在,则需要换个昵称
if(USER_EXIST.equals(content)) {
name = "";
}
System.out.println(content);
sk.interestOps(SelectionKey.OP_READ);
}
}
}
public static void main(String[] args) throws IOException
{
new ChatRoomClient().init();
}
}