登录验证处理器示例代码:
/**
* @program: learnnetty
* @description: 服务端验证客户端身份
* @create: 2020-05-11 15:55
**/
public class ServerAuthHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (!LoginUtil.hasLogin(ctx.channel())){
ctx.channel().close();
}else {
//如果已经登录则不需要每次都验证
ctx.pipeline().remove(this);
super.channelRead(ctx, msg);
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
if (LoginUtil.hasLogin(ctx.channel())){
System.out.println("已完成登录验证,无需再次验证");
}else {
System.out.println("未登录验证,关闭连接");
}
}
}
在第一次登录之后就可以把此处的验证移除,同一条链接发消息没必要每一次都验证一下。
为服务端添加登录验证处理器:
/**
* @program: learnnetty
* @description: 服务端
* @create: 2020-05-06 17:18
**/
public class Server {
public static void main(String[] args) {
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel
.pipeline()
.addLast(new FrameDecoder())
.addLast(new ServerLoginHandler())
.addLast(new ServerAuthHandler())
.addLast(new ServerMsgHandler())
.addLast(new FrameEncoder());
}
});
bind(serverBootstrap, 8080);
}
private static void bind(final ServerBootstrap serverBootstrap, final int port){
serverBootstrap.bind(port).addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
if (future.isSuccess()){
System.out.println("端口[" + port + "]绑定成功");
}else {
System.out.println("端口[" + port + "]绑定失败,尝试绑定[" + (port + 1) + "]端口");
bind(serverBootstrap, port + 1);
}
}
});
}
}
当连接被拒绝时客户端应该有反馈:
/**
* @program: learnnetty
* @description: 客户端业务处理
* @create: 2020-05-06 15:40
**/
public class ClientLoginHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(new Date() + ",客户端开始登录");
LoginRequestFrame loginFrame = new LoginRequestFrame();
loginFrame.setUserId(UUID.randomUUID().toString());
loginFrame.setUserName("zcd");
loginFrame.setPassword("zzz");
ctx.channel().writeAndFlush(loginFrame);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
BaseFrame frame = (BaseFrame) msg;
if (frame instanceof LoginResponseFrame){
LoginUtil.resolveRespLoginFrame(frame, ctx);
}else {
ctx.fireChannelRead(frame);
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("未通过登录验证关闭连接");
}
}
客户端成功连接服务端输出:
客户端连接成功
Mon May 11 16:16:33 CST 2020,客户端开始登录
客户端登录成功
输入消息发送至服务端:
123
输入消息发送至服务端:
Mon May 11 16:16:36 CST 2020,收到来自服务器的消息:服务器已收到消息
123
输入消息发送至服务端:
Mon May 11 16:16:38 CST 2020,收到来自服务器的消息:服务器已收到消息
123
输入消息发送至服务端:
Mon May 11 16:16:39 CST 2020,收到来自服务器的消息:服务器已收到消息
客户端多次发送消息至服务端输出:
端口[8080]绑定成功
Mon May 11 16:16:34 CST 2020,服务端开始处理请求
Mon May 11 16:16:34 CST 2020,zcd客户端登录成功
Mon May 11 16:16:36 CST 2020,服务端开始处理请求
已完成登录验证,无需再次验证
Mon May 11 16:16:36 CST 2020,收到来自客户端的消息:123
Mon May 11 16:16:38 CST 2020,服务端开始处理请求
Mon May 11 16:16:38 CST 2020,收到来自客户端的消息:123
Mon May 11 16:16:39 CST 2020,服务端开始处理请求
Mon May 11 16:16:39 CST 2020,收到来自客户端的消息:123
解析来自客户端的信息:
public class ServerMsgHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
BaseFrame baseFrame = (BaseFrame) msg;
if (baseFrame instanceof MsgRequestFrame){
//ctx.channel().writeAndFlush(MsgUtil.respMsg(baseFrame));
//声明响应实体
BaseFrame respFrame;
Channel aimUserChannel;
//解析目标用户id
String toId = ((MsgRequestFrame)baseFrame).getToUserId();
//获取目标用户Channel
if (SessionUtil.getChannel(toId) != null && LoginUtil.hasLogin(SessionUtil.getChannel(toId))){
aimUserChannel = SessionUtil.getChannel(toId);
}else if (SessionUtil.hasGroup(toId)){
MsgUtil.postGroupMsg(SessionUtil.getChannels(toId), (MsgRequestFrame)baseFrame);
return;
}else {
aimUserChannel = ctx.channel();
baseFrame = new MsgResponseFrame();
((MsgResponseFrame)baseFrame).setContent("对方不在线");
}
//发送消息
aimUserChannel.writeAndFlush(baseFrame);
}else {
ctx.fireChannelRead(baseFrame);
}
}
}
根据目标ID在SessionUtil中获取对应的Channel,并将数据从指定的Channel中发出。
解析来自客户端的命令,并作出反馈:
public class ServerOrderHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
BaseFrame baseFrame = (BaseFrame)msg;
if (baseFrame instanceof OrderReqFrame){
OrderReqFrame orderReqFrame = (OrderReqFrame)baseFrame;
OrderRespFrame orderRespFrame = new OrderRespFrame();
byte order = orderReqFrame.getOrder();
switch (order){
case Order.ALL_ONLINE_USERS:
orderRespFrame.setResult(ServerOnlineUserCache.getAllUser());
orderRespFrame.setFlag(Order.FLAG_POSITIVE);
break;
case Order.ALL_GROUP:
orderRespFrame.setResult(SessionUtil.getAllGroup());
orderRespFrame.setFlag(Order.FLAG_POSITIVE);
break;
case Order.CREATE_GROUP:
orderRespFrame.setResult(SessionUtil.createGroup(baseFrame, ctx.channel(), ctx));
orderRespFrame.setFlag(Order.FLAG_POSITIVE);
break;
case Order.INTO_GROUP:
orderRespFrame.setResult(SessionUtil.insertGroup(baseFrame, ctx.channel()));
orderRespFrame.setFlag(Order.FLAG_POSITIVE);
break;
case Order.LEAVE_GROUP:
orderRespFrame.setResult(SessionUtil.leaveGroup(baseFrame, ctx.channel()));
orderRespFrame.setFlag(Order.FLAG_POSITIVE);
break;
case Order.ALL_INSERT_GROUP:
orderRespFrame.setResult(SessionUtil.selectChannelInGroup(baseFrame, ctx.channel()));
orderRespFrame.setFlag(Order.FLAG_POSITIVE);
break;
default:
orderRespFrame.setResult("未知命令");
orderRespFrame.setFlag(Order.FLAG_NEGATIVE);
}
ctx.channel().writeAndFlush(orderRespFrame);
}else {
ctx.fireChannelRead(msg);
}
}
}
根据发送来的群号id,将该Channel加入到指定的ChannelGroup中:
public class SessionUtil {
/**
* 用户与Channel的映射
*/
private static Map<String, Channel> sessions = new ConcurrentHashMap<>();
/**
* 群聊缓存Channel
*/
private static Map<String, ChannelGroup> groupSession = new ConcurrentHashMap<>();
/**
* 群聊信息缓存
*/
private static Map<String, String> groupInfo = new ConcurrentHashMap<>();
public static String createGroup(BaseFrame baseFrame, Channel channel, ChannelHandlerContext ctx){
String groupId = UUID.randomUUID().toString();
OrderReqFrame orderReqFrame = (OrderReqFrame)baseFrame;
ChannelGroup channels = new DefaultChannelGroup(ctx.executor());
channels.add(channel);
groupSession.put(groupId, channels);
groupInfo.put(groupId, "[" + orderReqFrame.getUserName() + "的群聊]");
return "群号[" + groupId + "],名称[" + orderReqFrame.getUserName() + "的群聊]建立完成";
}
public static String insertGroup(BaseFrame baseFrame, Channel channel){
OrderReqFrame orderReqFrame = (OrderReqFrame)baseFrame;
String groupId = (String)orderReqFrame.getContent();
if (hasGroup(groupId)){
groupSession.get(groupId).add(channel);
return "群号[" + groupId + "],名称" + groupInfo.get(groupId) + ",加入成功";
}else {
return "群号[" + groupId +"]不存在";
}
}
public static String leaveGroup(BaseFrame baseFrame, Channel channel){
if (baseFrame == null){
for (Map.Entry<String, ChannelGroup> cursor :
groupSession.entrySet()) {
if (cursor.getValue().find(channel.id()) != null){
String groupId = cursor.getKey();
cursor.getValue().remove(channel);
if (cursor.getValue().isEmpty()){
groupSession.remove(groupId);
groupInfo.remove(groupId);
}
}
}
return null;
}
OrderReqFrame orderReqFrame = (OrderReqFrame)baseFrame;
String groupId = (String)orderReqFrame.getContent();
if (groupSession.containsKey(groupId)){
groupSession.get(groupId).remove(channel);
if (groupSession.get(groupId).isEmpty()){
groupSession.remove(groupId);
groupInfo.remove(groupId);
}
return "群聊退出成功";
}else {
return "尚未加入该群聊";
}
}
public static Object getAllGroup(){
return groupInfo;
}
/**
* 设置Channel的属性
*/
public static void bindSession(Session session, Channel channel){
sessions.put(session.getUserId(), channel);
channel.attr(Attributes.SESSION).set(session);
}
/**
* 移除属性
*/
public static void unbindSession(Channel channel){
if (hasLogin(channel)){
ServerOnlineUserCache.removeUser(getSession(channel).getUserId());
sessions.remove(getSession(channel).getUserId());
channel.attr(Attributes.SESSION).set(null);
}
System.out.println(sessions);
}
/**
* 获取所有已加入的群聊
*/
public static Object selectChannelInGroup(BaseFrame baseFrame, Channel channel) {
Map<String, String> result = new HashMap<>();
for (Map.Entry<String, ChannelGroup> cursor : groupSession.entrySet()){
if (cursor.getValue().find(channel.id()) != null){
result.put(cursor.getKey(), groupInfo.get(cursor.getKey()));
}
}
return result;
}
public static boolean hasLogin(Channel channel){
return channel.hasAttr(Attributes.SESSION);
}
public static Session getSession(Channel channel){
return channel.attr(Attributes.SESSION).get();
}
public static Channel getChannel(String userId){
return sessions.get(userId);
}
public static boolean hasGroup(String groupId){
return groupSession.containsKey(groupId);
}
public static ChannelGroup getChannels(String groupId){
return groupSession.get(groupId);
}
public static String getGroupInfo(String groupId){
return groupInfo.get(groupId);
}
}
解析来自客户端发来的消息,如果目标ID不是用户那就在ChannelGroup的缓存中寻找:
public class ServerMsgHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
BaseFrame baseFrame = (BaseFrame) msg;
if (baseFrame instanceof MsgRequestFrame){
//ctx.channel().writeAndFlush(MsgUtil.respMsg(baseFrame));
//声明响应实体
BaseFrame respFrame;
Channel aimUserChannel;
//解析目标用户id
String toId = ((MsgRequestFrame)baseFrame).getToUserId();
//获取目标用户Channel
if (SessionUtil.getChannel(toId) != null && LoginUtil.hasLogin(SessionUtil.getChannel(toId))){
aimUserChannel = SessionUtil.getChannel(toId);
}else if (SessionUtil.hasGroup(toId)){
MsgUtil.postGroupMsg(SessionUtil.getChannels(toId), (MsgRequestFrame)baseFrame);
return;
}else {
aimUserChannel = ctx.channel();
baseFrame = new MsgResponseFrame();
((MsgResponseFrame)baseFrame).setContent("对方不在线");
}
//发送消息
aimUserChannel.writeAndFlush(baseFrame);
}else {
ctx.fireChannelRead(baseFrame);
}
}
}
将发来的消息使用指定的ChannelGroup群发:
public class MsgUtil {
/**
* 对Msg做出响应
*/
public static BaseFrame respMsg(BaseFrame baseFrame) {
MsgRequestFrame msgRequestFrame = (MsgRequestFrame)baseFrame;
System.out.println(new Date() + ",收到来自客户端的消息:" + msgRequestFrame.getContent());
MsgResponseFrame responseFrame = new MsgResponseFrame();
responseFrame.setContent("服务器已收到消息");
return responseFrame;
}
/**
* 发送请求Msg
*/
public static BaseFrame reqMsg(String line){
MsgRequestFrame requestFrame = new MsgRequestFrame();
requestFrame.setContent(line);
return requestFrame;
}
/**
* 消息群发
*/
public static void postGroupMsg(ChannelGroup channels, MsgRequestFrame msgRequestFrame){
String groupId = msgRequestFrame.getToUserId();
String groupName = SessionUtil.getGroupInfo(groupId);
String posterId = msgRequestFrame.getFromUserId();
String posterName = msgRequestFrame.getFromUserName();
MsgRequestFrame requestFrame = new MsgRequestFrame();
requestFrame.setGroupInfo(true);
requestFrame.setFromUserName(groupName);
requestFrame.setFromUserId(groupId);
requestFrame.setContent("[" + posterId + "], " + posterName + ",对大家说:" + msgRequestFrame.getContent());
channels.writeAndFlush(requestFrame);
}
}