客户端
package com.zhao.client;
import com.zhao.message.LoginRequestMessage;
import com.zhao.protocol.MessageCodecSharable;
import com.zhao.protocol.ProcotolFrameDecoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.Scanner;
@Slf4j
public class ChatClient {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(group);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProcotolFrameDecoder());
ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
ch.pipeline().addLast("client handler",new ChannelInboundHandlerAdapter(){
// 接收响应消息
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.debug("{}",msg);
}
// 在连接建立之后触发这个 Active 事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 开启新的线程发送消息
new Thread(()->{
Scanner sc = new Scanner(System.in);
System.out.println("情输入用户名");
String username = sc.nextLine();
System.out.println("情输入密码");
String password = sc.nextLine();
LoginRequestMessage message = new LoginRequestMessage(username,password);
// 发送消息
ctx.writeAndFlush(message);
System.out.println("wait...");
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
},"system in").start();
}
});
}
});
Channel channel = bootstrap.connect("localhost", 8080).sync().channel();
channel.closeFuture().sync();
} catch (Exception e) {
log.error("client error", e);
} finally {
group.shutdownGracefully();
}
}
}
服务器端
package com.zhao.server;
import com.zhao.message.LoginRequestMessage;
import com.zhao.message.LoginResponseMessage;
import com.zhao.protocol.MessageCodecSharable;
import com.zhao.protocol.ProcotolFrameDecoder;
import com.zhao.server.service.UserServiceFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ChatServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.group(boss, worker);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProcotolFrameDecoder());
ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
// 只关心 LoginRequestMessage 事件
ch.pipeline().addLast(new SimpleChannelInboundHandler<LoginRequestMessage>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, LoginRequestMessage loginRequestMessage) throws Exception {
String username = loginRequestMessage.getUsername();
String password = loginRequestMessage.getPassword();
// 进行用户名和密码验证
boolean login = UserServiceFactory.getUserService().login(username, password);
LoginResponseMessage message;
if (login) {
message = new LoginResponseMessage(true, "登录成功");
} else {
message = new LoginResponseMessage(false, "用户名或密码错误");
}
// 这条消息会触发出站 handler,先触发 MESSAGE_CODEC 再触发 LOGGING_HANDLER
ctx.writeAndFlush(message);
}
});
}
});
Channel channel = serverBootstrap.bind(8080).sync().channel();
channel.closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
服务器端打印结果
客户端打印结果
两边接收和发送的字节数都是一致的。并且客户端也打印了服务器响应的消息
但是有一个问题?客户端用的是 nio 线程接收消息并打印的。与此同时新开启的 system in线程还处于等待状态呢。解决方式也很简单,引入 CountDownLatch 计数器,当服务端的消息返回就把计数器 减1,此时等待的线程就可以运行了。
修改客户端后代码
package com.zhao.client;
import com.zhao.message.LoginRequestMessage;
import com.zhao.message.LoginResponseMessage;
import com.zhao.protocol.MessageCodecSharable;
import com.zhao.protocol.ProcotolFrameDecoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
@Slf4j
public class ChatClient {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
CountDownLatch WAIT_FOR_LOGIN = new CountDownLatch(1);
// 服务器响应状态
AtomicBoolean LOGIN = new AtomicBoolean(false);
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(group);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProcotolFrameDecoder());
// ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
ch.pipeline().addLast("client handler", new ChannelInboundHandlerAdapter() {
// 接收响应消息
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.debug("{}", msg);
if (msg instanceof LoginResponseMessage) {
LoginResponseMessage response = (LoginResponseMessage) msg;
// 如果登录成功
if (response.isSuccess()) {
LOGIN.set(true);
}
// 计数器 减1,唤醒 system in 线程
WAIT_FOR_LOGIN.countDown();
}
}
// 在连接建立之后触发这个 Active 事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 开启新的线程发送消息
new Thread(() -> {
Scanner sc = new Scanner(System.in);
System.out.println("情输入用户名");
String username = sc.nextLine();
System.out.println("情输入密码");
String password = sc.nextLine();
LoginRequestMessage message = new LoginRequestMessage(username, password);
// 发送消息
ctx.writeAndFlush(message);
try {
WAIT_FOR_LOGIN.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 如果登录失败
if (!LOGIN.get()) {
ctx.channel().close();
return;
}
// 登录成功,做后续操作(显示菜单)
while (true) {
System.out.println("==================================");
System.out.println("send [username] [content]");
System.out.println("gsend [group name] [content]");
System.out.println("gcreate [group name] [m1,m2,m3...]");
System.out.println("gmembers [group name]");
System.out.println("gjoin [group name]");
System.out.println("gquit [group name]");
System.out.println("quit");
System.out.println("==================================");
System.out.println("选择菜单");
String line = sc.nextLine();
}
}, "system in").start();
}
});
}
});
Channel channel = bootstrap.connect("localhost", 8080).sync().channel();
channel.closeFuture().sync();
} catch (Exception e) {
log.error("client error", e);
} finally {
group.shutdownGracefully();
}
}
}
客户端结果
如果登录成功,服务端返回 true,修改共享变量,通知 system in 线程继续向下运行。返回 false,system in 线程关闭客户端返回
登录成功后续操作逻辑实现
package com.zhao.client;
import com.zhao.message.*;
import com.zhao.protocol.MessageCodecSharable;
import com.zhao.protocol.ProcotolFrameDecoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
@Slf4j
public class ChatClient {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
CountDownLatch WAIT_FOR_LOGIN = new CountDownLatch(1);
AtomicBoolean LOGIN = new AtomicBoolean(false);
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(group);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProcotolFrameDecoder());
// ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
ch.pipeline().addLast("client handler", new ChannelInboundHandlerAdapter() {
// 接收响应消息
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.debug("{}", msg);
if (msg instanceof LoginResponseMessage) {
LoginResponseMessage response = (LoginResponseMessage) msg;
// 如果登录成功
if (response.isSuccess()) {
LOGIN.set(true);
}
// 计数器 减1,唤醒 system in 线程
WAIT_FOR_LOGIN.countDown();
}
}
// 在连接建立之后触发这个 Active 事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 开启新的线程发送消息
new Thread(() -> {
Scanner sc = new Scanner(System.in);
System.out.println("情输入用户名");
String username = sc.nextLine();
System.out.println("情输入密码");
String password = sc.nextLine();
LoginRequestMessage message = new LoginRequestMessage(username, password);
// 发送消息
ctx.writeAndFlush(message);
try {
WAIT_FOR_LOGIN.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 如果登录失败
if (!LOGIN.get()) {
ctx.channel().close();
return;
}
// 登录成功,做后续操作(显示菜单)
while (true) {
System.out.println("==================================");
System.out.println("send [username] [content]");
System.out.println("gsend [group name] [content]");
System.out.println("gcreate [group name] [m1,m2,m3...]");
System.out.println("gmembers [group name]");
System.out.println("gjoin [group name]");
System.out.println("gquit [group name]");
System.out.println("quit");
System.out.println("==================================");
System.out.println("选择菜单");
String command = sc.nextLine();
String[] s = command.split(" ");
switch (s[0]) {
case "send":
ctx.writeAndFlush(new ChatRequestMessage(username, s[1], s[2]));
break;
case "gsend":
ctx.writeAndFlush(new GroupChatRequestMessage(username, s[1], s[2]));
break;
case "gcreate":
Set<String> set = new HashSet<>(Arrays.asList(s[2].split(",")));
ctx.writeAndFlush(new GroupCreateRequestMessage(username, set));
break;
case "gmembers":
ctx.writeAndFlush(new GroupMembersRequestMessage(s[1]));
break;
case "gjoin":
ctx.writeAndFlush(new GroupJoinRequestMessage(username, s[1]));
break;
case "gquit":
ctx.writeAndFlush(new GroupQuitRequestMessage(username, s[1]));
break;
case "quit":
ctx.channel().close();
return;
}
}
}, "system in").start();
}
});
}
});
Channel channel = bootstrap.connect("localhost", 8080).sync().channel();
channel.closeFuture().sync();
} catch (Exception e) {
log.error("client error", e);
} finally {
group.shutdownGracefully();
}
}
}
对服务端的改进,当有用户登录上来后,就存储 用户名和用户对应的 channel。
// 把 Channel 和 登录用户进行绑定 SessionFactory.getSession().bind(ctx.channel(), username);
此时服务器的 handler 已显得足够臃肿。为了美观,在这里来一些骚操作
我想把 SimpleChannelInboundHandler 这个 handler 单独提取出一个类
结果
移动内部类
照猫画虎,下面的 handler 全部提取为单独类
当服务器接收到客户端选择的菜单后,handler 所做的操作
package com.zhao.server.handler;
import com.zhao.message.ChatRequestMessage;
import com.zhao.message.ChatResponseMessage;
import com.zhao.server.session.SessionFactory;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* @Auther: HackerZhao
* @Date: 2021/11/18 19:36
* @Description: 只关注 ChatRequestMessage
*/
@ChannelHandler.Sharable
public class ChatRequestMessageHandler extends SimpleChannelInboundHandler<ChatRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ChatRequestMessage msg) throws Exception {
String to = msg.getTo(); // 发给谁
// 如果对方不在线或者未注册,又该怎么办
// 根据用户名找channel
Channel channel = SessionFactory.getSession().getChannel(to);
// 在线
if (channel != null){
ctx.writeAndFlush(new ChatResponseMessage(msg.getFrom(),msg.getContent()));
}else {
// 不在线
ctx.writeAndFlush(new ChatResponseMessage(false,"对方用户不存在或不在线"));
}
}
}
修改过后的服务端
package com.zhao.server;
import com.zhao.protocol.MessageCodecSharable;
import com.zhao.protocol.ProcotolFrameDecoder;
import com.zhao.server.handler.ChatRequestMessageHandler;
import com.zhao.server.handler.LoginRequestMessageHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ChatServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
LoginRequestMessageHandler LOGIN_HANDLER = new LoginRequestMessageHandler();
ChatRequestMessageHandler CHAT_HANDLER = new ChatRequestMessageHandler();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.group(boss, worker);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProcotolFrameDecoder());
ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
// 只关心 LoginRequestMessage 用户登录事件
ch.pipeline().addLast(LOGIN_HANDLER);
// 只关心 ChatRequestMessageHandler 发送消息事件
ch.pipeline().addLast(CHAT_HANDLER);
}
});
Channel channel = serverBootstrap.bind(8080).sync().channel();
channel.closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
开启两个客户端,登录后发送消息
张三客户端
李四客户端
李四回复张三
如果只是两个人聊天未免有些单调,所以可以添加 群聊的功能
群聊 handler 代码实现
package com.zhao.server.handler;
import com.zhao.message.GroupCreateRequestMessage;
import com.zhao.message.GroupCreateResponseMessage;
import com.zhao.server.session.Group;
import com.zhao.server.session.GroupSession;
import com.zhao.server.session.GroupSessionFactory;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.List;
import java.util.Set;
/**
* @Auther: HackerZhao
* @Date: 2021/11/18 20:53
* @Description: 群聊的 handler
*/
@ChannelHandler.Sharable
public class GroupCreateRequestMessageHandler extends SimpleChannelInboundHandler<GroupCreateRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, GroupCreateRequestMessage msg) throws Exception {
// 接收客户端传来的 群聊名称
String groupName = msg.getGroupName();
// 被邀请的群成员
Set<String> members = msg.getMembers();
// 群管理器
GroupSession groupSession = GroupSessionFactory.getGroupSession();
// 创建群,如果存在 返回群名,不存在返回 null
Group group = groupSession.createGroup(groupName, members);
// 不存在,可以安全创建
if (group == null) {
// 向 创建这个群 的用户发送成功消息
ctx.writeAndFlush(new GroupCreateResponseMessage(true, groupName + "创建成功"));
// 向 被拉入 的用户发送消息
List<Channel> channels = groupSession.getMembersChannel(groupName);
for (Channel channel : channels) {
channel.writeAndFlush(new GroupCreateResponseMessage(true,"您已被拉入"+groupName));
}
}else {
ctx.writeAndFlush(new GroupCreateResponseMessage(false, groupName + "已经存在"));
}
}
}
服务器添加这个 handler
// 只关心 GroupCreateRequestMessageHandler 创建群聊事件 ch.pipeline().addLast(GROUP_CREATE_HANDLER);
启动多个客户端测试群聊功能
张三客户端
李四客户端
王五客户端
张三此时想给群聊发送一条信息,发现自己并没有在群聊里边。建群的时候首先要把 群主拉到群里边。
case "gcreate":
Set<String> set = new HashSet<>(Arrays.asList(s[2].split(",")));
// 加入自己
set.add(username);
ctx.writeAndFlush(new GroupCreateRequestMessage(s[1], set));
break;
发起群聊
张三客户端
李四客户端
王五客户端
赵六客户端
客户端的主动退出和异常退出
添加新的 Handler,QuitHandler
测试结果,张三客户端
服务器端
李四客户端,异常退出后,服务器打印结果
大批用户连接服务器后出现 网络设备故障,出现假死状况,服务器 连接数释放不出来,新连接连接不上。该如何处理
Netty 解决假死的手段
IdleStateHandler
// 用来判断是不是 读空闲时间过长,或写空闲时间过长
// 参数1 读空闲时间过长没收到数据会做出处理
// 参数2 写空闲时间过长没收到数据会做出处理
// 参数3 读写空闲时间过长没收到数据会做出处理
// 5s内没有收到 channel 的数据,就会触发一个 IdleState.READER_IDLE 事件
ch.pipeline().addLast(new IdleStateHandler(5,0,0));
// 处理没有收到数据的事件,处理双向(读写事件)的handler ChannelDuplexHandler
ch.pipeline().addLast(new ChannelDuplexHandler(){
// 用来处理特殊事件
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent event = (IdleStateEvent) evt;
// 触发了读空闲事件
if (event.state()== IdleState.READER_IDLE) {
log.debug("已经 5s 没有读到数据了");
}
}
});
启动服务器和客户端,控制台打印
没有收到数据,可以让客户端定时向服务器发送一些无用包,证明自己还活着,俗称“心跳”。
客户端加入上面的代码,3s 向服务器发送一个心跳包
// 3s内没有向服务器写数据,就会触发一个 IdleState.WRITER_IDLE事件,写与读的的间隔大概是 1/2
ch.pipeline().addLast(new IdleStateHandler(0,3,0));
// 处理没有收到数据的事件,处理双向(读写事件)的handler ChannelDuplexHandler
ch.pipeline().addLast(new ChannelDuplexHandler(){
// 用来处理特殊事件
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent event = (IdleStateEvent) evt;
// 触发了写空闲事件
if (event.state()== IdleState.WRITER_IDLE) {
// log.debug("3s 没有写数据了,发送一个心跳包");
ctx.writeAndFlush(new PingMessage());
}
}
});
服务器显示,并没有关闭客户端
客户端
服务器和客户端完整代码
服务器
package com.zhao.server;
import com.zhao.protocol.MessageCodecSharable;
import com.zhao.protocol.ProcotolFrameDecoder;
import com.zhao.server.handler.*;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ChatServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
LoginRequestMessageHandler LOGIN_HANDLER = new LoginRequestMessageHandler();
ChatRequestMessageHandler CHAT_HANDLER = new ChatRequestMessageHandler();
GroupCreateRequestMessageHandler GROUP_CREATE_HANDLER = new GroupCreateRequestMessageHandler();
GroupChatRequestMessageHandler GROUP_CHAT_HANDLER = new GroupChatRequestMessageHandler();
GroupJoinRequestMessageHandler GROUP_JOIN_HANDLER = new GroupJoinRequestMessageHandler();
GroupMembersRequestMessageHandler GROUP_MEMBERS_HANDLER = new GroupMembersRequestMessageHandler();
GroupQuitRequestMessageHandler GROUP_QUIT_HANDLER = new GroupQuitRequestMessageHandler();
QuitHandler QUIT_HANDLER = new QuitHandler();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.group(boss, worker);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProcotolFrameDecoder());
ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
// 用来判断是不是 读空闲时间过长,或写空闲时间过长
// 参数1 读空闲时间过长没收到数据会做出处理
// 参数2 写空闲时间过长没收到数据会做出处理
// 参数3 读写空闲时间过长没收到数据会做出处理
// 5s内没有收到 channel 的数据,就会触发一个 IdleState.READER_IDLE 事件
ch.pipeline().addLast(new IdleStateHandler(5,0,0));
// 处理没有收到数据的事件,处理双向(读写事件)的handler ChannelDuplexHandler
ch.pipeline().addLast(new ChannelDuplexHandler(){
// 用来处理特殊事件
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent event = (IdleStateEvent) evt;
// 触发了读空闲事件
if (event.state()== IdleState.READER_IDLE) {
log.debug("已经 5s 没有读到数据了");
}
}
});
// 只关心 LoginRequestMessage 用户登录事件
ch.pipeline().addLast(LOGIN_HANDLER);
// 只关心 ChatRequestMessageHandler 发送消息事件
ch.pipeline().addLast(CHAT_HANDLER);
// 只关心 GroupCreateRequestMessageHandler 创建群聊事件
ch.pipeline().addLast(GROUP_CREATE_HANDLER);
// 只关心 GroupChatRequestMessage 向群聊发送消息事件
ch.pipeline().addLast(GROUP_CHAT_HANDLER);
// 当连接断开时移除 channel
ch.pipeline().addLast(QUIT_HANDLER);
}
});
Channel channel = serverBootstrap.bind(8080).sync().channel();
channel.closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
客户端
package com.zhao.client;
import com.zhao.message.*;
import com.zhao.protocol.MessageCodecSharable;
import com.zhao.protocol.ProcotolFrameDecoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
@Slf4j
public class ChatClient {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
CountDownLatch WAIT_FOR_LOGIN = new CountDownLatch(1);
AtomicBoolean LOGIN = new AtomicBoolean(false);
AtomicBoolean EXIT = new AtomicBoolean(false);
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(group);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProcotolFrameDecoder());
// ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
// 用来判断是不是 写空闲时间过长
// 参数1 读空闲时间过长没收到数据会做出处理
// 参数2 写空闲时间过长没收到数据会做出处理
// 参数3 读写空闲时间过长没收到数据会做出处理
// 3s内没有向服务器写数据,就会触发一个 IdleState.WRITER_IDLE事件,写与读的的间隔大概是 1/2
ch.pipeline().addLast(new IdleStateHandler(0,3,0));
// 处理没有收到数据的事件,处理双向(读写事件)的handler ChannelDuplexHandler
ch.pipeline().addLast(new ChannelDuplexHandler(){
// 用来处理特殊事件
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent event = (IdleStateEvent) evt;
// 触发了写空闲事件
if (event.state()== IdleState.WRITER_IDLE) {
// log.debug("3s 没有写数据了,发送一个心跳包");
ctx.writeAndFlush(new PingMessage());
}
}
});
ch.pipeline().addLast("client handler", new ChannelInboundHandlerAdapter() {
// 接收响应消息
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// log.debug("{}", msg);
System.out.println(msg);
if (msg instanceof LoginResponseMessage) {
LoginResponseMessage response = (LoginResponseMessage) msg;
// 如果登录成功
if (response.isSuccess()) {
LOGIN.set(true);
}
// 计数器 减1,唤醒 system in 线程
WAIT_FOR_LOGIN.countDown();
}
}
// 在连接建立之后触发这个 Active 事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 开启新的线程发送消息
new Thread(() -> {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名");
String username = sc.nextLine();
System.out.println("请输入密码");
String password = sc.nextLine();
LoginRequestMessage message = new LoginRequestMessage(username, password);
// 发送消息
ctx.writeAndFlush(message);
try {
WAIT_FOR_LOGIN.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 如果登录失败
if (!LOGIN.get()) {
ctx.channel().close();
return;
}
// 登录成功,做后续操作(显示菜单)
while (true) {
System.out.println("==================================");
System.out.println("send [username] [content]");
System.out.println("gsend [group name] [content]");
System.out.println("gcreate [group name] [m1,m2,m3...]");
System.out.println("gmembers [group name]");
System.out.println("gjoin [group name]");
System.out.println("gquit [group name]");
System.out.println("quit");
System.out.println("==================================");
System.out.println("选择菜单");
String command = sc.nextLine();
String[] s = command.split(" ");
switch (s[0]) {
case "send":
ctx.writeAndFlush(new ChatRequestMessage(username, s[1], s[2]));
break;
case "gsend":
ctx.writeAndFlush(new GroupChatRequestMessage(username, s[1], s[2]));
break;
case "gcreate":
Set<String> set = new HashSet<>(Arrays.asList(s[2].split(",")));
// 加入自己
set.add(username);
ctx.writeAndFlush(new GroupCreateRequestMessage(s[1], set));
break;
case "gmembers":
ctx.writeAndFlush(new GroupMembersRequestMessage(s[1]));
break;
case "gjoin":
ctx.writeAndFlush(new GroupJoinRequestMessage(username, s[1]));
break;
case "gquit":
ctx.writeAndFlush(new GroupQuitRequestMessage(username, s[1]));
break;
case "quit":
ctx.channel().close();
return;
}
}
}, "system in").start();
}
});
}
// 在连接断开时触发
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
log.debug("连接已经断开,按任意键退出..");
EXIT.set(true);
}
// 在出现异常时触发
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.debug("连接已经断开,按任意键退出..{}", cause.getMessage());
EXIT.set(true);
}
});
Channel channel = bootstrap.connect("localhost", 8080).sync().channel();
channel.closeFuture().sync();
} catch(
Exception e)
{
log.error("client error", e);
} finally
{
group.shutdownGracefully();
}
}
}
百度网盘存放聊天室所有代码,链接在下方
提取码:qr1p