java NIO群聊系统

demo要求:

1)编写一个NIO群聊系统,实现服务器端和客户端之间的数据简单通讯(非阻塞)

2)实现多人群聊

3)服务器端:可以监测用户上线,离线,并实现消息转发功能。

4)客户端:通过channel可以无阻塞发送消息给其他所有用户(客户端),同时可以接受其他用户发送的消息(由服务器转发得到)

5)目的:进一步理解NIO非阻塞网络编程机制。

从Netty的Reactor模式看demo是单Reactor单线程模式。其实Netty是在NIO1.0的基础上封装了复杂的调用操作,解决了JDK1.6的NIO臭名昭著的Epoll Bug,方便程序员进行网络编程,提升效率。

以下代码:

服务器端实现的代码:

java 复制代码
package com.tfq.netty.nio.groupchat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * @author: fqtang
 * @date: 2024/03/19/11:22
 * @description: 服务器端
 */
public class GroupChatServer {

	//定义属性
	private ServerSocketChannel listenChannel;
	private Selector selector;
	private static final int PORT = 6667;

	//构造器
	public GroupChatServer() {
		try {
			//得到选择器
			this.selector = Selector.open();
			//获取监听通道
			this.listenChannel = ServerSocketChannel.open();
			//绑定端口
			this.listenChannel.socket()
				.bind(new InetSocketAddress(PORT));
			//设置通道为非阻塞
			this.listenChannel.configureBlocking(false);
			//将该listenChannel注册到selector
			this.listenChannel.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()) {
						//取出selectionkey
						SelectionKey key = iterator.next();
						//监听到accept
						if(key.isAcceptable()) {
							SocketChannel sc = listenChannel.accept();
							sc.configureBlocking(false);
							//将sc注册到Selector
							sc.register(selector, SelectionKey.OP_READ);
							System.out.println(sc.getRemoteAddress() + " 上线");
						}

						if(key.isReadable()) {//通道发送read事件,即通道是可读的状态
							//处理读
							readDate(key);
						}
						//当前的Key删除,防止重复处理
						iterator.remove();
					}
				}
			}
		} catch(IOException e) {
			e.printStackTrace();
		} finally {
			try {
				listenChannel.close();
			} catch(IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	/**
	 * 读取客户端的消息
	 */
	private void readDate(SelectionKey key) {
		//定义一个SocketChannel
		SocketChannel channel = null;
		try {
			//得到channel
			channel = (SocketChannel) key.channel();
			//创建buffer
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			int count = channel.read(buffer);
			if(count > 0) {
				//把缓冲区的数据转成字符串
				String msg = new String(buffer.array());
				System.out.println("from 客户端发送的消息:" + msg);

				//向其他的客户端转发消息
				sendMsgToOtherClients(channel, msg);
			}
		} catch(Exception e) {
			try {
				System.out.println(channel.getRemoteAddress() + "已离线了");
				//取消注册
				key.cancel();
				//关闭通道
				channel.close();
			} catch(IOException ex) {
				throw new RuntimeException(ex);
			}
		}

	}

	/**
	 * 转发消息给其他通道,排除自己
	 * @param selfChannel
	 * @param msg
	 */
	private void sendMsgToOtherClients(SocketChannel selfChannel,String msg) throws IOException {
		System.out.println("服务器转发消息中.....");
		//遍历 所有注册到selector 上的SocketChannel,并排除seflChannel
		for(SelectionKey key: selector.keys()){
			//通过key 取出对应的SocketChannel
			Channel targetChannel = key.channel();
			//排除自己
			if(targetChannel instanceof  SocketChannel && targetChannel != selfChannel ){
				//转型
				SocketChannel dest = (SocketChannel) targetChannel;
				//将数据存储到buffer。写入buffer
				ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
				//将buffer 的数据写入通道
				dest.write(byteBuffer);
			}
		}
	}

	public static void main(String[] args) {
		GroupChatServer groupChatServer = new GroupChatServer();
		groupChatServer.listen();
	}

}

客户端代码如下:

java 复制代码
package com.tfq.netty.nio.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;

/**
 * @author: fqtang
 * @date: 2024/03/19/14:14
 * @description: 描述
 */
public class GroupChatClient {

	//定义属性
	private final String HOST = "127.0.0.1";
	private final int PORT = 6667;
	private SocketChannel sc;
	private Selector selector;
	private String userName;

	public GroupChatClient() throws IOException {
		selector = Selector.open();
		//连接服务器
		sc = sc.open(new InetSocketAddress(HOST, PORT));
		//设置非阻塞
		sc.configureBlocking(false);
		//将channel注册到selector
		sc.register(selector, SelectionKey.OP_READ);
		//得到username
		userName = sc.getLocalAddress()
			.toString()
			.substring(1);
		System.out.println(userName + " is ok.....");
	}

	/**
	 * 向服务器发送消息
	 *
	 * @param info
	 */
	public void sendMsg(String info) {
		info = userName + " 说: " + info;
		try {
			sc.write(ByteBuffer.wrap(info.getBytes()));
		} catch(IOException e) {
			e.printStackTrace();
		}

	}

	/**
	 * 读取服务器端的数据
	 */
	public void readMsg() {
		try {
			int readChannel = selector.select();
			if(readChannel > 0) {//有可用通道
				Iterator<SelectionKey> iterator = selector.selectedKeys()
					.iterator();
				while(iterator.hasNext()) {
					SelectionKey key = iterator.next();
					if(key.isReadable()) {
						//得到相关通道
						SocketChannel socketChannel = (SocketChannel) key.channel();
						//得到一个Buffer
						ByteBuffer buffer = ByteBuffer.allocate(1024);
						//读取
						socketChannel.read(buffer);

						System.out.println("读取数据:" + new String(buffer.array()));
					}
				}
				//删除当前的selectionKey,防止重复操作,若不清空,其他客户端收到不到最新消息数据
				iterator.remove();
			}
		} catch(IOException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws IOException {
		GroupChatClient groupChatClient = new GroupChatClient();

		//启动一个线程,每隔3秒读取发送的数据
		new Thread() {
			public void run() {
				while(true){
					groupChatClient.readMsg();
					try {
						Thread.sleep(3000);
					} catch(InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}.start();

		//发送数据给服务器端
		Scanner scanner =  new Scanner(System.in);
		while(scanner.hasNextLine()){
			String s = scanner.nextLine();
			groupChatClient.sendMsg(s);
		}
	}

}

通过idea运行GroupChatClient.java,多开几个客户端实现。实现如下截图:

**总结:**服务器端用一个线程通过多路复用器搞定所有的IO操作(包括连接、读、写等),编码简单,清晰明了,但是如果客户端连接数量较多,将无法支撑。要使用单Reactor多线程解决。

若有问题请留言。

相关推荐
云间月131412 分钟前
飞算JavaAI智慧文旅场景实践:从景区管理到游客服务的全链路系统搭建
java·开发语言
盖世英雄酱5813612 分钟前
必须掌握的【InheritableThreadLocal】
java·后端
找不到、了18 分钟前
JVM的逃逸分析深入学习
java·jvm
赏点剩饭77828 分钟前
linux中的hostpath卷、nfs卷以及静态持久卷的区别
linux·运维·服务器
用户03321266636732 分钟前
Java 查找并替换 PDF 中的文本:高效自动化处理指南
java
神鸟云35 分钟前
DELL服务器 R系列 IPMI的配置
linux·运维·服务器·网络·边缘计算·pcdn
叽哥42 分钟前
Kotlin学习第 1 课:Kotlin 入门准备:搭建学习环境与认知基础
android·java·kotlin
herderl1 小时前
**僵尸进程(Zombie Process)** 和**孤儿进程(Orphan Process)**
linux·运维·服务器·网络·网络协议
Hy行者勇哥1 小时前
物联网软件开发过程中,数据流图(DFD),用例图,类图,活动图,序列图,状态图,实体关系图(ERD),BPMN(业务流程建模)详解分析
java·物联网·struts
tomelrg1 小时前
多台服务器批量发布arcgisserver服务并缓存切片
服务器·python·arcgis