作业要求:
使用java nio 实现一个CS 架构的通讯工具,服务器端:监控客户 端上线、离线、转发消息,客户端:发送、接受消息
一、功能需求
- 客户端: 发送消息,接收消息 ------ 发送数据与接收数据
- 服务器端: 监控客户端上线,离线,转发消息 ------ Selector(选择器)
二、代码
1.Server端
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();
}
}
2.Client端代码
package demo;
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 ChatClient {
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();
}
}
总结
自己没写出来,学习NIO之后知道这个东西的原理是什么了,但是代码就只能很磕磕绊绊的写出来服务器的初始化和监听,别的没写出来,看代码的话能理解大概……
学习NIO参考的是这个:原文链接:https://blog.csdn.net/guorui_java/article/details/123589971 和 微信图书
代码参考的是:原文链接:https://blog.csdn.net/guorui_java/article/details/123589971 和 原文链接:https://blog.csdn.net/RenJianLeLe/article/details/106977914