参考链接:
java NIO实例1:http://blog.chinaunix.net/uid-25808509-id-3346228.html
java NIO教程之selector(一系列): http://ifeve.com/selectors/多人聊天室 NIO:http://www.cnblogs.com/yanghuahui/p/3686054.html
public class OpWriteRegister {
private Selector selector;
//注册写事件,需要借助一个缓存,缓存需要写到某个特定通道(key)
private Map> writeCache = new HashMap>();
public void init() throws IOException{
selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8899));
server.configureBlocking(false);
server.register(selector, SelectionKey.OP_ACCEPT);
while(true){
selector.select();
Iterator ikeys = selector.selectedKeys().iterator();
while(ikeys.hasNext()){
SelectionKey key = ikeys.next();
if(key.isAcceptable()){
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);
}
if(key.isReadable()){
SocketChannel client = (SocketChannel) key.channel();
//TODO 将待写出的数据先存如缓存
//writeCache.put(client, List);
}
if(key.isWritable()){
SocketChannel client = (SocketChannel) key.channel();
//TODO 将缓存中对应SocketChannel的数据取出,输出到特定的channel
//List data = writeCache.get(client);
//client.write(data);
}
}
}
}
}
public class MultiClient {
private Selector selector;
public void init() throws IOException{
selector = Selector.open();
//下面是一个selector管理三个客户端连接,实际使用时,三个待连接服务端IP或端口是不同的
SocketChannel client1 = SocketChannel.open(new InetSocketAddress("127.0.0.1",8897));
client1.configureBlocking(false);
client1.register(selector, SelectionKey.OP_READ);
SocketChannel client2 = SocketChannel.open(new InetSocketAddress("127.0.0.1",8898));
client2.configureBlocking(false);
client2.register(selector, SelectionKey.OP_READ);
SocketChannel client3 = SocketChannel.open(new InetSocketAddress("127.0.0.1",8899));
client3.configureBlocking(false);
client3.register(selector, SelectionKey.OP_READ);
//数据处理伪代码
while(true){
selector.select();
Iterator ikeys = selector.selectedKeys().iterator();
while(ikeys.hasNext()){
SelectionKey key = ikeys.next();
if(key.isReadable()){
//TODO 数据处理
}
}
}
}
}
###服务端###
public class ChatServer {
private final int port = 8899;
private final String seperator = "[|]"; //消息分隔符
private final Charset charset = Charset.forName("UTF-8"); //字符集
private ByteBuffer buffer = ByteBuffer.allocate(1024); //缓存
private Map onlineUsers = new HashMap();//将用户对应的channel对应起来
private Selector selector;
private ServerSocketChannel server;
public void startServer() throws IOException{
//NIO server初始化固定流程:5步
selector = Selector.open(); //1.selector open
server = ServerSocketChannel.open(); //2.ServerSocketChannel open
server.bind(new InetSocketAddress(port)); //3.serverChannel绑定端口
server.configureBlocking(false); //4.设置NIO为非阻塞模式
server.register(selector, SelectionKey.OP_ACCEPT);//5.将channel注册在选择器上
//NIO server处理数据固定流程:5步
SocketChannel client;
SelectionKey key;
Iterator iKeys;
while(true){
selector.select(); //1.用select()方法阻塞,一直到有可用连接加入
iKeys = selector.selectedKeys().iterator(); //2.到了这步,说明有可用连接到底,取出所有可用连接
while(iKeys.hasNext()){
key = iKeys.next(); //3.遍历
if(key.isAcceptable()){ //4.对每个连接感兴趣的事做不同的处理
//对于客户端连接,注册到服务端
client = server.accept(); //获取客户端首次连接
client.configureBlocking(false);
//不用注册写,只有当写入量大,或写需要争用时,才考虑注册写事件
client.register(selector, SelectionKey.OP_READ);
System.out.println("+++++客户端:"+client.getRemoteAddress()+",建立连接+++++");
client.write(charset.encode("请输入自定义用户名:"));
}
if(key.isReadable()){
client = (SocketChannel) key.channel();//通过key取得客户端channel
StringBuilder msg = new StringBuilder();
buffer.clear(); //多次使用的缓存,用前要先清空
try{
while(client.read(buffer) > 0){
buffer.flip(); //将写模式转换为读模式
msg.append(charset.decode(buffer));
}
}catch(IOException e){
//如果client.read(buffer)抛出异常,说明此客户端主动断开连接,需做下面处理
client.close(); //关闭channel
key.cancel(); //将channel对应的key置为不可用
onlineUsers.values().remove(client); //将问题连接从map中删除
System.out.println("-----用户'"+key.attachment().toString()+"'退出连接,当前用户列表:"+onlineUsers.keySet().toString()+"-----");
continue; //跳出循环
}
if(msg.length() > 0) this.processMsg(msg.toString(),client,key); //处理消息体
}
iKeys.remove(); //5.处理完一次事件后,要显示的移除
}
}
}
/**
* 处理客户端传来的消息
* @param msg 格式:user_to|body|user_from
* @Key 这里主要用attach()方法,给通道定义一个表示符
* @throws IOException
*/
private void processMsg(String msg, SocketChannel client,SelectionKey key) throws IOException{
String[] ms = msg.split(seperator);
if(ms.length == 1){
String user = ms[0]; //输入的是自定义用户名
if(onlineUsers.containsKey(user)){
client.write(charset.encode("当前用户已存在,请重新输入用户名:"));
}else{
onlineUsers.put(user, client);
key.attach(user); //给通道定义一个表示符
String welCome = "\t欢迎'"+user+"'上线,当前在线人数"+this.getOnLineNum()+"人。用户列表:"+onlineUsers.keySet().toString();
this.broadCast(welCome); //给所用用户推送上线信息,包括自己
}
}else if(ms.length == 3){
String user_to = ms[0];
String msg_body = ms[1];
String user_from = ms[2];
SocketChannel channel_to = onlineUsers.get(user_to);
if(channel_to == null){
client.write(charset.encode("用户'"+user_to+"'不存在,当前用户列表:"+onlineUsers.keySet().toString()));
}else{
channel_to.write(charset.encode("来自'"+user_from+"'的消息:"+msg_body));
}
}
}
//map中的有效数量已被很好的控制,可以从map中获取,也可以用下面的方法取
private int getOnLineNum(){
int count = 0;
Channel channel;
for(SelectionKey k:selector.keys()){
channel = k.channel();
if(channel instanceof SocketChannel){ //排除ServerSocketChannel
count++;
}
}
return count;
}
//广播上线消息
private void broadCast(String msg) throws IOException{
Channel channel;
for(SelectionKey k:selector.keys()){
channel = k.channel();
if(channel instanceof SocketChannel){
SocketChannel client = (SocketChannel) channel;
client.write(charset.encode(msg));
}
}
}
public static void main(String[] args){
try {
new ChatServer().startServer();
} catch (IOException e) {
e.printStackTrace();
}
}
}
###客户端###
public class ChatClient1 {
private final int port = 8899;
private final String seperator = "|";
private final Charset charset = Charset.forName("UTF-8"); //字符集
private ByteBuffer buffer = ByteBuffer.allocate(1024);
private SocketChannel _self;
private Selector selector;
private String name = "";
private boolean flag = true; //服务端断开,客户端的读事件不会一直发生(与服务端不一样)
Scanner scanner = new Scanner(System.in);
public void startClient() throws IOException{
//客户端初始化固定流程:4步
selector = Selector.open(); //1.打开Selector
_self = SocketChannel.open(new InetSocketAddress(port));//2.连接服务端,这里默认本机的IP
_self.configureBlocking(false); //3.配置此channel非阻塞
_self.register(selector, SelectionKey.OP_READ); //4.将channel的读事件注册到选择器
/*
* 因为等待用户输入会导致主线程阻塞
* 所以用主线程处理输入,新开一个线程处理读数据
*/
new Thread(new ClientReadThread()).start(); //开一个异步线程处理读
String input = "";
while(flag){
input = scanner.nextLine();
if("".equals(input)){
System.out.println("不允许输入空串!");
continue;
}else if("".equals(name) && input.split("[|]").length == 1){
name = input;
selector.keys().iterator().next().attach(name); //给通道添加名称
}else if(!"".equals(name) && input.split("[|]").length == 2){
input = input + seperator + name;
}else{
System.out.println("输入不合法,请重新输入:");
continue;
}
try{
_self.write(charset.encode(input));
}catch(Exception e){
System.out.println(e.getMessage()+"客户端主线程退出连接!!");
}
}
}
private class ClientReadThread implements Runnable{
@Override
public void run(){
Iterator ikeys;
SelectionKey key;
SocketChannel client;
try {
while(flag){
selector.select(); //调用此方法一直阻塞,直到有channel可用
ikeys = selector.selectedKeys().iterator();
while(ikeys.hasNext()){
key = ikeys.next();
if(key.isReadable()){ //处理读事件
client = (SocketChannel) key.channel();
//这里的输出是true,从selector的key中获取的客户端channel,是同一个
// System.out.println("client == _self:"+ (client == _self));
buffer.clear();
StringBuilder msg = new StringBuilder();
try{
while(client.read(buffer) > 0){
buffer.flip(); //将写模式转换为读模式
msg.append(charset.decode(buffer));
}
}catch(IOException en){
System.out.println(en.getMessage()+",客户端'"+key.attachment().toString()+"'读线程退出!!");
stopMainThread();
}
System.out.println(msg.toString());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void stopMainThread(){
flag = false;
}
public static void main(String[] args){
try {
new ChatClient1().startClient();
} catch (IOException e) {
e.printStackTrace();
}
}
}