实例要求:
- 编写一个NIO群聊系统,实现服务器端和多个客户端之间的数据简单通讯(非阻塞);
- 实现多人群聊;
- 服务器端:可以监测用户上线、离线,并实现消息转发功能;
- 客户端:通过channel可以无阻塞发送消息给其他所有用户,同时接受其他用户发送的消息;
- 目的:进一步理解NIO非阻塞网络编程机制;
代码演示:
服务端代码:
package com.study.demo.groupchat;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;public class GroupChatServer {//定义属性private Selector selector;private ServerSocketChannel listenChannel;private static final int PORT = 6667;//构造器//初始化工作public GroupChatServer() {try {//得到选择器selector = Selector.open();//ServerSocketChannellistenChannel = ServerSocketChannel.open();//绑定端口listenChannel.socket().bind(new InetSocketAddress(PORT));//设置非阻塞模式listenChannel.configureBlocking(false);//将该listenChannel 注册到selectorlistenChannel.register(selector, SelectionKey.OP_ACCEPT);} catch (IOException e) {e.printStackTrace();}}//监听public void listen() {try {while (true) {int count = selector.select();if (count > 0) {//有事件需要处理//遍历得到selectionKey集合Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {//获取selectionKeySelectionKey key = iterator.next();//监听acceptif(key.isAcceptable()) {SocketChannel sc = listenChannel.accept();sc.configureBlocking(false);//将该sc 注册到selectorsc.register(selector, SelectionKey.OP_READ);System.out.println(sc.getRemoteAddress() + "上线了");}//通道发送read事件,即通道是可读的操作if(key.isReadable()) {readDate(key);}//把当前的key删除 防止重复操作iterator.remove();}} else {System.out.println("等等.....");}}} catch (Exception e) {e.printStackTrace();}}//读取客户端消息private void readDate(SelectionKey key) {//定一个SocketChannelSocketChannel channel = null;try {//得到channelchannel = (SocketChannel) key.channel();//创建一个bufferByteBuffer buffer = ByteBuffer.allocate(1024);int count = channel.read(buffer);if (count > 0) {//把缓存区的数据转成字符串String msg = new String(buffer.array());//输出消息System.out.println("from 客户端:" + msg);//向其他的客户端转发消息,再写一个方法来处理sendInfoToOtherClients(msg, channel);}} catch (IOException e) {try {System.out.println(channel.getRemoteAddress() + " 离线了");//取消注册key.cancel();//关闭通道channel.close();} catch (IOException e1) {e1.printStackTrace();}e.printStackTrace();}}//转发给其他客户端(通道)private void sendInfoToOtherClients(String msg, SocketChannel self) throws IOException {System.out.println("服务器转发消息中...");//遍历 所有注册到selector上的SocketChannel,并排除selffor (SelectionKey key : selector.keys()) {//通过key 获取对应的SocketChannelChannel targetChannel = key.channel();//排除自己if(targetChannel instanceof SocketChannel && targetChannel != self) {//转型SocketChannel dest = (SocketChannel) targetChannel;//将msg存储到bufferByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());//将buffer的数据写入通道dest.write(buffer);}}}public static void main(String[] args) {//创建一个服务器对象GroupChatServer server = new GroupChatServer();server.listen();}}
客户端代码:
package com.study.demo.groupchat;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 GroupChatClient {//定义属性private final String HOST = "127.0.0.1";private final int PORT = 6667;private Selector selector;private SocketChannel socketChannel;private String username;//构造器public GroupChatClient() throws IOException {selector = Selector.open();//连接服务器socketChannel = socketChannel.open(new InetSocketAddress(HOST, PORT));//设置非阻塞socketChannel.configureBlocking(false);//将channel注册到selectorsocketChannel.register(selector, SelectionKey.OP_READ);//得到usernameusername = socketChannel.getLocalAddress().toString().substring(1);System.out.println(username + " is ok ...");}//向服务器发送消息public void sendInfo(String info) {info = username + " 说:" + info;try {socketChannel.write(ByteBuffer.wrap(info.getBytes()));} catch (Exception e) {e.printStackTrace();}}//读取从服务器端回复的消息public void readInfo() {try {int readChannels = selector.select();if (readChannels > 0) {//有可用的通道Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();if (key.isReadable()) {//得到相关的通道SocketChannel sc = (SocketChannel) key.channel();//得到一个bufferByteBuffer buffer = ByteBuffer.allocate(1024);//读取sc.read(buffer);//把读取的缓冲区的数据转成字符串String msg = new String(buffer.array());System.out.println(msg.trim());}}iterator.remove();}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {//启动客户端GroupChatClient chatClient = new GroupChatClient();//启动一个线程 每隔3秒 读取从服务器发送的数据new Thread() {@Overridepublic void run() {while (true) {chatClient.readInfo();try {Thread.currentThread().sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}}}.start();//发送数据给服务端Scanner scanner = new Scanner(System.in);while (scanner.hasNextLine()) {String s = scanner.nextLine();chatClient.sendInfo(s);}}}
演示效果:
server服务端输出:
/127.0.0.1:58743上线了
/127.0.0.1:56568上线了
/127.0.0.1:53544上线了
from 客户端:127.0.0.1:58743 说:我是 743
服务器转发消息中...
from 客户端:127.0.0.1:56568 说:我是 568
服务器转发消息中...
from 客户端:127.0.0.1:53544 说:我是544
服务器转发消息中...
/127.0.0.1:53544 离线了
client1客户端输出:
127.0.0.1:58743 is ok ...
我是 743
127.0.0.1:56568 说:我是 568
127.0.0.1:53544 说:我是544
clien2客户端输出:
127.0.0.1:56568 is ok ...
127.0.0.1:58743 说:我是 743
我是 568
127.0.0.1:53544 说:我是544
下一篇: Netty学习笔记:三、NIO零拷贝