【Java】NIO 客户端-服务器 聊天 例子

6 篇文章 0 订阅

想写一个简单的聊天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();
		}
	}
}
这次虽然写出来了,但是感觉还有好多地方需要理解和改进。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值