├── README.md └── easy-im ├── easy-im-client ├── easy-im-client.iml ├── pom.xml └── src │ └── main │ ├── assembly │ └── assembly.xml │ ├── java │ └── com │ │ └── sunnick │ │ └── easyim │ │ ├── Client.java │ │ └── handler │ │ ├── ClientHandler.java │ │ ├── ClientIdleHandler.java │ │ ├── CreateGroupResponseHandler.java │ │ ├── GroupMessageResponseHandler.java │ │ ├── LoginHandler.java │ │ ├── LoginResponseHandler.java │ │ └── MessageResponseHandler.java │ └── resources │ └── log4j2.xml ├── easy-im-commom ├── easy-im-commom.iml ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── sunnick │ └── easyim │ ├── Command │ ├── BroadcastCommand.java │ ├── CommandManager.java │ ├── ConsoleCommand.java │ ├── CreateGroupCommand.java │ ├── GroupMessageCommand.java │ └── P2PCommand.java │ ├── callback │ └── EasyImCallback.java │ ├── handler │ ├── DefaultErrorHandler.java │ ├── EasyImChannelInBoundHandler.java │ ├── MagicNumValidator.java │ ├── PacketCodecHandler.java │ ├── PacketDecoder.java │ ├── PacketEncoder.java │ └── ServerIdleHandler.java │ ├── packet │ ├── BaseResponsePacket.java │ ├── CreateGroupRequestPacket.java │ ├── CreateGroupResponsePacket.java │ ├── DefaultErrorPacket.java │ ├── GroupMessageRequestPacket.java │ ├── GroupMessageResponsePacket.java │ ├── HeartBeatPacket.java │ ├── LoginRequestPacket.java │ ├── LoginResponsePacket.java │ ├── MessageRequestPacket.java │ └── MessageResponsePacket.java │ ├── protocol │ ├── Command.java │ ├── JsonSerializer.java │ ├── Packet.java │ ├── PacketCodeC.java │ ├── SerializeAlgorithm.java │ └── Serializer.java │ └── util │ ├── Attributes.java │ ├── ChannelUtil.java │ ├── GroupUtil.java │ ├── LoginUtil.java │ ├── Scan.java │ ├── Session.java │ └── SessionUtil.java ├── easy-im-server ├── easy-im-server.iml ├── pom.xml └── src │ └── main │ ├── assembly │ └── assembly.xml │ ├── java │ └── com │ │ └── sunnick │ │ └── easyim │ │ ├── Server.java │ │ └── handler │ │ ├── AuthHandler.java │ │ ├── CreateGroupRequestHandler.java │ │ ├── GroupMessageRequestHandler.java │ │ ├── HeartBeatHandler.java │ │ ├── LoginRequestHandler.java │ │ ├── MessageRequestHandler.java │ │ └── ServerHandler.java │ └── resources │ └── log4j2.xml └── pom.xml /README.md: -------------------------------------------------------------------------------- 1 | # easy-im:一款基于netty的即时通讯系统 2 | ## 介绍 3 | easy-im是面向开发者的一款轻量级、开箱即用的即时通讯系统,帮助开发者快速搭建消息推送等功能。 4 | 基于easy-im,你可以快速实现以下功能: 5 | 6 | `` + 聊天软件 `` 7 | 8 | `` + IoT消息推送 `` 9 | 10 | ## 基本用法--单机版(v1分支) 11 | 项目分为easy-im-client、easy-im-server、easy-im-common三个模块。 12 | 13 | ``` 服务端: ``` 执行mvn package后生成easy-im-server.tar.gz,解压后至./lib目录执行命令: 14 | java -jar -Dport=8888 easy-im-server-1.0-SNAPSHOT.jar,即可启动服务端,其中port是服务端口。 15 | 16 | 17 | ``` 客户端: ``` 执行mvn package后生成easy-im-client.tar.gz,解压后至./lib目录执行命令: 18 | java -jar -Duserid=110 -Dusername=zhangsan -Dhost=127.0.0.1 -Dport=8888 easy-im-client-1.0-SNAPSHOT.jar。 19 | 其中userid为用户id,username为用户名,host为服务端ip,port为服务端端口,其中userid要保持唯一性。 20 | 21 | ``` 用法: ``` 客户端启动后,在命令行输入命令,命令格式为 command::content ,命令以英文双冒号为分隔符,现已支持如下命令: 22 | 23 | `` + 单聊 sendToUser::userId::msg `` 24 | 25 | `` + 群聊 sendToGroup::groupId::msg `` 26 | 27 | `` + 发起群聊 createGroup::userId1,userId2,userId3... `` 28 | 29 | `` + 广播 broadcast::msg `` 30 | 31 | 后续计划加入更多命令,如: 32 | 33 | `` + 退出群聊 quitGroup::groupId `` 34 | 35 | `` + 加入群聊 joinGroup::groupId `` 36 | 37 | `` + 查询所有在线用户 getAllUsers `` 38 | 39 | `` + 查询群聊中在线用户 getGroupUsers::groupId `` 40 | 41 | --- 42 | 43 | ## 计划中-集群版(v2分支) 44 | 45 | #### 打算将服务端做成可无限扩展的架构,使之满足高并发的需求。 46 | 47 | #### 架构图如下: 48 | 49 | ![](https://user-gold-cdn.xitu.io/2019/1/29/168998972ea46343?w=627&h=480&f=png&s=17428) 50 | 51 | #### 交互步骤: 52 | ##### 1. 启动server,将server注册到注册中心 53 | ##### 2. 启动路由层(route),到注册中心获取可用的server列表 54 | ##### 3. 启动client,到route获取一个server信息(route层实现选择策略,默认轮循) 55 | ##### 4. client与server建立长连接,并将server与client的对应关系存储至Redis或MySQL 56 | 57 | #### 注: 58 | ##### 1. route层采用http对外提供无状态的服务,可以采用nginx无限扩展 59 | ##### 2. server注册至注册中心,也可以无限扩展,用以实现百万连接 60 | ##### 3. client之间互聊时,需发送消息至route,route到redis中查找对应的server,再将消息通过server转发至对方的client,以实现两个client不在同一台server时互聊 61 | --- 62 | 63 | **目前集群版(v2分支)正在计划中,欢迎有兴趣的同学共同参与,共同进步。** 64 | 65 | --- 66 | 67 | 68 | ## 联系作者 69 | - [zhangshun9201@163.com](mailto:zhangshun9201@163.com) 70 | - 个人微信号 71 | 72 | ![](https://user-gold-cdn.xitu.io/2019/1/29/168999f3dcc33273?w=430&h=430&f=jpeg&s=40811) 73 | - 微信公众号 74 | 75 | ![](https://user-gold-cdn.xitu.io/2019/1/27/1688fbaaa4a0b0f3?w=254&h=241&f=png&s=43837) 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /easy-im/easy-im-client/easy-im-client.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /easy-im/easy-im-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | easy-im 7 | com.sunnick 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | easy-im-client 13 | 14 | 15 | 16 | com.sunnick 17 | easy-im-commom 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | 23 | 24 | 25 | org.apache.maven.plugins 26 | maven-jar-plugin 27 | 2.4 28 | 29 | 30 | 31 | 32 | com.sunnick.easyim.Client 33 | 34 | true 35 | 36 | ./ 37 | 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-assembly-plugin 44 | 45 | 46 | make-assembly 47 | package 48 | 49 | single 50 | 51 | 52 | ${project.name} 53 | src/main/assembly/assembly.xml 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /easy-im/easy-im-client/src/main/assembly/assembly.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tar.gz 6 | 7 | 8 | true 9 | 10 | 11 | 12 | true 13 | lib 14 | 15 | runtime 16 | 17 | 18 | 19 | 20 | src/main/bin 21 | / 22 | 23 | 24 | -------------------------------------------------------------------------------- /easy-im/easy-im-client/src/main/java/com/sunnick/easyim/Client.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim; 2 | 3 | import com.sunnick.easyim.handler.*; 4 | import com.sunnick.easyim.util.Scan; 5 | import io.netty.bootstrap.Bootstrap; 6 | import io.netty.channel.Channel; 7 | import io.netty.channel.ChannelFuture; 8 | import io.netty.channel.ChannelFutureListener; 9 | import io.netty.channel.ChannelInitializer; 10 | import io.netty.channel.nio.NioEventLoopGroup; 11 | import io.netty.channel.socket.SocketChannel; 12 | import io.netty.channel.socket.nio.NioSocketChannel; 13 | import io.netty.util.internal.StringUtil; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | /** 18 | * Created by Sunnick on 2019/1/12/012. 19 | */ 20 | public class Client { 21 | 22 | private static Logger logger = LoggerFactory.getLogger(Client.class); 23 | private static String userid = "157"; 24 | private static String username = "wangwu"; 25 | private static String host = "127.0.0.1"; 26 | private static int port = 8888; 27 | 28 | 29 | public static void main(String[] strings){ 30 | userid = StringUtil.isNullOrEmpty(System.getProperty("userid")) ? userid : System.getProperty("userid"); 31 | username = StringUtil.isNullOrEmpty(System.getProperty("username")) ? username : System.getProperty("username"); 32 | host = StringUtil.isNullOrEmpty(System.getProperty("host")) ? host : System.getProperty("host"); 33 | port = StringUtil.isNullOrEmpty(System.getProperty("port")) ? port : Integer.parseInt(System.getProperty("port")); 34 | start(); 35 | } 36 | 37 | public static void start() { 38 | NioEventLoopGroup worker = new NioEventLoopGroup(); 39 | Bootstrap bootstrap = new Bootstrap(); 40 | bootstrap.group(worker).channel(NioSocketChannel.class) 41 | .handler(new ChannelInitializer() { 42 | @Override 43 | protected void initChannel(SocketChannel channel) throws Exception { 44 | channel.pipeline().addLast(new ServerIdleHandler()); 45 | channel.pipeline().addLast(new MagicNumValidator()); 46 | channel.pipeline().addLast(PacketCodecHandler.getInstance()); 47 | channel.pipeline().addLast(new ClientIdleHandler()); 48 | channel.pipeline().addLast(new LoginHandler(userid,username)); 49 | channel.pipeline().addLast(LoginResponseHandler.getInstance()); 50 | channel.pipeline().addLast(ClientHandler.getInstance()); 51 | } 52 | }); 53 | ChannelFuture future = bootstrap.connect(host,port).addListener(new ChannelFutureListener() { 54 | public void operationComplete(ChannelFuture channelFuture) throws Exception { 55 | if(channelFuture.isSuccess()){ 56 | logger.info("connect to server success!"); 57 | //启动控制台输入 58 | startScanConsole(channelFuture.channel()); 59 | }else{ 60 | logger.info("failed to connect the server! "); 61 | System.exit(0); 62 | } 63 | } 64 | }); 65 | try { 66 | future.channel().closeFuture().sync(); 67 | logger.info("与服务端断开连接!"); 68 | } catch (InterruptedException e) { 69 | e.printStackTrace(); 70 | } 71 | } 72 | 73 | /** 74 | * 启动控制台输入 75 | */ 76 | private static void startScanConsole(Channel channel){ 77 | Scan scan = new Scan(channel); 78 | Thread thread = new Thread(scan); 79 | thread.setName("scan-thread"); 80 | thread.start(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /easy-im/easy-im-client/src/main/java/com/sunnick/easyim/handler/ClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.sunnick.easyim.Client; 5 | import com.sunnick.easyim.packet.*; 6 | import com.sunnick.easyim.protocol.Packet; 7 | import com.sunnick.easyim.protocol.PacketCodeC; 8 | import com.sunnick.easyim.util.Session; 9 | import com.sunnick.easyim.util.SessionUtil; 10 | import io.netty.buffer.ByteBuf; 11 | import io.netty.channel.ChannelHandler; 12 | import io.netty.channel.ChannelHandlerContext; 13 | import io.netty.channel.ChannelInboundHandlerAdapter; 14 | import io.netty.channel.SimpleChannelInboundHandler; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import java.util.Map; 19 | import java.util.UUID; 20 | import java.util.concurrent.ConcurrentHashMap; 21 | 22 | import static com.sunnick.easyim.protocol.Command.*; 23 | import static com.sunnick.easyim.protocol.Command.CREATE_GROUP_REQUEST; 24 | import static com.sunnick.easyim.protocol.Command.CREATE_GROUP_RESPONSE; 25 | 26 | /** 27 | * Created by Sunnick on 2019/1/13/013. 28 | */ 29 | 30 | @ChannelHandler.Sharable 31 | public class ClientHandler extends SimpleChannelInboundHandler { 32 | 33 | private static Logger logger = LoggerFactory.getLogger(ClientHandler.class); 34 | 35 | private ClientHandler(){} 36 | 37 | private static ClientHandler instance = new ClientHandler(); 38 | 39 | public static ClientHandler getInstance(){ 40 | return instance; 41 | } 42 | 43 | private static Map> handlerMap = new ConcurrentHashMap<>(); 44 | static{ 45 | handlerMap.putIfAbsent(MESSAGE_RESPONSE,MessageResponseHandler.getInstance()); 46 | handlerMap.putIfAbsent(CREATE_GROUP_RESPONSE,CreateGroupResponseHandler.getInstance()); 47 | handlerMap.putIfAbsent(GROUP_MESSAGE_RESPONSE,GroupMessageResponseHandler.getInstance()); 48 | handlerMap.putIfAbsent(DEFAULT_ERROR, DefaultErrorHandler.getInstance()); 49 | } 50 | 51 | @Override 52 | protected void channelRead0(ChannelHandlerContext channelHandlerContext, Packet packet) throws Exception { 53 | logger.info("收到服务器响应:{}",JSON.toJSONString(packet)); 54 | SimpleChannelInboundHandler handler = handlerMap.get(packet.getCommand()); 55 | if(handler != null ){ 56 | handler.channelRead(channelHandlerContext,packet); 57 | }else if (packet.getCommand() == HEART_BEAT){ 58 | logger.info("收到心跳响应:{}",JSON.toJSONString(packet)); 59 | }else{ 60 | logger.info("未找到响应指令,请确认指令是否正确!"); 61 | } 62 | } 63 | 64 | @Override 65 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 66 | logger.info("与服务端连接断开,即将重连.."); 67 | Client.start(); 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /easy-im/easy-im-client/src/main/java/com/sunnick/easyim/handler/ClientIdleHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.sunnick.easyim.packet.HeartBeatPacket; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.timeout.IdleStateEvent; 6 | import io.netty.handler.timeout.IdleStateHandler; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * Created by Sunnick on 2019/1/27/027. 14 | */ 15 | public class ClientIdleHandler extends IdleStateHandler { 16 | 17 | private static Logger logger = LoggerFactory.getLogger(ClientIdleHandler.class); 18 | 19 | private static final int HEART_BEAT_TIME = 50; 20 | 21 | public ClientIdleHandler() { 22 | super(0, 0, HEART_BEAT_TIME); 23 | } 24 | 25 | @Override 26 | protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception { 27 | logger.info("发送心跳...."); 28 | ctx.writeAndFlush(new HeartBeatPacket()); 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /easy-im/easy-im-client/src/main/java/com/sunnick/easyim/handler/CreateGroupResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.sunnick.easyim.packet.CreateGroupRequestPacket; 5 | import com.sunnick.easyim.packet.CreateGroupResponsePacket; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | /** 12 | * Created by Sunnick on 2019/1/20/020. 13 | */ 14 | public class CreateGroupResponseHandler extends EasyImChannelInBoundHandler { 15 | 16 | private static Logger logger = LoggerFactory.getLogger(CreateGroupResponseHandler.class); 17 | 18 | public static CreateGroupResponseHandler getInstance(){ 19 | return instance; 20 | } 21 | 22 | private CreateGroupResponseHandler(){} 23 | 24 | private static CreateGroupResponseHandler instance = new CreateGroupResponseHandler(); 25 | 26 | 27 | @Override 28 | protected void handleResponse(ChannelHandlerContext ctx, CreateGroupResponsePacket packet) { 29 | if (!packet.success()){ 30 | logger.info(JSON.toJSONString(packet)); 31 | return; 32 | } 33 | logger.info("群聊创建成功,群id为:{}",packet.getGroupId()); 34 | logger.info("群成员为:{}",packet.getUserNames()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /easy-im/easy-im-client/src/main/java/com/sunnick/easyim/handler/GroupMessageResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.sunnick.easyim.packet.GroupMessageResponsePacket; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * Created by Sunnick on 2019/1/22/022. 11 | */ 12 | public class GroupMessageResponseHandler extends EasyImChannelInBoundHandler { 13 | 14 | private static GroupMessageResponseHandler instance = new GroupMessageResponseHandler(); 15 | private GroupMessageResponseHandler(){} 16 | public static GroupMessageResponseHandler getInstance(){ 17 | return instance; 18 | } 19 | 20 | private static Logger logger = LoggerFactory.getLogger(GroupMessageResponseHandler.class); 21 | 22 | @Override 23 | protected void handleResponse(ChannelHandlerContext ctx, GroupMessageResponsePacket packet) { 24 | logger.info("收到群聊{}的消息:{}-->{}",packet.getGroupId(),packet.getFromUserName(),packet.getGroupMsg()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /easy-im/easy-im-client/src/main/java/com/sunnick/easyim/handler/LoginHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.sunnick.easyim.packet.LoginRequestPacket; 5 | import io.netty.channel.ChannelHandler; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | import io.netty.channel.SimpleChannelInboundHandler; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | /** 13 | * Created by Sunnick on 2019/1/24/024. 14 | */ 15 | public class LoginHandler extends ChannelInboundHandlerAdapter { 16 | private static Logger logger = LoggerFactory.getLogger(LoginHandler.class); 17 | 18 | private String userId; 19 | private String userName; 20 | 21 | public LoginHandler(String userId, String userName) { 22 | this.userId = userId; 23 | this.userName = userName; 24 | } 25 | 26 | @Override 27 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 28 | //发起登录 29 | LoginRequestPacket packet = getLoginRequestPacket(); 30 | logger.info("ready to login!-->" + JSON.toJSONString(packet)); 31 | ctx.channel().writeAndFlush(packet); 32 | } 33 | 34 | public LoginRequestPacket getLoginRequestPacket() { 35 | LoginRequestPacket packet = new LoginRequestPacket(); 36 | packet.setUserId(this.userId); 37 | packet.setUserName(this.userName); 38 | return packet; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /easy-im/easy-im-client/src/main/java/com/sunnick/easyim/handler/LoginResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.sunnick.easyim.packet.LoginRequestPacket; 5 | import com.sunnick.easyim.packet.LoginResponsePacket; 6 | import com.sunnick.easyim.protocol.Packet; 7 | import com.sunnick.easyim.util.LoginUtil; 8 | import io.netty.channel.ChannelHandler; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.channel.SimpleChannelInboundHandler; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * Created by Sunnick on 2019/1/13/013. 16 | * 处理登录响应 17 | */ 18 | @ChannelHandler.Sharable 19 | public class LoginResponseHandler extends SimpleChannelInboundHandler { 20 | 21 | private static Logger logger = LoggerFactory.getLogger(LoginResponseHandler.class); 22 | 23 | public static LoginResponseHandler getInstance(){ 24 | return instance; 25 | } 26 | 27 | private LoginResponseHandler(){} 28 | 29 | private static LoginResponseHandler instance = new LoginResponseHandler(); 30 | 31 | 32 | 33 | /** 34 | * 登录成功响应 35 | */ 36 | private void loginResponse(ChannelHandlerContext ctx,Packet packet) { 37 | LoginResponsePacket response = (LoginResponsePacket) packet; 38 | if (response.success()){ 39 | logger.info("login success!"); 40 | LoginUtil.markAsLogin(ctx.channel()); 41 | }else { 42 | logger.info("login failed!-->" + JSON.toJSONString(response)); 43 | } 44 | } 45 | 46 | @Override 47 | protected void channelRead0(ChannelHandlerContext channelHandlerContext, LoginResponsePacket packet) throws Exception { 48 | loginResponse(channelHandlerContext,packet); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /easy-im/easy-im-client/src/main/java/com/sunnick/easyim/handler/MessageResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.sunnick.easyim.packet.MessageResponsePacket; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * Created by Sunnick on 2019/1/13/013. 11 | * 处理消息响应 12 | */ 13 | public class MessageResponseHandler extends EasyImChannelInBoundHandler { 14 | 15 | private static Logger logger = LoggerFactory.getLogger(MessageResponseHandler.class); 16 | 17 | public static MessageResponseHandler getInstance(){ 18 | return instance; 19 | } 20 | 21 | private MessageResponseHandler(){} 22 | 23 | private static MessageResponseHandler instance = new MessageResponseHandler(); 24 | 25 | /** 26 | * 处理消息请求 27 | */ 28 | private void handlMessage(MessageResponsePacket response) { 29 | logger.info("收到{}的消息:{}" ,response.getFromUserId(),response.getMessage()); 30 | } 31 | 32 | @Override 33 | protected void handleResponse(ChannelHandlerContext ctx, MessageResponsePacket messageResponsePacket) { 34 | handlMessage(messageResponsePacket); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /easy-im/easy-im-client/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/easy-im-commom.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | easy-im 7 | com.sunnick 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | easy-im-commom 13 | 14 | 15 | 16 | 17 | org.apache.maven.plugins 18 | maven-compiler-plugin 19 | 3.5 20 | 21 | 1.8 22 | 1.8 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/Command/BroadcastCommand.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.Command; 2 | 3 | import com.sunnick.easyim.packet.MessageRequestPacket; 4 | import com.sunnick.easyim.util.ChannelUtil; 5 | import io.netty.channel.Channel; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * Created by Sunnick on 2019/1/18/018. 11 | * 广播 broadcast::msg 12 | */ 13 | public class BroadcastCommand implements ConsoleCommand { 14 | 15 | private static Logger logger = LoggerFactory.getLogger(P2PCommand.class); 16 | 17 | public void exec(Channel channel,String string) { 18 | String[] strs = string.split("::"); 19 | if(strs.length < 2){ 20 | logger.info("广播请按如下格式发送:broadcast::msg"); 21 | }else { 22 | MessageRequestPacket packet = buildRequestPacket(strs[1]); 23 | ChannelUtil.writeAndFlush(channel,packet); 24 | } 25 | } 26 | 27 | private MessageRequestPacket buildRequestPacket(String msg) { 28 | MessageRequestPacket request = new MessageRequestPacket(); 29 | request.setMessage(msg); 30 | return request; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/Command/CommandManager.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.Command; 2 | 3 | import com.sunnick.easyim.protocol.Command; 4 | import io.netty.channel.Channel; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.util.Map; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | /** 12 | * Created by Sunnick on 2019/1/18/018. 13 | * 14 | * 命令管理器 15 | */ 16 | public class CommandManager { 17 | 18 | private static Logger logger = LoggerFactory.getLogger(CommandManager.class); 19 | 20 | /** 21 | * 单例模式 22 | */ 23 | private static CommandManager instance = new CommandManager(); 24 | 25 | private static Map commandMap = new ConcurrentHashMap(); 26 | 27 | static { 28 | commandMap.putIfAbsent("sendToUser",new P2PCommand()); 29 | commandMap.putIfAbsent("broadcast",new BroadcastCommand()); 30 | commandMap.putIfAbsent("createGroup",new CreateGroupCommand()); 31 | commandMap.putIfAbsent("sendToGroup",new GroupMessageCommand()); 32 | 33 | } 34 | 35 | public static CommandManager getInstance(){ 36 | return instance; 37 | } 38 | 39 | private CommandManager(){ } 40 | 41 | 42 | /** 43 | * 命令执行: 44 | * 单聊 sendToUser::userId::msg 45 | * 群聊 sendToGroup::groupId::msg 46 | * 发起群聊 createGroup::userId1,userId2,userId3... 47 | * 退出群聊 quitGroup::groupId 48 | * 加入群聊 joinGroup::groupId 49 | * 查询所有在线用户 getAllUsers 50 | * 查询群聊中在线用户 getGroupUsers::groupId 51 | * 广播 broadcast::msg 52 | */ 53 | public static void exec(Channel channel,String msg){ 54 | String[] strs = msg.split("::"); 55 | ConsoleCommand command = commandMap.get(strs[0]); 56 | if(command != null){ 57 | command.exec(channel,msg); 58 | }else{ 59 | logger.info("命令输入有误,请使用 command::content 的格式!"); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/Command/ConsoleCommand.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.Command; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | /** 6 | * Created by Sunnick on 2019/1/18/018. 7 | * 命令行指令接口 8 | * 单聊 sendToUser::userId::msg 9 | * 群聊 sendToGroup::groupId::msg 10 | * 发起群聊 createGroup::userId1,userId2,userId3... 11 | * 退出群聊 quitGroup::groupId 12 | * 加入群聊 joinGroup::groupId 13 | * 查询所有在线用户 getAllUsers 14 | * 查询群聊中在线用户 getGroupUsers::groupId 15 | * 广播 broadcast::msg 16 | */ 17 | public interface ConsoleCommand { 18 | void exec(Channel channel, String string); 19 | } 20 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/Command/CreateGroupCommand.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.Command; 2 | 3 | import com.sunnick.easyim.packet.CreateGroupRequestPacket; 4 | import com.sunnick.easyim.util.ChannelUtil; 5 | import io.netty.channel.Channel; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * Created by Sunnick on 2019/1/20/020. 14 | * 发起群聊 createGroup::userId1,userId2,userId3... 15 | */ 16 | public class CreateGroupCommand implements ConsoleCommand { 17 | 18 | private static Logger logger = LoggerFactory.getLogger(CreateGroupCommand.class); 19 | 20 | @Override 21 | public void exec(Channel channel, String string) { 22 | 23 | String[] strs = string.split("::"); 24 | if(strs.length < 2){ 25 | logger.info("创建群聊请使用 createGroup::userId1,userId2,userId3... 命令!"); 26 | return; 27 | } 28 | CreateGroupRequestPacket packet = buildPacket(strs[1]); 29 | ChannelUtil.writeAndFlush(channel,packet); 30 | } 31 | 32 | private CreateGroupRequestPacket buildPacket(String usersString) { 33 | CreateGroupRequestPacket packet = new CreateGroupRequestPacket(); 34 | String[] users = usersString.split(","); 35 | List userList = new ArrayList(); 36 | for (String str : users){ 37 | userList.add(str); 38 | } 39 | packet.setUsers(userList); 40 | 41 | return packet; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/Command/GroupMessageCommand.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.Command; 2 | 3 | import com.sunnick.easyim.packet.GroupMessageRequestPacket; 4 | import com.sunnick.easyim.util.ChannelUtil; 5 | import io.netty.channel.Channel; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * Created by Sunnick on 2019/1/22/022. 11 | * 群聊 sendToGroup::groupId::msg 12 | */ 13 | public class GroupMessageCommand implements ConsoleCommand { 14 | 15 | private static Logger logger = LoggerFactory.getLogger(GroupMessageCommand.class); 16 | 17 | @Override 18 | public void exec(Channel channel, String string) { 19 | String[] strs = string.split("::"); 20 | if(strs.length < 3){ 21 | logger.info("群聊请输入如下命令 sendToGroup::groupId::msg"); 22 | return; 23 | } 24 | GroupMessageRequestPacket packet = BuildRequestPacket(strs[1],strs[2]); 25 | ChannelUtil.writeAndFlush(channel,packet); 26 | } 27 | 28 | private GroupMessageRequestPacket BuildRequestPacket(String groupId,String groupMsg) { 29 | GroupMessageRequestPacket packet = new GroupMessageRequestPacket(); 30 | packet.setGroupId(groupId); 31 | packet.setGroupMsg(groupMsg); 32 | return packet; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/Command/P2PCommand.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.Command; 2 | 3 | import com.sunnick.easyim.packet.MessageRequestPacket; 4 | import com.sunnick.easyim.protocol.Packet; 5 | import com.sunnick.easyim.protocol.PacketCodeC; 6 | import com.sunnick.easyim.util.ChannelUtil; 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.channel.Channel; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | /** 13 | * Created by Sunnick on 2019/1/18/018. 14 | * 私聊 sendToUser::userId::msg 15 | */ 16 | public class P2PCommand implements ConsoleCommand{ 17 | private static Logger logger = LoggerFactory.getLogger(P2PCommand.class); 18 | 19 | public void exec(Channel channel,String string) { 20 | String[] strs = string.split("::"); 21 | if(strs.length < 3){ 22 | logger.info("私聊请按如下格式发送:sendToUser::userId::msg"); 23 | }else { 24 | MessageRequestPacket packet = buildMessageRequestPacket(strs[1],strs[2]); 25 | ChannelUtil.writeAndFlush(channel,packet); 26 | } 27 | } 28 | 29 | 30 | private MessageRequestPacket buildMessageRequestPacket(String userId,String msg) { 31 | MessageRequestPacket request = new MessageRequestPacket(); 32 | logger.info("发送消息给{}:{}",userId,msg); 33 | request.setToUserId(userId); 34 | request.setMessage(msg); 35 | return request; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/callback/EasyImCallback.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.callback; 2 | 3 | import com.sunnick.easyim.protocol.Packet; 4 | import io.netty.channel.ChannelHandlerContext; 5 | 6 | /** 7 | * Created by Sunnick on 2019/1/27/027. 8 | * 事件回调 9 | */ 10 | public interface EasyImCallback { 11 | void callback(ChannelHandlerContext ctx, T t); 12 | } 13 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/handler/DefaultErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.sunnick.easyim.packet.DefaultErrorPacket; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * Created by Sunnick on 2019/1/22/022. 11 | */ 12 | public class DefaultErrorHandler extends EasyImChannelInBoundHandler { 13 | 14 | private static DefaultErrorHandler instance = new DefaultErrorHandler(); 15 | public static DefaultErrorHandler getInstance(){ 16 | return instance; 17 | } 18 | private DefaultErrorHandler(){} 19 | 20 | private static Logger logger = LoggerFactory.getLogger(DefaultErrorHandler.class); 21 | 22 | @Override 23 | protected void handleResponse(ChannelHandlerContext ctx, DefaultErrorPacket packet) { 24 | logger.info("{}---{}",packet.getCode(),packet.getMsg()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/handler/EasyImChannelInBoundHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.sunnick.easyim.callback.EasyImCallback; 4 | import com.sunnick.easyim.protocol.Packet; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.SimpleChannelInboundHandler; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * Created by Sunnick on 2019/1/27/027. 14 | * 业务handler的基类。 15 | */ 16 | public abstract class EasyImChannelInBoundHandler extends SimpleChannelInboundHandler { 17 | /** 18 | * 用来存放回调列表 19 | */ 20 | protected static List callbackList = new ArrayList(); 21 | 22 | @Override 23 | protected void channelRead0(ChannelHandlerContext ctx, T t) throws Exception { 24 | handleResponse(ctx, t); 25 | dealCallbacks(ctx, t); 26 | } 27 | 28 | protected void dealCallbacks(ChannelHandlerContext ctx, T t){ 29 | for (EasyImCallback callback : callbackList){ 30 | callback.callback(ctx,t); 31 | } 32 | } 33 | 34 | public static void addCalbBack(EasyImCallback callback){ 35 | callbackList.add(callback); 36 | } 37 | public static void removeCallback(EasyImCallback callback){ 38 | callbackList.remove(callback); 39 | } 40 | 41 | protected abstract void handleResponse(ChannelHandlerContext ctx, T t); 42 | } 43 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/handler/MagicNumValidator.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 7 | 8 | import static com.sunnick.easyim.protocol.PacketCodeC.MAGIC_NUMBER; 9 | 10 | /** 11 | * Created by Sunnick on 2019/1/13/013. 12 | */ 13 | public class MagicNumValidator extends LengthFieldBasedFrameDecoder { 14 | 15 | private static final int LENGTH_FIELD_OFFSET = 7; 16 | private static final int LENGTH_FIELD_LENGTH = 4; 17 | 18 | public MagicNumValidator() { 19 | super(Integer.MAX_VALUE, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH); 20 | } 21 | 22 | @Override 23 | protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { 24 | //魔数校验不通过 25 | if(in.getInt(in.readerIndex()) != MAGIC_NUMBER){ 26 | ctx.channel().close(); 27 | return null; 28 | } 29 | 30 | return super.decode(ctx, in); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/handler/PacketCodecHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.sunnick.easyim.protocol.Packet; 4 | import com.sunnick.easyim.protocol.PacketCodeC; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandler; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.handler.codec.MessageToMessageCodec; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Created by Sunnick on 2019/1/18/018. 14 | */ 15 | @ChannelHandler.Sharable 16 | public class PacketCodecHandler extends MessageToMessageCodec { 17 | 18 | private PacketCodecHandler(){} 19 | 20 | private static PacketCodecHandler instance = new PacketCodecHandler(); 21 | 22 | public static PacketCodecHandler getInstance(){ 23 | return instance; 24 | } 25 | 26 | protected void encode(ChannelHandlerContext ctx, Packet packet, List list) throws Exception { 27 | ByteBuf byteBuf = ctx.channel().alloc().ioBuffer(); 28 | PacketCodeC.getInstance().encode(byteBuf,packet); 29 | list.add(byteBuf); 30 | 31 | } 32 | 33 | protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List list) throws Exception { 34 | list.add(PacketCodeC.getInstance().decode(buf)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/handler/PacketDecoder.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.sunnick.easyim.protocol.Packet; 5 | import com.sunnick.easyim.protocol.PacketCodeC; 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.handler.codec.ByteToMessageDecoder; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * Created by Sunnick on 2019/1/13/013. 16 | */ 17 | public class PacketDecoder extends ByteToMessageDecoder { 18 | 19 | private static Logger logger = LoggerFactory.getLogger(PacketDecoder.class); 20 | 21 | @Override 22 | protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { 23 | Packet packet = PacketCodeC.getInstance().decode(byteBuf); 24 | list.add(packet); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/handler/PacketEncoder.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.sunnick.easyim.protocol.Packet; 4 | import com.sunnick.easyim.protocol.PacketCodeC; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.MessageToByteEncoder; 8 | 9 | /** 10 | * Created by Sunnick on 2019/1/13/013. 11 | */ 12 | public class PacketEncoder extends MessageToByteEncoder { 13 | @Override 14 | protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf byteBuf) throws Exception { 15 | PacketCodeC.getInstance().encode(byteBuf,packet); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/handler/ServerIdleHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.timeout.IdleStateEvent; 5 | import io.netty.handler.timeout.IdleStateHandler; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * Created by Sunnick on 2019/1/27/027. 11 | * 心跳检测,150s没收到心跳包的话,断开连接 12 | */ 13 | public class ServerIdleHandler extends IdleStateHandler { 14 | private static Logger logger = LoggerFactory.getLogger(ServerIdleHandler.class); 15 | 16 | private static int HERT_BEAT_TIME = 150; 17 | 18 | public ServerIdleHandler() { 19 | super(0, 0, HERT_BEAT_TIME); 20 | } 21 | 22 | @Override 23 | protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception { 24 | logger.info("{}内没有收到心跳,关闭连接...",HERT_BEAT_TIME); 25 | ctx.channel().close(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/packet/BaseResponsePacket.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.packet; 2 | 3 | import com.sunnick.easyim.protocol.Packet; 4 | 5 | /** 6 | * Created by Sunnick on 2019/1/13/013. 7 | */ 8 | public abstract class BaseResponsePacket extends Packet { 9 | /** 10 | * 返回码,0000表示成功 11 | */ 12 | private String code = "0000"; 13 | /** 14 | * 返回消息 15 | */ 16 | private String msg; 17 | 18 | /** 19 | * 判断是否操作成功,0000表示成功 20 | */ 21 | public boolean success(){ 22 | return "0000".equals(code); 23 | } 24 | 25 | public String getCode() { 26 | return code; 27 | } 28 | 29 | public void setCode(String code) { 30 | this.code = code; 31 | } 32 | 33 | public String getMsg() { 34 | return msg; 35 | } 36 | 37 | public void setMsg(String msg) { 38 | this.msg = msg; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/packet/CreateGroupRequestPacket.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.packet; 2 | 3 | import com.sunnick.easyim.protocol.Packet; 4 | 5 | import java.util.List; 6 | 7 | import static com.sunnick.easyim.protocol.Command.CREATE_GROUP_REQUEST; 8 | 9 | /** 10 | * Created by Sunnick on 2019/1/20/020. 11 | * 12 | * 创建群聊 13 | */ 14 | public class CreateGroupRequestPacket extends Packet { 15 | @Override 16 | public Byte getCommand() { 17 | return CREATE_GROUP_REQUEST; 18 | } 19 | 20 | private List users ; 21 | 22 | public List getUsers() { 23 | return users; 24 | } 25 | 26 | public void setUsers(List users) { 27 | this.users = users; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/packet/CreateGroupResponsePacket.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.packet; 2 | 3 | import java.util.List; 4 | 5 | import static com.sunnick.easyim.protocol.Command.CREATE_GROUP_RESPONSE; 6 | 7 | /** 8 | * Created by Sunnick on 2019/1/20/020. 9 | */ 10 | public class CreateGroupResponsePacket extends BaseResponsePacket { 11 | 12 | private String groupId; 13 | 14 | private List userNames; 15 | 16 | public List getUserNames() { 17 | return userNames; 18 | } 19 | 20 | public void setUserNames(List userNames) { 21 | this.userNames = userNames; 22 | } 23 | 24 | public String getGroupId() { 25 | return groupId; 26 | } 27 | 28 | public void setGroupId(String groupId) { 29 | this.groupId = groupId; 30 | } 31 | 32 | @Override 33 | public Byte getCommand() { 34 | return CREATE_GROUP_RESPONSE; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/packet/DefaultErrorPacket.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.packet; 2 | 3 | import static com.sunnick.easyim.protocol.Command.DEFAULT_ERROR; 4 | 5 | /** 6 | * Created by Sunnick on 2019/1/22/022. 7 | * 8 | * 错误报文 9 | */ 10 | public class DefaultErrorPacket extends BaseResponsePacket{ 11 | 12 | @Override 13 | public Byte getCommand() { 14 | return DEFAULT_ERROR; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/packet/GroupMessageRequestPacket.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.packet; 2 | 3 | import com.sunnick.easyim.protocol.Packet; 4 | 5 | import static com.sunnick.easyim.protocol.Command.GROUP_MESSAGE_REQUEST; 6 | 7 | /** 8 | * Created by Sunnick on 2019/1/22/022. 9 | */ 10 | public class GroupMessageRequestPacket extends Packet { 11 | 12 | private String groupId; 13 | private String groupMsg; 14 | 15 | @Override 16 | public Byte getCommand() { 17 | return GROUP_MESSAGE_REQUEST; 18 | } 19 | 20 | public String getGroupId() { 21 | return groupId; 22 | } 23 | 24 | public void setGroupId(String groupId) { 25 | this.groupId = groupId; 26 | } 27 | 28 | public String getGroupMsg() { 29 | return groupMsg; 30 | } 31 | 32 | public void setGroupMsg(String groupMsg) { 33 | this.groupMsg = groupMsg; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/packet/GroupMessageResponsePacket.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.packet; 2 | 3 | import static com.sunnick.easyim.protocol.Command.GROUP_MESSAGE_RESPONSE; 4 | 5 | /** 6 | * Created by Sunnick on 2019/1/22/022. 7 | */ 8 | public class GroupMessageResponsePacket extends BaseResponsePacket { 9 | 10 | private String groupId; 11 | private String fromUserName; 12 | private String groupMsg; 13 | 14 | @Override 15 | public Byte getCommand() { 16 | return GROUP_MESSAGE_RESPONSE; 17 | } 18 | 19 | public String getGroupId() { 20 | return groupId; 21 | } 22 | 23 | public void setGroupId(String groupId) { 24 | this.groupId = groupId; 25 | } 26 | 27 | public String getFromUserName() { 28 | return fromUserName; 29 | } 30 | 31 | public void setFromUserName(String fromUserName) { 32 | this.fromUserName = fromUserName; 33 | } 34 | 35 | public String getGroupMsg() { 36 | return groupMsg; 37 | } 38 | 39 | public void setGroupMsg(String groupMsg) { 40 | this.groupMsg = groupMsg; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/packet/HeartBeatPacket.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.packet; 2 | 3 | import com.sunnick.easyim.protocol.Packet; 4 | 5 | import static com.sunnick.easyim.protocol.Command.HEART_BEAT; 6 | 7 | /** 8 | * Created by Sunnick on 2019/1/27/027. 9 | */ 10 | public class HeartBeatPacket extends Packet { 11 | 12 | private String msg = "ping-pong"; 13 | 14 | public String getMsg() { 15 | return msg; 16 | } 17 | 18 | public void setMsg(String msg) { 19 | this.msg = msg; 20 | } 21 | 22 | @Override 23 | public Byte getCommand() { 24 | return HEART_BEAT; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/packet/LoginRequestPacket.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.packet; 2 | 3 | import com.sunnick.easyim.protocol.Command; 4 | import com.sunnick.easyim.protocol.Packet; 5 | 6 | /** 7 | * Created by Sunnick on 2019/1/13/013. 8 | * 登录请求包 9 | */ 10 | 11 | public class LoginRequestPacket extends Packet { 12 | 13 | private String userId; 14 | private String userName; 15 | private String password; 16 | 17 | @Override 18 | public Byte getCommand() { 19 | return Command.LOGIN_REQUEST; 20 | } 21 | 22 | public String getUserId() { 23 | return userId; 24 | } 25 | 26 | public void setUserId(String userId) { 27 | this.userId = userId; 28 | } 29 | 30 | public String getUserName() { 31 | return userName; 32 | } 33 | 34 | public void setUserName(String userName) { 35 | this.userName = userName; 36 | } 37 | 38 | public String getPassword() { 39 | return password; 40 | } 41 | 42 | public void setPassword(String password) { 43 | this.password = password; 44 | } 45 | 46 | public LoginRequestPacket(String userId, String userName, String password) { 47 | this.userId = userId; 48 | this.userName = userName; 49 | this.password = password; 50 | } 51 | public LoginRequestPacket(){ 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/packet/LoginResponsePacket.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.packet; 2 | 3 | import static com.sunnick.easyim.protocol.Command.LOGIN_RESPONSE; 4 | 5 | /** 6 | * Created by Sunnick on 2019/1/13/013. 7 | * 8 | * 登录响应packet 9 | */ 10 | public class LoginResponsePacket extends BaseResponsePacket { 11 | public Byte getCommand() { 12 | return LOGIN_RESPONSE; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/packet/MessageRequestPacket.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.packet; 2 | 3 | import com.sunnick.easyim.protocol.Packet; 4 | 5 | import static com.sunnick.easyim.protocol.Command.MESSAGE_REQUEST; 6 | 7 | /** 8 | * Created by Sunnick on 2019/1/13/013. 9 | * 10 | * 发送消息的packet 11 | */ 12 | public class MessageRequestPacket extends Packet { 13 | 14 | /** 15 | * 消息内容 16 | */ 17 | private String message; 18 | 19 | 20 | /** 21 | * 消息接受者 22 | */ 23 | private String toUserId; 24 | 25 | 26 | public String getToUserId() { 27 | return toUserId; 28 | } 29 | 30 | public void setToUserId(String toUserId) { 31 | this.toUserId = toUserId; 32 | } 33 | 34 | @Override 35 | public Byte getCommand() { 36 | return MESSAGE_REQUEST; 37 | } 38 | 39 | public String getMessage() { 40 | return message; 41 | } 42 | 43 | public void setMessage(String message) { 44 | this.message = message; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/packet/MessageResponsePacket.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.packet; 2 | 3 | import static com.sunnick.easyim.protocol.Command.MESSAGE_RESPONSE; 4 | 5 | /** 6 | * Created by Sunnick on 2019/1/13/013. 7 | * 消息响应 8 | */ 9 | public class MessageResponsePacket extends BaseResponsePacket { 10 | 11 | /** 12 | * 响应内容 13 | */ 14 | private String message; 15 | 16 | /** 17 | * 消息来源 18 | */ 19 | private String fromUserId; 20 | private String fromUserName; 21 | 22 | public String getFromUserId() { 23 | return fromUserId; 24 | } 25 | 26 | public void setFromUserId(String fromUserId) { 27 | this.fromUserId = fromUserId; 28 | } 29 | 30 | public String getFromUserName() { 31 | return fromUserName; 32 | } 33 | 34 | public void setFromUserName(String fromUserName) { 35 | this.fromUserName = fromUserName; 36 | } 37 | 38 | @Override 39 | public Byte getCommand() { 40 | return MESSAGE_RESPONSE; 41 | } 42 | 43 | public String getMessage() { 44 | return message; 45 | } 46 | 47 | public void setMessage(String message) { 48 | this.message = message; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/protocol/Command.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.protocol; 2 | 3 | /** 4 | * Created by Sunnick on 2019/1/13/013. 5 | * 6 | * 指令集 7 | */ 8 | public interface Command { 9 | /** 10 | * 心跳包 11 | */ 12 | final Byte HEART_BEAT = 0; 13 | /** 14 | * 登录请求 15 | */ 16 | final Byte LOGIN_REQUEST = 1; 17 | /** 18 | * 登录响应 19 | */ 20 | final Byte LOGIN_RESPONSE = 2; 21 | 22 | /** 23 | * 消息请求 24 | */ 25 | final Byte MESSAGE_REQUEST = 3; 26 | /** 27 | * 消息响应 28 | */ 29 | final Byte MESSAGE_RESPONSE = 4; 30 | /** 31 | * 创建群聊 32 | */ 33 | final Byte CREATE_GROUP_REQUEST = 5; 34 | 35 | /** 36 | * 创建群聊响应 37 | */ 38 | final Byte CREATE_GROUP_RESPONSE = 6; 39 | 40 | /** 41 | * 发送群聊 42 | */ 43 | final Byte GROUP_MESSAGE_REQUEST = 7; 44 | 45 | /** 46 | * 创建群聊响应 47 | */ 48 | final Byte GROUP_MESSAGE_RESPONSE = 8; 49 | 50 | /** 51 | * 默认错误 52 | */ 53 | final Byte DEFAULT_ERROR = 127; 54 | } 55 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/protocol/JsonSerializer.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.protocol; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | 5 | /** 6 | * Created by Sunnick on 2019/1/13/013. 7 | * 8 | * json序列化器 9 | */ 10 | public class JsonSerializer implements Serializer { 11 | 12 | public byte getSerializerAlgorithm() { 13 | return SerializeAlgorithm.json; 14 | } 15 | 16 | public byte[] serialize(Object object) { 17 | return JSON.toJSONBytes(object); 18 | } 19 | 20 | public T deSerialize(Class clazz, byte[] bytes) { 21 | return JSON.parseObject(bytes,clazz); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/protocol/Packet.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.protocol; 2 | 3 | /** 4 | * Created by Sunnick on 2019/1/13/013. 5 | * 6 | * 通信过程的对象,所有需要通过网络传输的对象,都继承自Packet 7 | */ 8 | 9 | public abstract class Packet { 10 | /** 11 | * 协议版本号 12 | */ 13 | private Byte version = 1; 14 | 15 | /** 16 | * 操作指令 17 | */ 18 | public abstract Byte getCommand(); 19 | 20 | public Byte getVersion() { 21 | return version; 22 | } 23 | 24 | public void setVersion(Byte version) { 25 | this.version = version; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/protocol/PacketCodeC.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.protocol; 2 | 3 | import com.sunnick.easyim.packet.*; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.ByteBufAllocator; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import static com.sunnick.easyim.protocol.Command.*; 11 | 12 | /** 13 | * Created by Sunnick on 2019/1/13/013. 14 | * 15 | * 编解码对象 16 | */ 17 | public class PacketCodeC { 18 | /** 19 | * 魔数 20 | */ 21 | public static final int MAGIC_NUMBER = 0x88888888; 22 | 23 | public static PacketCodeC instance = new PacketCodeC(); 24 | 25 | /** 26 | * 采用单例模式 27 | */ 28 | public static PacketCodeC getInstance(){ 29 | return instance; 30 | } 31 | 32 | private static final Map> packetTypeMap; 33 | private static final Map serializerMap; 34 | 35 | static { 36 | packetTypeMap = new HashMap>(); 37 | packetTypeMap.put(HEART_BEAT, HeartBeatPacket.class); 38 | packetTypeMap.put(LOGIN_REQUEST, LoginRequestPacket.class); 39 | packetTypeMap.put(LOGIN_RESPONSE, LoginResponsePacket.class); 40 | packetTypeMap.put(MESSAGE_REQUEST, MessageRequestPacket.class); 41 | packetTypeMap.put(MESSAGE_RESPONSE, MessageResponsePacket.class); 42 | packetTypeMap.put(CREATE_GROUP_REQUEST, CreateGroupRequestPacket.class); 43 | packetTypeMap.put(CREATE_GROUP_RESPONSE, CreateGroupResponsePacket.class); 44 | packetTypeMap.put(GROUP_MESSAGE_REQUEST, GroupMessageRequestPacket.class); 45 | packetTypeMap.put(GROUP_MESSAGE_RESPONSE, GroupMessageResponsePacket.class); 46 | 47 | serializerMap = new HashMap(); 48 | Serializer serializer = new JsonSerializer(); 49 | serializerMap.put(serializer.getSerializerAlgorithm(), serializer); 50 | } 51 | 52 | private PacketCodeC(){ 53 | 54 | } 55 | 56 | 57 | /** 58 | * 默认使用json序列化,如需修改,可以使用setSerializer(serializer) 59 | */ 60 | private static Serializer serializer = new JsonSerializer(); 61 | 62 | /** 63 | * 编码 64 | * 65 | * 魔数(4字节) + 版本号(1字节) + 序列化算法(1字节) + 指令(1字节) + 数据长度(4字节) + 数据(N字节) 66 | */ 67 | public ByteBuf encode(ByteBufAllocator alloc, Packet packet){ 68 | //创建ByteBuf对象 69 | ByteBuf buf = alloc.ioBuffer(); 70 | return encode(buf,packet); 71 | } 72 | 73 | public ByteBuf encode(ByteBuf buf,Packet packet){ 74 | //序列化java对象 75 | byte[] objBytes = serializer.serialize(packet); 76 | 77 | //实际编码过程,即组装通信包 78 | //魔数(4字节) + 版本号(1字节) + 序列化算法(1字节) + 指令(1字节) + 数据长度(4字节) + 数据(N字节) 79 | buf.writeInt(MAGIC_NUMBER); 80 | buf.writeByte(packet.getVersion()); 81 | buf.writeByte(serializer.getSerializerAlgorithm()); 82 | buf.writeByte(packet.getCommand()); 83 | buf.writeInt(objBytes.length); 84 | buf.writeBytes(objBytes); 85 | 86 | return buf; 87 | } 88 | 89 | public ByteBuf encode(Packet packet){ 90 | return encode(ByteBufAllocator.DEFAULT, packet); 91 | } 92 | 93 | /** 94 | * 解码 95 | * 96 | * 魔数(4字节) + 版本号(1字节) + 序列化算法(1字节) + 指令(1字节) + 数据长度(4字节) + 数据(N字节) 97 | */ 98 | public Packet decode(ByteBuf buf){ 99 | //魔数校验(暂不做) 100 | buf.skipBytes(4); 101 | //版本号校验(暂不做) 102 | buf.skipBytes(1); 103 | //序列化算法 104 | byte serializeAlgorithm = buf.readByte(); 105 | //指令 106 | byte command = buf.readByte(); 107 | //数据长度 108 | int length = buf.readInt(); 109 | //数据 110 | byte[] dataBytes = new byte[length]; 111 | buf.readBytes(dataBytes); 112 | 113 | Class packetType = getRequestType(command); 114 | Serializer serializer = getSerializer(serializeAlgorithm); 115 | if(packetType != null && serializer != null){ 116 | return serializer.deSerialize(packetType,dataBytes); 117 | } 118 | 119 | return null; 120 | } 121 | 122 | /** 123 | * 124 | * 根据序列化算法获取对应的serializer 125 | * @param serializeAlgorithm 126 | * @return serializer 127 | */ 128 | private Serializer getSerializer(byte serializeAlgorithm) { 129 | return serializerMap.get(serializeAlgorithm); 130 | } 131 | 132 | /** 133 | * 134 | * 根据指令类型获取对应的packet 135 | * @param command 136 | * @return packet 137 | */ 138 | private Class getRequestType(byte command) { 139 | return packetTypeMap.get(command); 140 | } 141 | 142 | /** 143 | * 设置序列化方式 144 | */ 145 | public static void setSerializer(Serializer serializer) { 146 | PacketCodeC.serializer = serializer; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/protocol/SerializeAlgorithm.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.protocol; 2 | 3 | /** 4 | * Created by Sunnick on 2019/1/13/013. 5 | * 6 | * 定义序列化算法 7 | */ 8 | public interface SerializeAlgorithm { 9 | /** 10 | * json序列化标识 11 | */ 12 | byte json = 1; 13 | } 14 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/protocol/Serializer.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.protocol; 2 | 3 | /** 4 | * Created by Sunnick on 2019/1/13/013. 5 | * 6 | * Serializer,用来指定序列化算法,用于序列化对象 7 | */ 8 | public interface Serializer { 9 | 10 | 11 | /** 12 | * @return 序列化算法 13 | */ 14 | byte getSerializerAlgorithm(); 15 | 16 | /** 17 | * 将对象序列化成二进制 18 | * 19 | */ 20 | byte[] serialize(Object object); 21 | 22 | /** 23 | * 将二进制反序列化为对象 24 | */ 25 | T deSerialize(Class clazz, byte[] bytes); 26 | } 27 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/util/Attributes.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.util; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.util.AttributeKey; 5 | 6 | /** 7 | * Created by Sunnick on 2019/1/13/013. 8 | */ 9 | public interface Attributes { 10 | /** 11 | * 登录状态,使用channel.attr()方法保存 12 | */ 13 | AttributeKey LOGIN = AttributeKey.newInstance("login"); 14 | AttributeKey SESSION = AttributeKey.newInstance("session"); 15 | } 16 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/util/ChannelUtil.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.util; 2 | 3 | import com.sunnick.easyim.protocol.Packet; 4 | import com.sunnick.easyim.protocol.PacketCodeC; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.Channel; 7 | 8 | /** 9 | * Created by Sunnick on 2019/1/18/018. 10 | */ 11 | public class ChannelUtil { 12 | public static void writeAndFlush(Channel channel, Packet packet){ 13 | ByteBuf buf = PacketCodeC.getInstance().encode(packet); 14 | channel.writeAndFlush(buf); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/util/GroupUtil.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.util; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.group.ChannelGroup; 6 | import io.netty.channel.group.DefaultChannelGroup; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.UUID; 13 | import java.util.concurrent.ConcurrentHashMap; 14 | 15 | /** 16 | * Created by Sunnick on 2019/1/20/020. 17 | */ 18 | public class GroupUtil { 19 | 20 | private static Logger logger = LoggerFactory.getLogger(GroupUtil.class); 21 | 22 | /** 23 | * 服务端用来保存群聊关系 24 | */ 25 | private static final Map groupMap = new ConcurrentHashMap<>(); 26 | 27 | /** 28 | * @param ctx 29 | * @param users 30 | * @return groupId 31 | */ 32 | public static String createGroup(ChannelHandlerContext ctx, List users){ 33 | String groupId = null; 34 | ChannelGroup group = new DefaultChannelGroup(ctx.executor()); 35 | for(String userId : users){ 36 | Channel channel = SessionUtil.getChannelByUserId(userId); 37 | if(channel == null){ 38 | logger.info("该userid并没有登录,无法发起群聊:{}",userId); 39 | continue; 40 | } 41 | group.add(channel); 42 | } 43 | if (group.isEmpty()){ 44 | return groupId; 45 | } 46 | // TODO groupId生成算法需要改进 47 | groupId = UUID.randomUUID().toString().split("-")[0]; 48 | groupMap.putIfAbsent(groupId,group); 49 | return groupId; 50 | } 51 | 52 | 53 | public static ChannelGroup getChannelGroup(String groupId) { 54 | return groupMap.get(groupId); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/util/LoginUtil.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.util; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.util.Attribute; 5 | 6 | /** 7 | * Created by Sunnick on 2019/1/13/013. 8 | */ 9 | public class LoginUtil { 10 | /** 11 | * 标记登录状态 12 | */ 13 | public static void markAsLogin(Channel channel){ 14 | channel.attr(Attributes.LOGIN).set(true); 15 | } 16 | public static boolean hasLogin(Channel channel){ 17 | Attribute login = channel.attr(Attributes.LOGIN); 18 | //只要标志位不为空,即表示登录过 19 | if (login.get() != null) 20 | return true; 21 | return false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/util/Scan.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.util; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.sunnick.easyim.Command.CommandManager; 5 | import com.sunnick.easyim.packet.MessageRequestPacket; 6 | import com.sunnick.easyim.protocol.PacketCodeC; 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.channel.Channel; 9 | import io.netty.util.internal.StringUtil; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.Scanner; 14 | 15 | /** 16 | * Created by Sunnick on 2019/1/13/013. 17 | * 18 | */ 19 | public class Scan implements Runnable { 20 | 21 | private final static Logger logger = LoggerFactory.getLogger(Scan.class); 22 | 23 | private Channel channel; 24 | 25 | public Scan(Channel channel){ 26 | this.channel = channel; 27 | } 28 | 29 | public void run() { 30 | Scanner sc = new Scanner(System.in,"utf-8"); 31 | while (!Thread.interrupted()) { 32 | if(LoginUtil.hasLogin(channel)){ 33 | String msg = sc.nextLine(); 34 | if(!StringUtil.isNullOrEmpty(msg) && !StringUtil.isNullOrEmpty(msg.trim())){ 35 | CommandManager.getInstance().exec(this.channel,msg); 36 | } 37 | } 38 | } 39 | 40 | } 41 | // public void run() { 42 | // Scanner sc = new Scanner(System.in); 43 | // while (!Thread.interrupted()) { 44 | // if(LoginUtil.hasLogin(channel)){ 45 | // String msg = sc.nextLine(); 46 | // MessageRequestPacket messageRequestPacket = buildMessageRequestPacket(msg); 47 | // if(messageRequestPacket != null) { 48 | // writeMessage(messageRequestPacket); 49 | // } 50 | // } 51 | // } 52 | // 53 | // } 54 | 55 | // private void writeMessage(MessageRequestPacket messageRequestPacket) { 56 | // ByteBuf buf = PacketCodeC.getInstance().encode(messageRequestPacket); 57 | // channel.writeAndFlush(buf); 58 | // } 59 | // 60 | // private MessageRequestPacket buildMessageRequestPacket(String msg) { 61 | // MessageRequestPacket request = new MessageRequestPacket(); 62 | // //发送消息格式为 userId::message ,如果userId为空,则发给所有人 63 | // String[] strs = msg.split("::"); 64 | // if(strs.length < 2){ 65 | // logger.info("发送广播:{}",msg); 66 | // request.setMessage(msg); 67 | // }else { 68 | // logger.info("发送消息给{}:{}",strs[0],strs[1]); 69 | // request.setToUserId(strs[0]); 70 | // request.setMessage(strs[1]); 71 | // } 72 | // return request; 73 | // } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/util/Session.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.util; 2 | 3 | /** 4 | * Created by Sunnick on 2019/1/16/016. 5 | */ 6 | public class Session { 7 | private String userId; 8 | private String userName; 9 | 10 | public Session(String userId, String userName) { 11 | this.userId = userId; 12 | this.userName = userName; 13 | } 14 | 15 | public String getUserId() { 16 | return userId; 17 | } 18 | 19 | public void setUserId(String userId) { 20 | this.userId = userId; 21 | } 22 | 23 | public String getUserName() { 24 | return userName; 25 | } 26 | 27 | public void setUserName(String userName) { 28 | this.userName = userName; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /easy-im/easy-im-commom/src/main/java/com/sunnick/easyim/util/SessionUtil.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.util; 2 | 3 | import com.sunnick.easyim.packet.LoginRequestPacket; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.group.ChannelGroup; 6 | import io.netty.util.Attribute; 7 | 8 | import java.util.Map; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | /** 12 | * Created by Sunnick on 2019/1/16/016. 13 | */ 14 | public class SessionUtil { 15 | private static final Map sessionMap = new ConcurrentHashMap() ; 16 | 17 | 18 | public static void bindSession(Session session, Channel channel){ 19 | sessionMap.putIfAbsent(session.getUserId(),channel); 20 | channel.attr(Attributes.SESSION).set(session); 21 | } 22 | 23 | public static void unBindSession(Session session,Channel channel){ 24 | sessionMap.remove(session.getUserId()); 25 | channel.attr(Attributes.SESSION).remove(); 26 | } 27 | 28 | public static Channel getChannelBySession(Session session){ 29 | return getChannelByUserId(session.getUserId()); 30 | } 31 | 32 | public static Map getAllSession(){ 33 | return sessionMap; 34 | } 35 | 36 | public static Channel getChannelByUserId(String id){ 37 | return sessionMap.get(id); 38 | } 39 | 40 | public static Session getSessionByChannel(Channel channel){ 41 | return channel.attr(Attributes.SESSION).get(); 42 | } 43 | 44 | public static boolean hasLogin(Channel channel){ 45 | Attribute login = channel.attr(Attributes.SESSION); 46 | //只要标志位不为空,即表示登录过 47 | if (login.get() != null) 48 | return true; 49 | return false; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /easy-im/easy-im-server/easy-im-server.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /easy-im/easy-im-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | easy-im 7 | com.sunnick 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | easy-im-server 13 | 14 | 15 | 16 | com.sunnick 17 | easy-im-commom 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | 23 | 24 | 25 | org.apache.maven.plugins 26 | maven-jar-plugin 27 | 2.4 28 | 29 | 30 | 31 | 32 | com.sunnick.easyim.Server 33 | 34 | true 35 | 36 | ./ 37 | 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-assembly-plugin 44 | 45 | 46 | make-assembly 47 | package 48 | 49 | single 50 | 51 | 52 | ${project.name} 53 | src/main/assembly/assembly.xml 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /easy-im/easy-im-server/src/main/assembly/assembly.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tar.gz 6 | 7 | 8 | true 9 | 10 | 11 | 12 | true 13 | lib 14 | 15 | runtime 16 | 17 | 18 | 19 | 20 | src/main/bin 21 | / 22 | 23 | 24 | -------------------------------------------------------------------------------- /easy-im/easy-im-server/src/main/java/com/sunnick/easyim/Server.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim; 2 | 3 | import com.sunnick.easyim.handler.*; 4 | import io.netty.bootstrap.ServerBootstrap; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelFutureListener; 7 | import io.netty.channel.ChannelHandler; 8 | import io.netty.channel.ChannelInitializer; 9 | import io.netty.channel.nio.NioEventLoopGroup; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import io.netty.channel.socket.nio.NioSocketChannel; 12 | import io.netty.util.internal.StringUtil; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | /** 17 | * Created by Sunnick on 2019/1/12/012. 18 | */ 19 | 20 | @ChannelHandler.Sharable 21 | public class Server { 22 | 23 | private static Logger logger = LoggerFactory.getLogger(Server.class); 24 | 25 | private static int port = 8888; 26 | 27 | public static void main(String[] strings){ 28 | 29 | 30 | port = StringUtil.isNullOrEmpty(System.getProperty("port")) ? port : Integer.parseInt(System.getProperty("port")); 31 | 32 | NioEventLoopGroup boss = new NioEventLoopGroup(); 33 | NioEventLoopGroup worker = new NioEventLoopGroup(); 34 | ServerBootstrap bootstrap = new ServerBootstrap(); 35 | bootstrap.group(boss,worker).channel(NioServerSocketChannel.class) 36 | .childHandler(new ChannelInitializer() { 37 | @Override 38 | protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception { 39 | nioSocketChannel.pipeline().addLast(new ServerIdleHandler()); 40 | nioSocketChannel.pipeline().addLast(new MagicNumValidator()); 41 | nioSocketChannel.pipeline().addLast(PacketCodecHandler.getInstance()); 42 | nioSocketChannel.pipeline().addLast(LoginRequestHandler.getInstance()); 43 | nioSocketChannel.pipeline().addLast(HeartBeatHandler.getInstance()); 44 | nioSocketChannel.pipeline().addLast(AuthHandler.getInstance()); 45 | nioSocketChannel.pipeline().addLast(ServerHandler.getInstance()); 46 | } 47 | }); 48 | 49 | ChannelFuture future = bootstrap.bind(port); 50 | future.addListener(new ChannelFutureListener() { 51 | @Override 52 | public void operationComplete(ChannelFuture channelFuture) throws Exception { 53 | if (channelFuture.isSuccess()){ 54 | logger.info("server started! using port {} " , port); 55 | }else { 56 | logger.info("server start failed! using port {} " , port); 57 | channelFuture.cause().printStackTrace(); 58 | System.exit(0); 59 | } 60 | } 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /easy-im/easy-im-server/src/main/java/com/sunnick/easyim/handler/AuthHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.sunnick.easyim.util.LoginUtil; 4 | import com.sunnick.easyim.util.Session; 5 | import com.sunnick.easyim.util.SessionUtil; 6 | import io.netty.channel.ChannelHandler; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.ChannelInboundHandlerAdapter; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | /** 13 | * Created by Sunnick on 2019/1/14/014. 14 | * 15 | * 登录校验 16 | */ 17 | @ChannelHandler.Sharable 18 | public class AuthHandler extends ChannelInboundHandlerAdapter { 19 | 20 | private static Logger logger = LoggerFactory.getLogger(AuthHandler.class); 21 | 22 | public static AuthHandler getInstance(){ 23 | return instance; 24 | } 25 | 26 | private AuthHandler(){} 27 | 28 | private static AuthHandler instance = new AuthHandler(); 29 | 30 | @Override 31 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 32 | //如果已经登录,就可以继续走,没登录直接断开连接 33 | if(SessionUtil.hasLogin(ctx.channel())){ 34 | //如果登录了,只要连接没断,就无需再校验 35 | ctx.channel().pipeline().remove(this); 36 | super.channelRead(ctx, msg); 37 | }else{ 38 | ctx.channel().close(); 39 | } 40 | } 41 | 42 | @Override 43 | public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 44 | if(LoginUtil.hasLogin(ctx.channel())){ 45 | logger.info("登录验证已通过,后续无需再验证!"); 46 | }else{ 47 | logger.info("登录验证未通过,连接断开..."); 48 | } 49 | } 50 | 51 | @Override 52 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 53 | Session session = SessionUtil.getSessionByChannel(ctx.channel()); 54 | if (session != null ) { 55 | SessionUtil.unBindSession(session,ctx.channel()); 56 | logger.info("{}下线啦!",session.getUserId()); 57 | } 58 | ctx.channel().close(); 59 | } 60 | 61 | @Override 62 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 63 | //异常时断开连接 64 | Session session = SessionUtil.getSessionByChannel(ctx.channel()); 65 | if (session != null ) { 66 | SessionUtil.unBindSession(session,ctx.channel()); 67 | logger.info("{}下线啦!",session.getUserId()); 68 | } 69 | ctx.channel().close(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /easy-im/easy-im-server/src/main/java/com/sunnick/easyim/handler/CreateGroupRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.sunnick.easyim.packet.CreateGroupRequestPacket; 5 | import com.sunnick.easyim.packet.CreateGroupResponsePacket; 6 | import com.sunnick.easyim.util.GroupUtil; 7 | import com.sunnick.easyim.util.SessionUtil; 8 | import io.netty.channel.Channel; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.channel.SimpleChannelInboundHandler; 11 | import io.netty.util.internal.StringUtil; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * Created by Sunnick on 2019/1/20/020. 20 | */ 21 | public class CreateGroupRequestHandler extends EasyImChannelInBoundHandler { 22 | 23 | private static Logger logger = LoggerFactory.getLogger(CreateGroupRequestHandler.class); 24 | 25 | public static CreateGroupRequestHandler getInstance(){ 26 | return instance; 27 | } 28 | 29 | private CreateGroupRequestHandler(){} 30 | 31 | private static CreateGroupRequestHandler instance = new CreateGroupRequestHandler(); 32 | 33 | 34 | @Override 35 | protected void handleResponse(ChannelHandlerContext ctx, CreateGroupRequestPacket packet) { 36 | CreateGroupResponsePacket response = createGroup(ctx,packet); 37 | logger.info("返回给客户端:{}",JSON.toJSONString(response)); 38 | for (String userId : packet.getUsers()){ 39 | SessionUtil.getChannelByUserId(userId).writeAndFlush(response); 40 | } 41 | } 42 | 43 | private CreateGroupResponsePacket createGroup(ChannelHandlerContext ctx, CreateGroupRequestPacket packet) { 44 | CreateGroupResponsePacket response = new CreateGroupResponsePacket(); 45 | logger.info("收到创建群聊请求:{}", JSON.toJSONString(packet)); 46 | 47 | List users = packet.getUsers(); 48 | String groupId = GroupUtil.createGroup(ctx,users); 49 | if(StringUtil.isNullOrEmpty(groupId)){ 50 | response.setCode("1002"); 51 | response.setMsg("创建群聊失败!"); 52 | } 53 | List userNames = getUserNames(users); 54 | 55 | response.setGroupId(groupId); 56 | response.setUserNames(userNames); 57 | 58 | return response; 59 | } 60 | 61 | private List getUserNames(List users) { 62 | List userNames = new ArrayList<>(); 63 | 64 | for(String id : users){ 65 | Channel channel = SessionUtil.getChannelByUserId(id); 66 | if (channel != null){ 67 | String name = SessionUtil.getSessionByChannel(channel).getUserName(); 68 | userNames.add(name); 69 | } 70 | } 71 | return userNames; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /easy-im/easy-im-server/src/main/java/com/sunnick/easyim/handler/GroupMessageRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.sunnick.easyim.packet.GroupMessageRequestPacket; 4 | import com.sunnick.easyim.packet.GroupMessageResponsePacket; 5 | import com.sunnick.easyim.util.GroupUtil; 6 | import com.sunnick.easyim.util.SessionUtil; 7 | import io.netty.channel.Channel; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.SimpleChannelInboundHandler; 10 | import io.netty.channel.group.ChannelGroup; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * Created by Sunnick on 2019/1/22/022. 16 | */ 17 | public class GroupMessageRequestHandler extends EasyImChannelInBoundHandler { 18 | 19 | private static GroupMessageRequestHandler instance = new GroupMessageRequestHandler(); 20 | private GroupMessageRequestHandler(){} 21 | public static GroupMessageRequestHandler getInstance(){ 22 | return instance; 23 | } 24 | 25 | private static Logger logger = LoggerFactory.getLogger(GroupMessageRequestHandler.class); 26 | 27 | 28 | @Override 29 | protected void handleResponse(ChannelHandlerContext ctx, GroupMessageRequestPacket packet) { 30 | ChannelGroup channelGroup = GroupUtil.getChannelGroup(packet.getGroupId()); 31 | if(channelGroup == null){ 32 | logger.info("群聊不存在!"); 33 | return; 34 | } 35 | GroupMessageResponsePacket response = new GroupMessageResponsePacket(); 36 | response.setGroupMsg(packet.getGroupMsg()); 37 | response.setGroupId(packet.getGroupId()); 38 | response.setFromUserName(SessionUtil.getSessionByChannel(ctx.channel()).getUserName()); 39 | for(Channel channel : channelGroup){ 40 | channel.writeAndFlush(response); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /easy-im/easy-im-server/src/main/java/com/sunnick/easyim/handler/HeartBeatHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.sunnick.easyim.packet.HeartBeatPacket; 5 | import io.netty.channel.ChannelHandler; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | /** 12 | * Created by Sunnick on 2019/1/27/027. 13 | */ 14 | @ChannelHandler.Sharable 15 | public class HeartBeatHandler extends SimpleChannelInboundHandler { 16 | 17 | private static Logger logger = LoggerFactory.getLogger(HeartBeatHandler.class); 18 | 19 | private static HeartBeatHandler instance = new HeartBeatHandler(); 20 | private HeartBeatHandler(){} 21 | 22 | @Override 23 | protected void channelRead0(ChannelHandlerContext ctx, HeartBeatPacket heartBeatPacket) throws Exception { 24 | logger.info("收到心跳包:{}", JSON.toJSONString(heartBeatPacket)); 25 | ctx.writeAndFlush(heartBeatPacket); 26 | } 27 | 28 | public static HeartBeatHandler getInstance() { 29 | return instance; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /easy-im/easy-im-server/src/main/java/com/sunnick/easyim/handler/LoginRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.sunnick.easyim.packet.LoginRequestPacket; 4 | import com.sunnick.easyim.packet.LoginResponsePacket; 5 | import com.sunnick.easyim.util.LoginUtil; 6 | import com.sunnick.easyim.util.Session; 7 | import com.sunnick.easyim.util.SessionUtil; 8 | import io.netty.channel.ChannelHandler; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.channel.SimpleChannelInboundHandler; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * Created by Sunnick on 2019/1/13/013. 16 | */ 17 | @ChannelHandler.Sharable 18 | public class LoginRequestHandler extends EasyImChannelInBoundHandler { 19 | 20 | private static Logger logger = LoggerFactory.getLogger(LoginRequestHandler.class); 21 | 22 | public static LoginRequestHandler getInstance(){ 23 | return instance; 24 | } 25 | 26 | private LoginRequestHandler(){} 27 | 28 | private static LoginRequestHandler instance = new LoginRequestHandler(); 29 | 30 | /** 31 | * 处理登录请求 32 | */ 33 | private LoginResponsePacket login(ChannelHandlerContext ctx,LoginRequestPacket packet) { 34 | LoginResponsePacket response = new LoginResponsePacket(); 35 | if(valid(packet)){ 36 | //login success 37 | response.setCode("0000"); 38 | response.setMsg("登陆成功!"); 39 | logger.info("[{}],登录成功!,id为{}",packet.getUserName(),packet.getUserId()); 40 | LoginUtil.markAsLogin(ctx.channel()); 41 | SessionUtil.bindSession(new Session(packet.getUserId(),packet.getUserName()),ctx.channel()); 42 | }else{ 43 | //login failed 44 | response.setCode("1001"); 45 | response.setMsg("登陆失败!"); 46 | } 47 | return response; 48 | } 49 | 50 | 51 | /** 52 | * 校验登录 53 | * 54 | */ 55 | private boolean valid(LoginRequestPacket loginPacket) { 56 | return true; 57 | } 58 | 59 | @Override 60 | protected void handleResponse(ChannelHandlerContext ctx, LoginRequestPacket packet) { 61 | LoginResponsePacket response = login(ctx,packet); 62 | ctx.channel().writeAndFlush(response); 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /easy-im/easy-im-server/src/main/java/com/sunnick/easyim/handler/MessageRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.sunnick.easyim.packet.MessageRequestPacket; 5 | import com.sunnick.easyim.packet.MessageResponsePacket; 6 | import com.sunnick.easyim.util.Session; 7 | import com.sunnick.easyim.util.SessionUtil; 8 | import io.netty.channel.Channel; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.channel.SimpleChannelInboundHandler; 11 | import io.netty.util.internal.StringUtil; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.util.Map; 16 | 17 | /** 18 | * Created by Sunnick on 2019/1/13/013. 19 | */ 20 | public class MessageRequestHandler extends EasyImChannelInBoundHandler { 21 | 22 | private static Logger logger = LoggerFactory.getLogger(MessageRequestHandler.class); 23 | 24 | public static MessageRequestHandler getInstance(){ 25 | return instance; 26 | } 27 | 28 | private MessageRequestHandler(){} 29 | 30 | private static MessageRequestHandler instance = new MessageRequestHandler(); 31 | /** 32 | * 处理消息请求 33 | */ 34 | private void handlMessage(ChannelHandlerContext ctx,MessageRequestPacket request) { 35 | logger.info("收到客户端消息:{}", request.getMessage()); 36 | //拿到消息发送方的userId 37 | Session session = SessionUtil.getSessionByChannel(ctx.channel()); 38 | logger.info("发送方为:{}" ,JSON.toJSONString(session)); 39 | String fromUserId = session.getUserId(); 40 | String fromUserName = session.getUserName(); 41 | 42 | //构造发送报文 43 | MessageResponsePacket response = new MessageResponsePacket(); 44 | response.setFromUserId(fromUserId); 45 | response.setFromUserName(fromUserName); 46 | response.setMessage(request.getMessage()); 47 | 48 | 49 | //拿到接收方的信息 50 | String toUserId = request.getToUserId(); 51 | //toUserId不为空,则私聊;为空则发广播 52 | if(!StringUtil.isNullOrEmpty(toUserId)){ 53 | p2pChat(toUserId,response); 54 | }else{ 55 | broadcast(response); 56 | } 57 | 58 | 59 | } 60 | 61 | /** 62 | * 给所有人群发消息 63 | */ 64 | private void broadcast(MessageResponsePacket response) { 65 | Channel channel = null; 66 | //获取所有channel,遍历 67 | Map sessions = SessionUtil.getAllSession(); 68 | for (Map.Entry entry : sessions.entrySet()){ 69 | channel = entry.getValue(); 70 | logger.info("发送给客户端{}:{}" ,entry.getKey(), JSON.toJSONString(response)); 71 | writeMessage(response, channel); 72 | } 73 | } 74 | 75 | /** 76 | * 点对点私聊 77 | */ 78 | private void p2pChat(String toUserId, MessageResponsePacket response) { 79 | Channel toUserChannel = SessionUtil.getChannelByUserId(toUserId); 80 | logger.info("发送给客户端{}:{}" ,toUserId, JSON.toJSONString(response)); 81 | writeMessage(response, toUserChannel); 82 | } 83 | 84 | private void writeMessage(MessageResponsePacket response, Channel toUserChannel) { 85 | //写数据 86 | if (toUserChannel!= null && SessionUtil.hasLogin(toUserChannel) ) { 87 | toUserChannel.writeAndFlush(response); 88 | }else{ 89 | logger.info(" 该用户未登录,无法向他发送消息!"); 90 | } 91 | } 92 | 93 | @Override 94 | protected void handleResponse(ChannelHandlerContext ctx, MessageRequestPacket messageRequestPacket) { 95 | handlMessage(ctx,messageRequestPacket); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /easy-im/easy-im-server/src/main/java/com/sunnick/easyim/handler/ServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.sunnick.easyim.handler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.sunnick.easyim.protocol.Packet; 5 | import com.sunnick.easyim.util.Session; 6 | import com.sunnick.easyim.util.SessionUtil; 7 | import io.netty.channel.ChannelHandler; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.SimpleChannelInboundHandler; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.Map; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | 16 | import static com.sunnick.easyim.protocol.Command.*; 17 | 18 | 19 | /** 20 | * Created by Sunnick on 2019/1/13/013. 21 | */ 22 | @ChannelHandler.Sharable 23 | public class ServerHandler extends SimpleChannelInboundHandler { 24 | 25 | private static Logger logger = LoggerFactory.getLogger(ServerHandler.class); 26 | 27 | private ServerHandler(){} 28 | 29 | private static ServerHandler instance = new ServerHandler(); 30 | 31 | public static ServerHandler getInstance(){ 32 | return instance; 33 | } 34 | 35 | private static Map> handlerMap = new ConcurrentHashMap<>(); 36 | static{ 37 | handlerMap.putIfAbsent(MESSAGE_REQUEST,MessageRequestHandler.getInstance()); 38 | handlerMap.putIfAbsent(CREATE_GROUP_REQUEST,CreateGroupRequestHandler.getInstance()); 39 | handlerMap.putIfAbsent(GROUP_MESSAGE_REQUEST,GroupMessageRequestHandler.getInstance()); 40 | } 41 | 42 | @Override 43 | protected void channelRead0(ChannelHandlerContext channelHandlerContext, Packet packet) throws Exception { 44 | logger.info("收到客户端请求:{}", JSON.toJSONString(packet)); 45 | SimpleChannelInboundHandler handler = handlerMap.get(packet.getCommand()); 46 | if(handler != null ){ 47 | handler.channelRead(channelHandlerContext,packet); 48 | }else{ 49 | logger.info("未找到响应指令,请确认指令是否正确!"); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /easy-im/easy-im-server/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /easy-im/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.sunnick 8 | easy-im 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | easy-im-commom 13 | easy-im-client 14 | easy-im-server 15 | 16 | 17 | 18 | 19 | io.netty 20 | netty-all 21 | 4.1.32.Final 22 | 23 | 24 | 25 | com.alibaba 26 | fastjson 27 | 1.2.53 28 | 29 | 30 | 31 | org.projectlombok 32 | lombok 33 | 1.18.4 34 | provided 35 | 36 | 37 | 38 | junit 39 | junit 40 | 4.12 41 | test 42 | 43 | 44 | 45 | 46 | org.apache.logging.log4j 47 | log4j-core 48 | 2.6.2 49 | 50 | 51 | org.apache.logging.log4j 52 | log4j-api 53 | 2.6.2 54 | 55 | 56 | org.apache.logging.log4j 57 | log4j-slf4j-impl 58 | 2.6.2 59 | 60 | 61 | org.slf4j 62 | log4j-over-slf4j 63 | 1.7.12 64 | 65 | 66 | 67 | com.lmax 68 | disruptor 69 | 3.3.6 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-compiler-plugin 80 | 3.5 81 | 82 | 1.8 83 | 1.8 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | --------------------------------------------------------------------------------