在Java网络编程基础(四)中提到了基于Socket的TCP/IP简单聊天系统实现了一个多客户端之间护法消息的简单聊天系统。其服务端采用了多线程来处理多个客户端的消息发送,并转发给目的用户。但是由于它是基于Socket的,因此是阻塞的。

    本节我们将通过SocketChannel和ServerSocketChannel来实现同样的功能。

    1、客户端输入消息的格式

        username:msg    username表示要发送的的用户名,msg为发送内容,以冒号分割

    2、实现思路

        实现思路与Java网络编程基础(四)的实现思路类似,服务端需要保存一份用户名的列表,以便在转发消息时能够查到对应的用户。对于客户端来说,客户端需要能够随时收取服务端转发来的消息,并能够随时通过键盘输入发送消息。

    3、实现过程

        根据以上思路,客户端需要有独立线程,因此需要实现3个类

        (1)客户端主程序ChatClient.java

            该程序根据启动时输入的用户名参数负责启动客户端子线程,由子线程建立与服务端的连接。在主线程中,需要循环读取键盘的输入,将输入的消息通过子线程发送给服务端,交由服务端来转发该消息给客户端

        (2)客户端子线程ClientThread.java

            该线程封装了客户端与服务端的所有操作

            a、在构造函数中,根据创建与服务端的连接

            b、在线程主函数中建立与服务端的连接事件时,发送用户名给服务器进行注册,格式为:username = XXX(启动参数)

            c、发送消息函数:外部的主线程在接收到键盘输入后,调用该函数给服务端发送消息;消息格式为“to:content”(即username:message),为了服务端能够区分是谁发送给to用户的,需要将用户名usename也加进去。from:to:content

            d、关闭函数:在客户端输入bye命令后,关闭客户端与服务器的连接

        (3)服务端主程序ChatServer.java

            该类负责启动服务端,并负责监听客户端的请求,并负责处理客户端的输入/输出和消息转发任务,因此包含以下功能

            a、启动服务器,并监听客户端的连接

            b、在读取到客户端发送的有“username = XXX”字样的消息时,表示客户端第一次建立的连接,将该用户添加到客户端列表中

            c、读取客户端输入的数据,消息格式类似于"from:to:content",第一个冒号":"前的部分为发送者用户名,第二个冒号前的部分为接收者用户名,最后一部分为消息内容。根据该接受者用户名在客户端列表中找到对应的客户端,发送数据给该客户端。

【服务端代码】

package org.test.nio.chat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
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.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Hashtable;
import java.util.Iterator;

public class ChatServer {

	public static void main(String[] args) {
		// 客户端列表
		Hashtable clietList = new Hashtable();

		Selector selector = null;
		ServerSocketChannel server = null;

		try {
			// 创建一个Selector
			selector = Selector.open();

			// 创建Socket并注册
			server = ServerSocketChannel.open();
			server.configureBlocking(false);
			server.register(selector, SelectionKey.OP_ACCEPT);

			// 启动监听端口
			InetSocketAddress ip = new InetSocketAddress(12345);
			server.socket().bind(ip);
			
			System.out.println("成功启动服务端!");

			// TODO 监听事件
			while (true) {
				// 监听事件
				selector.select();
				// 事件来源列表
				Iterator it = selector.selectedKeys().iterator();
				while (it.hasNext()) {
					SelectionKey key = it.next();
					// 删除该事件
					it.remove();

					// 判断事件类型
					if (key.isAcceptable()) {
						// 连接事件
						ServerSocketChannel server2 = (ServerSocketChannel) key.channel();
						SocketChannel channel = server2.accept();
						channel.configureBlocking(false);
						if (channel.isConnectionPending()) {
							channel.finishConnect();
						}
						channel.register(selector, SelectionKey.OP_READ);
						System.out.println("客户端连接:" + channel.socket().getInetAddress().getHostName()
								+ channel.socket().getPort());
					} else if (key.isReadable()) {
						// 读取数据事件
						SocketChannel channel = (SocketChannel) key.channel();

						// 读取数据
						CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
						ByteBuffer buffer = ByteBuffer.allocate(512);
						channel.read(buffer);
						buffer.flip();
						String msg = decoder.decode(buffer).toString();
						System.out.println("收到:" + msg);

						if(msg.startsWith("username=")){
							String username = msg.replaceAll("username=", "");
							clietList.put(username, channel);
						}else{
							//转发消息给客户端
							String[] arr = msg.split(":");
							if(arr.length == 3){
								String from = arr[0];//发送者
								String to = arr[1];//接受者
								String content = arr[2];//发送内容
								
								if(clietList.containsKey(to)){
									CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
									//给接收者发送消息
									clietList.get(to).write(encoder.encode(CharBuffer.wrap(from+"】"+content)));
								}
							}else{
								String from = arr[0];
								String content = "来自服务器消息:您未指定接收人";
								CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
								//给接收者发送消息
								clietList.get(from).write(encoder.encode(CharBuffer.wrap(content)));
							}
						}
					}
				}
			}
		} catch (ClosedChannelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (CharacterCodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally{
			try {
				selector.close();
				server.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}

        客户端线程

package org.test.nio.chat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;

public class ClientThread extends Thread {
	private CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
	private CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
	private Selector selector = null;
	private SocketChannel socket = null;
	private SelectionKey clientKey = null;
	private String username;
	
	// TODO 启动客户端
	public ClientThread(String username){
		try {
			// 创建Selector
			selector = Selector.open();

			// 创建并注册Socket
			socket = SocketChannel.open();
			socket.configureBlocking(false);
			clientKey = socket.register(selector, SelectionKey.OP_CONNECT);

			// 连接到远程地址
			InetSocketAddress ip = new InetSocketAddress("localhost", 12345);
			socket.connect(ip);
			this.username = username;
			
		} catch (ClosedChannelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		try {
			// 监听事件
			while (true) {
				selector.select(1);
				// 事件来源列表
				Iterator it = selector.selectedKeys().iterator();
				while (it.hasNext()) {
					SelectionKey key = it.next();
					// 删除当前事件
					it.remove();

					// 判断当前事件类型
					if (key.isConnectable()) {
						// 连接事件
						SocketChannel channel = (SocketChannel) key.channel();
						if(channel.isConnectionPending()){
							channel.finishConnect();
						}
						channel.register(selector, SelectionKey.OP_READ);
						System.out.println("连接服务器端成功!");
						
						//发送用户名
						send("username="+this.username);
					} else if (key.isReadable()) {
						// 读取数据事件
						SocketChannel channel = (SocketChannel) key.channel();
//						channel.register(selector, SelectionKey.OP_WRITE);

						// 读取数据
						ByteBuffer buffer = ByteBuffer.allocate(512);
						channel.read(buffer);
						buffer.flip();
						String msg = decoder.decode(buffer).toString();
						System.out.println("【收到:" + msg);
					}
				}
			}
		} catch (ClosedChannelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (CharacterCodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	void send(String msg) {
		// TODO 发送消息
		try {
			SocketChannel channel = (SocketChannel) clientKey.channel();
			channel.write(encoder.encode(CharBuffer.wrap(msg)));
		} catch (CharacterCodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	// TODO 关闭客户端
	public void close() {
		try {
			selector.close();
			socket.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

客户端

package org.test.nio.chat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class ChatClient {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String username = args[0];
		ClientThread client = new ClientThread(username);
		client.start();

		// 输入\输出流
		BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));

		try {
			// 循环读取键盘输入
			String readLine;
			while ((readLine = sin.readLine().trim()) != null) {
				if (readLine.equals("bye")) {
					client.close();
					System.exit(0);
				}
				client.send(username + ":" + readLine);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}

运行结果

服务端

Java网络编程基础(六)— 基于TCP的NIO简单聊天系统_第1张图片

客户端

Java网络编程基础(六)— 基于TCP的NIO简单聊天系统_第2张图片

Java网络编程基础(六)— 基于TCP的NIO简单聊天系统_第3张图片

相对还很简陋,但是确实是NIO的具体应用,喜欢请关注