想写一个简单的聊天C/S,client可以单聊和群发,代码里面有每一次发送的格式。
发现没写过NIO还真不习惯这种模式,总体感觉是不管哪一端,最终都有一个select的循环,然后循环里面会对每一个就绪的key处理,key的处理又是一种宏观的写法,read或者write的处理会包括每一个通道的处理,所以这些函数可能会有很多的case,写起来还是挺费劲的。但是基本还是遵循了服务器的请求-相应模式。就是最开始是一个accept事件,然后有了socket以后,就先注册一个read事件,这个read是用来获取客户端的请求的,然后一旦有了read事件,就需要在处理函数里解析客户端发来的请求,然后再注册一个wirte事件,这样在下一个周期,就会触发write事件,再根据上一次解析的内容把信息写入,最后再重新注册read事件。这是一次请求-响应的基本逻辑,只不过在read函数和write函数里写起来费劲,因为要照顾到所有的channel,感觉分开会更好把。
与传统的io不同点在于,传统io每一次读到客户端请求,就可以直接写内容回去,但是nio无法做到,因为同一个通道的读和写肯定是在两个周期或者两次while循环,这就涉及到了一个如何在第二次的write周期找到上一个read周期解析以后得到的要发送的内容,这个显然需要一个全局的list或者变量来存储,涉及到一些共享变量的东西。个人感觉用多线程做这个比较好把。慢慢研究。
服务端:
package server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
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.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class Server {
private Selector selector;
private static final int PORT = 20001;
Map<String, SelectionKey> map1;
Map<SelectionKey, String> map2;
List<Msg> msgs;
public void connect(SelectionKey key){
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
System.out.println("log: client connects...");
try {
SocketChannel socketChannel = serverChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} catch (ClosedChannelException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void read(SelectionKey key){
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
StringBuffer sb = new StringBuffer();
int c = 0;
try {
while((c = socketChannel.read(buffer)) > 0){
buffer.flip();
int size = buffer.remaining();
byte[] bytes = new byte[size];
buffer.get(bytes, 0, size);
sb.append(new String(bytes));
}
if(c == -1){
System.out.println("log: client closes");
socketChannel.close();
return;
}else{
System.out.println("log: msg-> " + sb);
handleMsg(sb.toString(), key);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void write(SelectionKey key){
SocketChannel sc = (SocketChannel) key.channel();
Iterator<Msg> itr = msgs.iterator();
while(itr.hasNext()){
Msg msg = itr.next();
if(msg.getWho().equals("all") || msg.getWho().equals(map2.get(key))){
ByteBuffer buffer = ByteBuffer.allocate(1024);
String m = "[" + msg.getTime() + "]: " + msg.getMsg();
buffer.put(m.getBytes());
buffer.flip();
try {
sc.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
sc.register(selector, SelectionKey.OP_READ);
} catch (ClosedChannelException e) {
e.printStackTrace();
}
}
//格式: cmd(who):msg
//login:ly
//send(all):hello
//send(ly):hello
private void handleMsg(String msg, SelectionKey key){
String[] parse = msg.split(":");
String cmd = parse[0];
if(cmd.equals("login")){
map1.put(parse[1], key);
map2.put(key, parse[1]);
}else if(cmd.startsWith("send")){
Msg message = new Msg();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = df.format(new Date());
message.setTime(date);
int start = cmd.indexOf('(');
int end = cmd.indexOf(')');
String who = cmd.substring(start + 1, end);
message.setWho(who);
message.setMsg(parse[1]);
msgs.add(message);
if(who.equals("all")){
for(Entry<String, SelectionKey> entry : map1.entrySet()){
SelectionKey sKey = entry.getValue();
SocketChannel sc = (SocketChannel) sKey.channel();
try {
sc.register(selector, sKey.interestOps() | SelectionKey.OP_WRITE);
} catch (ClosedChannelException e) {
e.printStackTrace();
}
}
}else{
SelectionKey sKey = map1.get(who);
SocketChannel sc = (SocketChannel) sKey.channel();
try {
sc.register(selector, sKey.interestOps() | SelectionKey.OP_WRITE);
} catch (ClosedChannelException e) {
e.printStackTrace();
}
}
}else{
//其余的不做处理,仍然监听read
}
}
public void start(){
map1 = new HashMap<>();
map2 = new HashMap<>();
msgs = new LinkedList<>();
try {
this.selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(PORT));
serverChannel.register(selector, SelectionKey.OP_ACCEPT, "server");
System.out.println("log: sever starts...");
boolean write = false;
while(selector.select() > 0){
Iterator<SelectionKey> itr = selector.selectedKeys().iterator();
while(itr.hasNext()){
SelectionKey key = itr.next();
if(key.isAcceptable()){
connect(key);
}else if(key.isReadable()){
read(key);
}else if(key.isWritable()){
write(key);
write = true;
}
itr.remove();
}
if(write)
msgs.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Server().start();
}
}
Msg类:
package server;
public class Msg {
private String msg;
private String who;
private String time;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getWho() {
return who;
}
public void setWho(String who) {
this.who = who;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
}
客户端:
package client;
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;
public class Client {
private Selector selector;
private Scanner sc = new Scanner(System.in);
public static void main(String args[]){
new Client().start();
}
private void start(){
try {
selector = Selector.open();
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress("localhost", 20001));
while(true){
selector.select();
Iterator<SelectionKey> itr = selector.selectedKeys().iterator();
while(itr.hasNext()){
SelectionKey key = itr.next();
SocketChannel channel = (SocketChannel) key.channel();
if(key.isConnectable()){
System.out.println("con");
socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
socketChannel.finishConnect();
}else if(key.isReadable()){
System.out.println("read");
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
String m = new String(buffer.array());
System.out.println(m);
}else{
System.out.println("write");
String m = sc.next();
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(m.getBytes());
buffer.flip();
channel.write(buffer);
}
//itr.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这次虽然写出来了,但是感觉还有好多地方需要理解和改进。