1.服务器端代码

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* 聊天室服务器
* 网络多客户端聊天室
* 功能1: 客户端经过Java NIO链接到服务端,支持多客户端的链接
* 功能2:客户端初次链接时,服务端提示输入昵称,若是昵称已经有人使用,提示从新输入,若是昵称惟一,则登陆成功,以后发送消息都须要按照规定格式带着昵称发送消息
* 功能3:客户端登陆后,发送已经设置好的欢迎信息和在线人数给客户端,而且通知其余客户端该客户端上线
* 功能4:服务器收到已登陆客户端输入内容,转发至其余登陆客户端。
* @author 1
* @date Aug 22, 2016 4:02:01 PM
*/
public class ChatRoomServer {
/** 选择器 */
private Selector selector;
/*****端口号*****/
private final static int PORT=9900;
/*******在线统计人名或人数********/
private HashSet online = new HashSet();
/****编码*****/
private Charset charset = Charset.forName("UTF-8");
/****用户存在提示信息*****/
private static String USER_EXIST = "system message: user exist, please change a name";
/****至关于自定义协议格式,与客户端协商好*****/
private static String USER_CONTENT_SPILIT = "#@#";
public static void main(String[] args) {
try {
new ChatRoomServer().init();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 初始化服务器
* @author 1
* @throws IOException
*/
public void init() throws IOException{
//打开选择器
this.selector = Selector.open();
// 开启服务器端通道,并指定端口号
ServerSocketChannel server = ServerSocketChannel.open();
ServerSocket serverSocket = server.socket();
InetSocketAddress address = new InetSocketAddress(PORT);
serverSocket.bind(address);
server.configureBlocking(false);
//将选择器注册到服务器通道上
server.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("server is linstening...");
//等待客户端的链接
while(true){
int nums = this.selector.select();
if (nums<=0) {
continue;
}
//存在链接
Set selectionKeys = this.selector.selectedKeys();
Iterator iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
//获得当前的选择键
SelectionKey key = iterator.next();
iterator.remove();
//处理当前的选择键
dealWithSelectionKey(server,key);
}
}
}
/**
*
* @param server
* @param key
* @author 1
* @throws IOException
*/
private void dealWithSelectionKey(ServerSocketChannel server, SelectionKey key) throws IOException {
if (key.isAcceptable()) {
//接收客户端
SocketChannel sChannel = server.accept();
//设置非阻塞
sChannel.configureBlocking(false);
//注册选择器,并设置为读取模式,收到一个链接请求,而后起一个SocketChannel,并注册到selector上,以后这个链接的数据,就由这个socketchannel处理。
sChannel.register(selector, SelectionKey.OP_READ);
//将此对应的channel设置为准备接收其余客户端的请求
key.interestOps(SelectionKey.OP_ACCEPT);
System.out.println("Server is listening from client :" + sChannel.socket().getRemoteSocketAddress());
sChannel.write(charset.encode("Please input your name:"));
}
//处理来自客户端的数据读取请求
else if (key.isReadable()) {
//获得该key对应的channel,其中有数据须要读取
SocketChannel sc = (SocketChannel) key.channel();
StringBuffer content = new StringBuffer();
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
//获得客户端传过来的消息
while(sc.read(buffer)>0){
buffer.flip();
content.append(charset.decode(buffer));
}
//将此对应的channel设置为准备下一次接受数据
key.interestOps(SelectionKey.OP_READ);
} catch (Exception e) {
e.printStackTrace();
key.cancel();
sc.close();
}
//若是内容不为空
if (content.length()>0) {
//拆分规则
String[] msgArr = content.toString().split(USER_CONTENT_SPILIT);
//注册名字
if (msgArr!=null && msgArr.length==1) {
//用户已经存在,则直接返回
if (online.contains(msgArr[0])) {
sc.write(charset.encode(USER_EXIST));
}else {
String name = msgArr[0];
online.add(name);
int onlineNum = this.onlineTotal();
String msg = "welcome "+name+" to chat room,current online people num is:"+onlineNum;
//通知全部的人
broadCast(selector, null, msg);
}
}
//聊天内容
else if (msgArr!=null && msgArr.length>1) {
String name = msgArr[0];
String message = content.substring(name.length()+USER_CONTENT_SPILIT.length());
message = name + " say " + message;
if(online.contains(name)) {
//不回发给发送此内容的客户端
broadCast(selector, sc, message);
}
}
}
}
}
/**
* 通知全部人
* @param selector 选择器
* @param sc 不通知的客户端
* @param msg 消息
* @author 1
* @throws IOException
*/
private void broadCast(Selector selector, SocketChannel except, String msg) throws IOException {
for(SelectionKey key : selector.keys()){
Channel channel = key.channel();
if (channel instanceof SocketChannel && channel != except) {
SocketChannel socketChannel = (SocketChannel) channel;
socketChannel.write(charset.encode(msg));
}
}
}
/**
* 获得在线总人数
* @return
* @author 1
*/
private int onlineTotal() {
int num=0;
for(SelectionKey key : this.selector.keys()){
Channel targetchannel = key.channel();
if (targetchannel instanceof SocketChannel) {
num++;
}
}
return num;
}
}

2.客户端代码

package com.hq.io.socket;
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.nio.charset.Charset;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
/**
*
* @author 1
* @date Aug 22, 2016 4:02:01 PM
*/
public class ChatRoomClient {
private Selector selector = null;
static final int port = 9900;
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();
}
}