描述:多用户的聊天系统,支持用户登录,一对一消息,一对多的消息,广播消息。
实际:用户登录我还没做,没用到数据库,可视化界面我也没做,以后有机会再写吧。我写的代码逻辑很啰嗦,我认为不美观,请大佬们指教。
代码:
- GroupChatServer.java
package com.atguigu.netty.groupchat;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/**
* @Author: Liyutian
* @Date:Create:in 2020/12/19 9:44
*/
public class GroupChatServer {
private int PORT;
public GroupChatServer(int port){
this.PORT = port;
}
//编写run方法,处理客户端请求
public void run() {
//创建两个线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//获取到 pipeline
ChannelPipeline pipeline = ch.pipeline();
//向 pipeline 中加入解码器
pipeline.addLast("decoder",new StringDecoder());
//向 pipeline 中加入编码器
pipeline.addLast("encoder",new StringEncoder());
//加入自己的业务处理 handler
pipeline.addLast(new GroupChatServerHandler());
}
});
System.out.println("netty 服务器启动");
ChannelFuture channelFuture = b.bind(PORT).sync();
//监听关闭
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
new GroupChatServer(7000).run();
}
}
- GroupChatServerHandler.java
package com.atguigu.netty.groupchat;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
/**
* @Author: Liyutian
* @Date:Create:in 2020/12/19 10:01
*/
public class GroupChatServerHandler extends SimpleChannelInboundHandler<String>{
//定义一个 channel 组,管理所有的 channel
//GlobalEventExecutor 是全局的事件执行器,是一个单例
//用在广播消息
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
//用在一对一,一对多信息发送
public static ConcurrentMap<String,Channel> channels = new ConcurrentHashMap<>();
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//handlerAdded 表示连接建立,一旦连接,第一个被执行
//将当前的 channel 加入到 channelGroup
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
//将该用户加入聊天的信息推送给其他在线的客户端
//该方法会将 channelGroup 中所有的channel遍历,并发送信息,不需要自己遍历
channelGroup.writeAndFlush("【客户端】"+channel.remoteAddress().toString().substring(1)+" 加入聊天 "+sdf.format(new Date())+"\n");
channelGroup.add(channel);
channels.put(channel.remoteAddress().toString().substring(1),channel);
Util util = new Util();
util.addId(channel.remoteAddress().toString().substring(1));
if (channels.size() != util.getList().size()){
util.clearList();
util.addId(channel.remoteAddress().toString().substring(1));
}
util.save();
}
//断开连接,将离开信息推送给其他当前在线的客户端
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush("【客户端】"+channel.remoteAddress().toString().substring(1)+" 离线了 "+sdf.format(new Date())+"\n");
channels.remove(channel.remoteAddress().toString().substring(1));
Util util = new Util();
util.removeId(channel.remoteAddress().toString().substring(1));
util.save();
}
//表示 channel 处于活动状态,提示上线
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel().remoteAddress().toString().substring(1)+" 上线了……"+sdf.format(new Date()));
}
//表示 channel 处于非活动状态,提示下线
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel().remoteAddress().toString().substring(1)+" 离线了……"+sdf.format(new Date()));
}
//读取数据
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
//获取当前channel
Channel channel = ctx.channel();
Util utilA = new Util();
if (utilA.getFlag()) {//启用广播消息
//这时我们遍历 channelGroup ,根据不同的情况,回送不同的消息
channelGroup.forEach(ch->{
if (ch!=channel){
ch.writeAndFlush("【广播消息】[客户端]"+channel.remoteAddress().toString().substring(1)+" 发送:"+msg+" ||"+sdf.format(new Date())+"\n");
}else{
ch.writeAndFlush("发送成功。\n");
}
});
utilA.setFlag(false);
utilA.save();
}else {//不启用广播消息
//进行一对一、一对多发送信息的处理
Util utilB = new Util();
ConcurrentLinkedQueue<String> queue = utilB.getQueue();
if (queue.size()==1){//一对一信息发送
channel.writeAndFlush("发送成功。\n");
channels.get(queue.poll()).writeAndFlush("【一对一消息】[客户端]"+channel.remoteAddress().toString().substring(1)+" 发送:"+msg+" ||"+sdf.format(new Date())+"\n");
}else {//一对多发送信息
channel.writeAndFlush("发送成功。\n");
queue.forEach(id->{channels.get(id).writeAndFlush("【一对多信息】[客户端]"+channel.remoteAddress().toString().substring(1)+" 发送:"+msg+" ||"+sdf.format(new Date())+"\n");});
}
utilB.clearQueue();
utilB.save();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//关闭通道
ctx.close();
}
}
- GroupChatClient.java
package com.atguigu.netty.groupchat;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
/**
* @Author: Liyutian
* @Date:Create:in 2020/12/19 10:56
*/
public class GroupChatClient {
private final String HOST;
private final int PORT;
public GroupChatClient(String host, int port) {
HOST = host;
PORT = port;
}
public void run(){
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventExecutors)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//得到 pipeline
ChannelPipeline pipeline = ch.pipeline();
//加入相关的 handler
pipeline.addLast("decoder",new StringDecoder());
pipeline.addLast("encoder",new StringEncoder());
pipeline.addLast(new GroupChatClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect(HOST, PORT).sync();
Channel channel = channelFuture.channel();
System.out.println("……"+channel.localAddress().toString().substring(1)+"登录成功……");
String self = channel.localAddress().toString().substring(1);
System.out.println("你的ID号是:"+self);
command(self,channel);
System.out.println("正在退出……");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
eventExecutors.shutdownGracefully();
}
}
private void command(String self,Channel channel){
System.out.print("1.发送一对一消息\n2.发送一对多的消息\n3.发送广播消息\n4.查询在线用户id\n5.退出\n");
Scanner sc = new Scanner(System.in);
while (sc.hasNextInt()){
int comm = sc.nextInt();
if (comm==1){
Util utilA = new Util();
//发送一对一消息
utilA.clearQueue();//清空目标用户的id队列
System.out.print("请输入目标用户的id:");
String targetId = new Scanner(System.in).nextLine();
if (!utilA.getList().contains(targetId)){
System.out.println(targetId+"用户id不存在!");
continue;
}
utilA.addQueue(targetId);
utilA.save();
System.out.print("发送:");
String msg = new Scanner(System.in).nextLine();
//通过 channel 发送到服务器
channel.writeAndFlush(msg);
}else if (comm==2){
//发送一对多的消息
Util utilB = new Util();
utilB.clearQueue();//清空目标用户的id队列
System.out.print("请输入目标用户们的id:(id与id之间用空格分隔)");
String[] targetIds = new Scanner(System.in).nextLine().split(" ");
boolean a=false;
for (String id : targetIds) {
if (!utilB.getList().contains(id)){
System.out.println(id+" 用户不存在!");
a=true;
}
}
if (a){continue;}
Arrays.asList(targetIds).forEach(e->utilB.addQueue(e));
utilB.save();
System.out.print("发送:");
String msg = new Scanner(System.in).nextLine();
//通过 channel 发送到服务器
channel.writeAndFlush(msg);
}else if (comm==3){
//发送广播消息
Util utilC = new Util();
utilC.setFlag(true);
utilC.save();
System.out.print("发送:");
String msg = new Scanner(System.in).nextLine();
//通过 channel 发送到服务器
channel.writeAndFlush(msg);
}else if (comm==4){
Util utilD = new Util();
List<String> list = utilD.getList();
if (list.size()>1) {
list.forEach(id->{
if (!id.equals(self)){
System.out.println(id);
}
});
}else {
System.out.println("当前没有其他用户登录。");
}
}else {
//退出
return;
}
}
}
public static void main(String[] args) {
new GroupChatClient("127.0.0.1",7000).run();
}
}
- GroupChatClientHandler.java
package com.atguigu.netty.groupchat;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* @Author: Liyutian
* @Date:Create:in 2020/12/19 11:18
*/
public class GroupChatClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg.trim());
}
}
- Util
package com.atguigu.netty.groupchat;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.*;
import java.util.stream.Collectors;
/**
* @Author: Liyutian
* @Date:Create:in 2020/12/19 12:18
*/
public class Util {
//true:启用广播信息,false:不启用
private boolean flag=false;//初始化不启用广播消息
//集合用来存储要给谁发信息的目标客户端的id
private ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
//用来存放已登录的客户端的id
private List<String> list = new ArrayList<>();
private static Properties properties = new Properties();
public Util(){
File file = new File("info.properties");
if (file.exists()){
try {
properties.load(new FileInputStream(file));
flag = Boolean.valueOf((String)properties.get("flag"));
String a = (String) properties.get("queue");
if (!a.equals("[]")){
String[] aa = a.substring(1).split("]")[0].split(",");
queue.addAll(Arrays.stream(aa).map(e->e.trim()).collect(Collectors.toList()));
}
String b = (String) properties.get("list");
if (!b.equals("[]")){
String[] bb = b.substring(1).split("]")[0].split(",");
list.addAll(Arrays.stream(bb).map(e->e.trim()).collect(Collectors.toList()));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//保存信息
public void save(){
FileOutputStream fos = null;
try {
fos = new FileOutputStream("info.properties");
fos.write(("flag="+flag+"\r\n").getBytes());
fos.write(("queue="+queue.toString()+"\r\n").getBytes());
fos.write(("list="+list.toString()+"\r\n").getBytes());
fos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
assert fos != null;
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//获取目标客户端的id集合
public ConcurrentLinkedQueue<String> getQueue(){
return queue;
}
//添加目标客户端的id
public void addQueue(String id){
queue.add(id);
}
//清空目标客户端的id集合
public void clearQueue(){
queue.clear();
}
//获取录客户端的id集合
public List<String> getList(){
return list;
}
//添加登录客户端的id
public void addId(String id){
list.add(id);
}
//移除离线的客户端id
public void removeId(String id){
list.remove(id);
}
public void setFlag(boolean f){
flag=f;
}
public boolean getFlag(){
return flag;
}
public void clearList() {
list.clear();
}
}
演示:
服务器:
发送广播消息:
发送一对多的消息:
发送一对一消息: