├── 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 | 
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 | 
73 | - 微信公众号
74 |
75 | 
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