├── .gitignore ├── README.md ├── cn.ctodb.push.core ├── src │ └── main │ │ └── java │ │ └── cn │ │ └── ctodb │ │ └── push │ │ ├── core │ │ ├── FilterResult.java │ │ ├── Filter.java │ │ ├── Connection.java │ │ └── PacketReceiver.java │ │ ├── dto │ │ ├── LoginReq.java │ │ ├── User.java │ │ ├── HandshakeReq.java │ │ ├── HandshakeResp.java │ │ ├── Error.java │ │ ├── TextMessage.java │ │ ├── Command.java │ │ ├── Message.java │ │ ├── ErrorCode.java │ │ └── Packet.java │ │ ├── handler │ │ ├── PacketHandler.java │ │ ├── HeartBeatHandler.java │ │ ├── TextMessageHandler.java │ │ ├── ErrorHandler.java │ │ └── AbstractHandler.java │ │ └── utils │ │ ├── MsgPackEncode.java │ │ └── MsgPackDecode.java └── pom.xml ├── cn.ctodb.push.sdk ├── src │ ├── main │ │ ├── java │ │ │ └── cn │ │ │ │ └── ctodb │ │ │ │ └── push │ │ │ │ └── client │ │ │ │ ├── ClientInfo.java │ │ │ │ ├── Util.java │ │ │ │ ├── ClientProperties.java │ │ │ │ ├── handler │ │ │ │ └── HandshakeHandler.java │ │ │ │ ├── conf │ │ │ │ └── ClientConfiguration.java │ │ │ │ ├── HeartBeatTasker.java │ │ │ │ ├── ClientHandler.java │ │ │ │ └── Client.java │ │ └── resources │ │ │ └── logback.xml │ └── test │ │ └── java │ │ └── ClientTest.java └── pom.xml ├── cn.ctodb.push.reg ├── src │ └── main │ │ ├── resources │ │ ├── application.yml │ │ ├── logback.xml │ │ └── banner.txt │ │ └── java │ │ └── cn │ │ └── ctodb │ │ └── push │ │ └── reg │ │ ├── RegApp.java │ │ ├── entity │ │ └── ServerNode.java │ │ ├── util │ │ └── IpUtil.java │ │ ├── conf │ │ └── CacheConfiguration.java │ │ └── controller │ │ └── RegController.java └── pom.xml ├── cn.ctodb.push.server ├── src │ ├── main │ │ ├── resources │ │ │ ├── logback.xml │ │ │ ├── banner.txt │ │ │ └── application.yml │ │ └── java │ │ │ └── cn │ │ │ └── ctodb │ │ │ └── push │ │ │ └── server │ │ │ ├── util │ │ │ └── SessionUtil.java │ │ │ ├── conf │ │ │ ├── KafkaConfiguration.java │ │ │ ├── ApplicationProperties.java │ │ │ ├── ApplicationReadyEventListener.java │ │ │ ├── CacheConfiguration.java │ │ │ └── ServerConfiguration.java │ │ │ ├── PushServerApp.java │ │ │ ├── kafka │ │ │ └── KafkaConsumer.java │ │ │ ├── handler │ │ │ ├── LoginHandler.java │ │ │ ├── HeartBeatHandler.java │ │ │ ├── TextMessageHandler.java │ │ │ └── HandshakeHandler.java │ │ │ ├── session │ │ │ ├── Session.java │ │ │ ├── SessionManager.java │ │ │ └── PushSession.java │ │ │ ├── info │ │ │ └── ClientType.java │ │ │ ├── UserManager.java │ │ │ ├── ServerHandler.java │ │ │ ├── filter │ │ │ └── AuthFilter.java │ │ │ └── service │ │ │ └── MgsServer.java │ └── test │ │ └── java │ │ └── cn │ │ └── ctodb │ │ └── push │ │ └── server │ │ └── test │ │ ├── TestKafka.java │ │ ├── TestRedis.java │ │ └── TestUser.java └── pom.xml ├── pom.xml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .settings/ 2 | .project 3 | target/ 4 | *.classpath 5 | .idea/ 6 | *.iml 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CTODB - PUSH 2 | ## 模块 3 | - push_core 核心 4 | - push_server 服务端 5 | - push_sdk 客户端SDK java 6 | - push_reg 注册服务 -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/core/FilterResult.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.core; 2 | 3 | /** 4 | * All rights Reserved, Designed By www.ctodb.cn 5 | * 6 | * @version V1.0 7 | * @author: lichaohn@163.com 8 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 9 | */ 10 | public enum FilterResult { 11 | NEXT, END 12 | } -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/dto/LoginReq.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.dto; 2 | 3 | /** 4 | * All rights Reserved, Designed By www.ctodb.cn 5 | * 6 | * @version V1.0 7 | * @author: lichaohn@163.com 8 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 9 | */ 10 | @org.msgpack.annotation.Message 11 | public class LoginReq { 12 | public String token; 13 | } 14 | -------------------------------------------------------------------------------- /cn.ctodb.push.sdk/src/main/java/cn/ctodb/push/client/ClientInfo.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.client; 2 | 3 | public class ClientInfo { 4 | public static String sessionId; 5 | 6 | public static String getSessionId() { 7 | return sessionId; 8 | } 9 | 10 | public static void setSessionId(String sessionId) { 11 | ClientInfo.sessionId = sessionId; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /cn.ctodb.push.reg/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | redis: 3 | host: 10.16.51.2 4 | port: 6379 5 | password: 123456 6 | lettuce: 7 | pool: 8 | max-active: 8 # 连接池最大连接数(使用负值表示没有限制) 9 | max-idle: 8 # 连接池中的最大空闲连接 10 | min-idle: 0 # 连接池中的最小空闲连接 11 | max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制) -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/core/Filter.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.core; 2 | 3 | import cn.ctodb.push.dto.Packet; 4 | 5 | /** 6 | * All rights Reserved, Designed By www.ctodb.cn 7 | * 8 | * @version V1.0 9 | * @author: lichaohn@163.com 10 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 11 | */ 12 | public interface Filter { 13 | 14 | FilterResult exec(Packet packet, Connection connection); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/dto/User.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.dto; 2 | 3 | import java.util.Set; 4 | 5 | /** 6 | * All rights Reserved, Designed By www.ctodb.cn 7 | * 8 | * @version V1.0 9 | * @author: lichaohn@163.com 10 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 11 | */ 12 | @org.msgpack.annotation.Message 13 | public class User { 14 | public String id; 15 | public Set servers; 16 | } 17 | -------------------------------------------------------------------------------- /cn.ctodb.push.sdk/src/main/java/cn/ctodb/push/client/Util.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.client; 2 | 3 | import org.msgpack.MessagePack; 4 | 5 | import java.io.IOException; 6 | 7 | /** 8 | * Created by cc on 2017/8/21. 9 | */ 10 | public class Util { 11 | 12 | private static MessagePack messagePack = new MessagePack(); 13 | 14 | public static byte[] msg2bytes(Object o) throws IOException { 15 | return messagePack.write(o); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/dto/HandshakeReq.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.dto; 2 | 3 | /** 4 | * All rights Reserved, Designed By www.ctodb.cn 5 | * 6 | * @version V1.0 7 | * @author: lichaohn@163.com 8 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 9 | */ 10 | @org.msgpack.annotation.Message 11 | public class HandshakeReq { 12 | 13 | public String deviceId; 14 | public String osName; 15 | public String osVersion; 16 | public String clientVersion; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/dto/HandshakeResp.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.dto; 2 | 3 | /** 4 | * All rights Reserved, Designed By www.ctodb.cn 5 | * 6 | * @version V1.0 7 | * @author: lichaohn@163.com 8 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 9 | */ 10 | @org.msgpack.annotation.Message 11 | public class HandshakeResp { 12 | 13 | public byte[] serverKey; 14 | public int heartbeat; 15 | public String sessionId; 16 | public long expireTime; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /cn.ctodb.push.sdk/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | client : %date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /cn.ctodb.push.sdk/src/main/java/cn/ctodb/push/client/ClientProperties.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.client; 2 | 3 | public class ClientProperties { 4 | private String serverHost; 5 | private int serverPort; 6 | 7 | public String getServerHost() { 8 | return serverHost; 9 | } 10 | 11 | public void setServerHost(String serverHost) { 12 | this.serverHost = serverHost; 13 | } 14 | 15 | public int getServerPort() { 16 | return serverPort; 17 | } 18 | 19 | public void setServerPort(int serverPort) { 20 | this.serverPort = serverPort; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /cn.ctodb.push.reg/src/main/java/cn/ctodb/push/reg/RegApp.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.reg; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableAsync; 6 | import org.springframework.scheduling.annotation.EnableScheduling; 7 | 8 | @SpringBootApplication 9 | @EnableAsync 10 | @EnableScheduling 11 | public class RegApp { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication app = new SpringApplication(RegApp.class); 15 | app.run(args); 16 | } 17 | } -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | server : %date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/dto/Error.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.dto; 2 | 3 | /** 4 | * All rights Reserved, Designed By www.ctodb.cn 5 | * 6 | * @version V1.0 7 | * @author: lichaohn@163.com 8 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 9 | */ 10 | @org.msgpack.annotation.Message 11 | public class Error { 12 | 13 | public byte code; 14 | public String msg; 15 | 16 | public void setCode(ErrorCode errorCode, String msg) { 17 | code = errorCode.errorCode; 18 | this.msg = msg; 19 | } 20 | 21 | public ErrorCode getErrorCode() { 22 | return ErrorCode.toEnum(code); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/util/SessionUtil.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.util; 2 | 3 | import org.apache.commons.lang3.RandomStringUtils; 4 | 5 | import java.security.MessageDigest; 6 | import java.security.NoSuchAlgorithmException; 7 | import java.security.SecureRandom; 8 | import java.util.Random; 9 | import java.util.UUID; 10 | 11 | /** 12 | * Created by cc on 2017/8/11. 13 | */ 14 | public class SessionUtil { 15 | 16 | private static final int SESSION_ID_BYTES = 16; 17 | 18 | public static enum SessionIdType {SHORT, LANG} 19 | 20 | public static String get() { 21 | return RandomStringUtils.randomAlphanumeric(16); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /cn.ctodb.push.reg/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | server : %date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | push-core 5 | 6 | cn.ctodb.push 7 | push-parent 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | 12 | cc 13 | lichaohn@163.com 14 | https://www.ctodb.cn 15 | https://github.com/ctodb/push 16 | 17 | 18 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/conf/KafkaConfiguration.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.conf; 2 | 3 | import cn.ctodb.push.server.kafka.KafkaConsumer; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.kafka.annotation.EnableKafka; 7 | 8 | /** 9 | * All rights Reserved, Designed By www.ctodb.cn 10 | * 11 | * @version V1.0 12 | * @author: lichaohn@163.com 13 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 14 | */ 15 | // @Configuration 16 | @EnableKafka 17 | public class KafkaConfiguration { 18 | 19 | @Bean 20 | public KafkaConsumer kafkaProduct() { 21 | return new KafkaConsumer(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/core/Connection.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.core; 2 | 3 | import cn.ctodb.push.dto.Packet; 4 | import io.netty.channel.ChannelHandlerContext; 5 | 6 | /** 7 | * All rights Reserved, Designed By www.ctodb.cn 8 | * 9 | * @version V1.0 10 | * @author: lichaohn@163.com 11 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 12 | */ 13 | public class Connection { 14 | 15 | private ChannelHandlerContext chc; 16 | 17 | public void send(Packet packet) { 18 | chc.channel().writeAndFlush(packet); 19 | } 20 | 21 | public ChannelHandlerContext getChc() { 22 | return chc; 23 | } 24 | 25 | public void setChc(ChannelHandlerContext chc) { 26 | this.chc = chc; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cn.ctodb.push.reg/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | ${AnsiColor.GREEN} ####### ######## ######## ${AnsiColor.RED} ######## ####### ####### ### ## 3 | ${AnsiColor.GREEN} ## ## ## ## ${AnsiColor.RED} ## ## ## ## ## #### ## 4 | ${AnsiColor.GREEN} ## ## ## ## ${AnsiColor.RED} ## ## ####### ## ## ## ## 5 | ${AnsiColor.GREEN} ## ## ## ## ${AnsiColor.RED} ## ## ## ## ## ## #### 6 | ${AnsiColor.GREEN} ####### ## ######## ${AnsiColor.RED} ######## ####### ## ####### ## ### 7 | 8 | ${AnsiColor.BRIGHT_BLUE}:: Ctodb @ :: Running Spring Boot ${spring-boot.version} :: 9 | :: http://www.ctodb.cn ::${AnsiColor.DEFAULT} -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | ${AnsiColor.GREEN} ####### ######## ######## ${AnsiColor.RED} ######## ####### ####### ### ## 3 | ${AnsiColor.GREEN} ## ## ## ## ${AnsiColor.RED} ## ## ## ## ## #### ## 4 | ${AnsiColor.GREEN} ## ## ## ## ${AnsiColor.RED} ## ## ####### ## ## ## ## 5 | ${AnsiColor.GREEN} ## ## ## ## ${AnsiColor.RED} ## ## ## ## ## ## #### 6 | ${AnsiColor.GREEN} ####### ## ######## ${AnsiColor.RED} ######## ####### ## ####### ## ### 7 | 8 | ${AnsiColor.BRIGHT_BLUE}:: Ctodb @ :: Running Spring Boot ${spring-boot.version} :: 9 | :: http://www.ctodb.cn ::${AnsiColor.DEFAULT} -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/PushServerApp.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.WebApplicationType; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.scheduling.annotation.EnableAsync; 7 | import org.springframework.scheduling.annotation.EnableScheduling; 8 | 9 | @SpringBootApplication 10 | @EnableAsync 11 | @EnableScheduling 12 | public class PushServerApp { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication app = new SpringApplication(PushServerApp.class); 16 | app.setWebApplicationType(WebApplicationType.NONE); 17 | app.run(args); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/handler/PacketHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.handler; 2 | 3 | import cn.ctodb.push.core.Connection; 4 | import cn.ctodb.push.dto.Command; 5 | import cn.ctodb.push.dto.Packet; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * All rights Reserved, Designed By www.ctodb.cn 12 | * 13 | * @version V1.0 14 | * @author: lichaohn@163.com 15 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 16 | */ 17 | public abstract class PacketHandler { 18 | 19 | protected final Logger logger = LoggerFactory.getLogger(getClass()); 20 | 21 | public abstract Command cmd(); 22 | 23 | public abstract void handle(Packet packet, Connection connection); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/dto/TextMessage.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.dto; 2 | 3 | /** 4 | * All rights Reserved, Designed By www.ctodb.cn 5 | * 6 | * @version V1.0 7 | * @author: lichaohn@163.com 8 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 9 | */ 10 | @org.msgpack.annotation.Message 11 | public class TextMessage extends Message { 12 | 13 | private String content; 14 | 15 | @Override 16 | public String toString() { 17 | return null; 18 | } 19 | 20 | public String getContent() { 21 | return content; 22 | } 23 | 24 | public void setContent(String content) { 25 | this.content = content; 26 | } 27 | 28 | @Override 29 | public Command getCmd() { 30 | return Command.TEXT_MESSAGE; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/utils/MsgPackEncode.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.utils; 2 | 3 | import org.msgpack.MessagePack; 4 | 5 | import cn.ctodb.push.dto.Packet; 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.ChannelHandler.Sharable; 9 | import io.netty.handler.codec.MessageToByteEncoder; 10 | 11 | /** 12 | * All rights Reserved, Designed By www.ctodb.cn 13 | * 14 | * @version V1.0 15 | * @author: lichaohn@163.com 16 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 17 | */ 18 | @Sharable 19 | public class MsgPackEncode extends MessageToByteEncoder { 20 | 21 | @Override 22 | protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf out) throws Exception { 23 | out.writeBytes(new MessagePack().write(packet)); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/kafka/KafkaConsumer.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.kafka; 2 | 3 | import org.apache.kafka.clients.consumer.ConsumerRecord; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.kafka.annotation.KafkaListener; 7 | 8 | import java.util.Optional; 9 | 10 | public class KafkaConsumer { 11 | 12 | private Logger logger = LoggerFactory.getLogger(KafkaConsumer.class); 13 | 14 | @KafkaListener(topics = {"test-topic"}) 15 | public void listen(ConsumerRecord record) { 16 | logger.debug("listen1 "); 17 | Optional kafkaMessage = Optional.ofNullable(record.value()); 18 | if (kafkaMessage.isPresent()) { 19 | Object message = kafkaMessage.get(); 20 | logger.debug("listen1 : {}", message); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/test/java/cn/ctodb/push/server/test/TestKafka.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.test; 2 | 3 | import cn.ctodb.push.server.PushServerApp; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.kafka.core.KafkaTemplate; 9 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 10 | 11 | /** 12 | * Created by cc on 2017/8/9. 13 | */ 14 | @RunWith(SpringJUnit4ClassRunner.class) 15 | @SpringBootTest(classes = PushServerApp.class) 16 | public class TestKafka { 17 | 18 | @Autowired 19 | public KafkaTemplate kafkaTemplate; 20 | 21 | @Test 22 | public void test() { 23 | kafkaTemplate.send("test-topic", "hello"); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/handler/HeartBeatHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.handler; 2 | 3 | import cn.ctodb.push.core.Connection; 4 | import cn.ctodb.push.dto.Command; 5 | import cn.ctodb.push.dto.Packet; 6 | import io.netty.channel.ChannelHandlerContext; 7 | 8 | /** 9 | * All rights Reserved, Designed By www.ctodb.cn 10 | * 11 | * @version V1.0 12 | * @author: lichaohn@163.com 13 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 14 | */ 15 | public final class HeartBeatHandler extends PacketHandler { 16 | 17 | @Override 18 | public Command cmd() { 19 | return Command.HEARTBEAT; 20 | } 21 | 22 | @Override 23 | public void handle(Packet packet, Connection connection) { 24 | logger.debug("heart : {}", connection.getChc().channel()); 25 | connection.getChc().channel().writeAndFlush(packet); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/dto/Command.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.dto; 2 | 3 | /** 4 | * All rights Reserved, Designed By www.ctodb.cn 5 | * 6 | * @version V1.0 7 | * @author: lichaohn@163.com 8 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 9 | */ 10 | public enum Command { 11 | HEARTBEAT(1), 12 | HANDSHAKE_REQ(10), 13 | HANDSHAKE_RESP(11), 14 | ERROR(2), 15 | LOGIN(3), 16 | LOGOUT(4), 17 | BIND(5), 18 | UNBIND(6), 19 | PUSH(7), 20 | TEXT_MESSAGE(100), 21 | UNKNOWN(-1); 22 | 23 | Command(int cmd) { 24 | this.cmd = (byte) cmd; 25 | } 26 | 27 | public final byte cmd; 28 | 29 | public static Command toCMD(byte b) { 30 | for (Command command : values()) { 31 | if (command.cmd == b) { 32 | return command; 33 | } 34 | } 35 | return UNKNOWN; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/handler/LoginHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.handler; 2 | 3 | import cn.ctodb.push.core.Connection; 4 | import cn.ctodb.push.dto.LoginReq; 5 | import cn.ctodb.push.dto.Command; 6 | import cn.ctodb.push.handler.AbstractHandler; 7 | 8 | /** 9 | * All rights Reserved, Designed By www.ctodb.cn 10 | * 11 | * @version V1.0 12 | * @author: lichaohn@163.com 13 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 14 | */ 15 | public final class LoginHandler extends AbstractHandler { 16 | 17 | @Override 18 | public Class getType() { 19 | return LoginReq.class; 20 | } 21 | 22 | @Override 23 | public void handle(LoginReq loginReq, Connection connection) { 24 | logger.debug("login : "); 25 | } 26 | 27 | @Override 28 | public Command cmd() { 29 | return Command.LOGIN; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cn.ctodb.push.sdk/src/main/java/cn/ctodb/push/client/handler/HandshakeHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.client.handler; 2 | 3 | import cn.ctodb.push.client.ClientInfo; 4 | import cn.ctodb.push.core.Connection; 5 | import cn.ctodb.push.dto.Command; 6 | import cn.ctodb.push.dto.HandshakeResp; 7 | import cn.ctodb.push.handler.AbstractHandler; 8 | 9 | /** 10 | * Created by cc on 2017/8/18. 11 | */ 12 | public class HandshakeHandler extends AbstractHandler { 13 | 14 | @Override 15 | public Command cmd() { 16 | return Command.HANDSHAKE_RESP; 17 | } 18 | 19 | @Override 20 | public Class getType() { 21 | return HandshakeResp.class; 22 | } 23 | 24 | @Override 25 | public void handle(HandshakeResp message, Connection connection) { 26 | logger.debug("握手成功:{}", message.sessionId); 27 | ClientInfo.setSessionId(message.sessionId); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/handler/TextMessageHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.handler; 2 | 3 | import cn.ctodb.push.core.Connection; 4 | import cn.ctodb.push.dto.Command; 5 | import cn.ctodb.push.dto.TextMessage; 6 | import io.netty.channel.ChannelHandlerContext; 7 | 8 | /** 9 | * All rights Reserved, Designed By www.ctodb.cn 10 | * 11 | * @version V1.0 12 | * @author: lichaohn@163.com 13 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 14 | */ 15 | public final class TextMessageHandler extends AbstractHandler { 16 | 17 | @Override 18 | public Class getType() { 19 | return TextMessage.class; 20 | } 21 | 22 | @Override 23 | public void handle(TextMessage message, Connection connection) { 24 | logger.debug(message.getContent()); 25 | } 26 | 27 | @Override 28 | public Command cmd() { 29 | return Command.TEXT_MESSAGE; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/handler/HeartBeatHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.handler; 2 | 3 | import cn.ctodb.push.core.Connection; 4 | import cn.ctodb.push.dto.Command; 5 | import cn.ctodb.push.dto.Packet; 6 | import cn.ctodb.push.handler.PacketHandler; 7 | import io.netty.channel.ChannelHandlerContext; 8 | 9 | /** 10 | * All rights Reserved, Designed By www.ctodb.cn 11 | * 12 | * @version V1.0 13 | * @author: lichaohn@163.com 14 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 15 | */ 16 | public final class HeartBeatHandler extends PacketHandler { 17 | 18 | @Override 19 | public Command cmd() { 20 | return Command.HEARTBEAT; 21 | } 22 | 23 | @Override 24 | public void handle(Packet packet, Connection connection) { 25 | logger.debug("heart : {}", connection.getChc().channel()); 26 | connection.getChc().channel().writeAndFlush(packet); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/handler/TextMessageHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.handler; 2 | 3 | import cn.ctodb.push.core.Connection; 4 | import cn.ctodb.push.dto.Command; 5 | import cn.ctodb.push.dto.TextMessage; 6 | import cn.ctodb.push.handler.AbstractHandler; 7 | 8 | /** 9 | * All rights Reserved, Designed By www.ctodb.cn 10 | * 11 | * @version V1.0 12 | * @author: lichaohn@163.com 13 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 14 | */ 15 | public final class TextMessageHandler extends AbstractHandler { 16 | 17 | @Override 18 | public Class getType() { 19 | return TextMessage.class; 20 | } 21 | 22 | @Override 23 | public void handle(TextMessage message, Connection connection) { 24 | logger.debug(message.getContent()); 25 | } 26 | 27 | @Override 28 | public Command cmd() { 29 | return Command.TEXT_MESSAGE; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/handler/ErrorHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.handler; 2 | 3 | import cn.ctodb.push.core.Connection; 4 | import cn.ctodb.push.dto.Command; 5 | import cn.ctodb.push.dto.Error; 6 | import cn.ctodb.push.dto.HandshakeResp; 7 | import cn.ctodb.push.dto.Packet; 8 | 9 | import java.util.Map; 10 | 11 | /** 12 | * All rights Reserved, Designed By www.ctodb.cn 13 | * 14 | * @version V1.0 15 | * @author: lichaohn@163.com 16 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 17 | */ 18 | public final class ErrorHandler extends AbstractHandler { 19 | 20 | @Override 21 | public Command cmd() { 22 | return Command.ERROR; 23 | } 24 | 25 | @Override 26 | public Class getType() { 27 | return Error.class; 28 | } 29 | 30 | @Override 31 | public void handle(Error message, Connection connection) { 32 | logger.debug("error : {}", message.getErrorCode()); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /cn.ctodb.push.sdk/src/main/java/cn/ctodb/push/client/conf/ClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.client.conf; 2 | 3 | import cn.ctodb.push.client.ClientHandler; 4 | import cn.ctodb.push.client.handler.HandshakeHandler; 5 | import cn.ctodb.push.core.PacketReceiver; 6 | import cn.ctodb.push.handler.ErrorHandler; 7 | 8 | /** 9 | * Created by cc on 2017/8/21. 10 | */ 11 | public class ClientConfiguration { 12 | 13 | private static PacketReceiver packetReceiver; 14 | 15 | static { 16 | init(); 17 | } 18 | 19 | public static void init() { 20 | packetReceiver = new PacketReceiver(); 21 | packetReceiver.addHandler(new HandshakeHandler()); 22 | packetReceiver.addHandler(new ErrorHandler()); 23 | } 24 | 25 | public static PacketReceiver packetReceiver() { 26 | return packetReceiver; 27 | } 28 | 29 | public static ClientHandler clientHandler() { 30 | return new ClientHandler(packetReceiver()); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/session/Session.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.session; 2 | 3 | import java.net.UnknownHostException; 4 | import java.security.cert.Certificate; 5 | import java.util.Date; 6 | 7 | public interface Session { 8 | 9 | static final int MAJOR_VERSION = 1; 10 | static final int MINOR_VERSION = 0; 11 | 12 | static final int STATUS_CLOSED = -1; 13 | static final int STATUS_CONNECTED = 1; 14 | static final int STATUS_AUTHENTICATED = 3; 15 | 16 | String getId(); 17 | 18 | int getStatus(); 19 | 20 | Date getCreationDate(); 21 | 22 | Date getLastActiveDate(); 23 | 24 | long getNumClientPackets(); 25 | 26 | long getNumServerPackets(); 27 | 28 | void close(); 29 | 30 | boolean isClosed(); 31 | 32 | boolean isSecure(); 33 | 34 | Certificate[] getPeerCertificates(); 35 | 36 | String getHostAddress() throws UnknownHostException; 37 | 38 | String getHostName() throws UnknownHostException; 39 | 40 | boolean validate(); 41 | 42 | } -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/utils/MsgPackDecode.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.utils; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelHandler.Sharable; 6 | import io.netty.handler.codec.MessageToMessageDecoder; 7 | 8 | import java.util.List; 9 | 10 | import org.msgpack.MessagePack; 11 | 12 | import cn.ctodb.push.dto.Packet; 13 | 14 | /** 15 | * All rights Reserved, Designed By www.ctodb.cn 16 | * 17 | * @version V1.0 18 | * @author: lichaohn@163.com 19 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 20 | */ 21 | @Sharable 22 | public class MsgPackDecode extends MessageToMessageDecoder { 23 | @Override 24 | protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { 25 | final int length = msg.readableBytes(); 26 | final byte[] array = new byte[length]; 27 | msg.getBytes(msg.readerIndex(), array, 0, length); 28 | out.add(new MessagePack().read(array, Packet.class)); 29 | } 30 | } -------------------------------------------------------------------------------- /cn.ctodb.push.reg/src/main/java/cn/ctodb/push/reg/entity/ServerNode.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.reg.entity; 2 | 3 | import java.io.Serializable; 4 | 5 | public class ServerNode implements Serializable { 6 | 7 | private String id; 8 | private String url; 9 | private long lastCheckTime; 10 | private long connectCount; 11 | 12 | public String getId() { 13 | return id; 14 | } 15 | 16 | public void setId(String id) { 17 | this.id = id; 18 | } 19 | 20 | public String getUrl() { 21 | return url; 22 | } 23 | 24 | public void setUrl(String url) { 25 | this.url = url; 26 | } 27 | 28 | public long getLastCheckTime() { 29 | return lastCheckTime; 30 | } 31 | 32 | public void setLastCheckTime(long lastCheckTime) { 33 | this.lastCheckTime = lastCheckTime; 34 | } 35 | 36 | public long getConnectCount() { 37 | return connectCount; 38 | } 39 | 40 | public void setConnectCount(long connectCount) { 41 | this.connectCount = connectCount; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/conf/ApplicationProperties.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.conf; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | /** 6 | * All rights Reserved, Designed By www.ctodb.cn 7 | * 8 | * @version V1.0 9 | * @author: lichaohn@163.com 10 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 11 | */ 12 | @ConfigurationProperties(prefix = "application") // 接收application.yml中的myProps下面的属性 13 | public class ApplicationProperties { 14 | 15 | private final Server server = new Server(); 16 | 17 | public Server getServer() { 18 | return server; 19 | } 20 | 21 | public static class Server { 22 | private int port = -1; 23 | private String center = ""; 24 | 25 | public int getPort() { 26 | return port; 27 | } 28 | 29 | public void setPort(int port) { 30 | this.port = port; 31 | } 32 | 33 | public String getCenter() { 34 | return center; 35 | } 36 | 37 | public void setCenter(String center) { 38 | this.center = center; 39 | } 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/test/java/cn/ctodb/push/server/test/TestRedis.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.test; 2 | 3 | import cn.ctodb.push.server.PushServerApp; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.data.redis.core.RedisTemplate; 11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 | 13 | @RunWith(SpringJUnit4ClassRunner.class) 14 | @SpringBootTest(classes = PushServerApp.class) 15 | public class TestRedis { 16 | private Logger logger = LoggerFactory.getLogger(TestRedis.class); 17 | 18 | @Autowired 19 | private RedisTemplate redisTemplate; 20 | 21 | @Test 22 | public void test() throws Exception { 23 | redisTemplate.boundSetOps("userOnline").add("user01"); 24 | redisTemplate.boundSetOps("userOnline").add("user02"); 25 | logger.debug(redisTemplate.boundSetOps("userOnline").size() + ""); 26 | redisTemplate.boundSetOps("userOnline").remove("user01"); 27 | } 28 | } -------------------------------------------------------------------------------- /cn.ctodb.push.sdk/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | push-sdk 5 | 6 | cn.ctodb.push 7 | push-parent 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | 12 | cc 13 | lichaohn@163.com 14 | https://www.ctodb.cn 15 | https://github.com/ctodb/push 16 | 17 | 18 | 19 | 20 | cn.ctodb.push 21 | push-core 22 | 23 | 24 | org.json 25 | json 26 | 20171018 27 | 28 | 29 | -------------------------------------------------------------------------------- /cn.ctodb.push.sdk/src/main/java/cn/ctodb/push/client/HeartBeatTasker.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.client; 2 | 3 | import cn.ctodb.push.dto.Command; 4 | import cn.ctodb.push.dto.Packet; 5 | 6 | /** 7 | * Created by cc on 2017/7/6. 8 | */ 9 | public class HeartBeatTasker implements Runnable { 10 | 11 | private Client client; 12 | private int waitTime = 10 * 1000; 13 | 14 | public HeartBeatTasker(Client client) { 15 | this.client = client; 16 | } 17 | 18 | @Override 19 | public void run() { 20 | for (; ; ) { 21 | if (client.getStatus().equals(Client.Status.STOP)) break; 22 | if (client.getStatus().equals(Client.Status.START)) { 23 | try { 24 | Packet packet = new Packet(Command.HEARTBEAT.cmd); 25 | client.send(packet); 26 | } catch (Throwable e) { 27 | 28 | } finally { 29 | try { 30 | Thread.sleep(waitTime); 31 | } catch (InterruptedException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/info/ClientType.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.info; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * All rights Reserved, Designed By www.ctodb.cn 7 | * 8 | * @version V1.0 9 | * @author: lichaohn@163.com 10 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 11 | */ 12 | public enum ClientType { 13 | MOBILE(1, "android", "ios"), 14 | PC(2, "windows", "mac", "linux"), 15 | WEB(3, "web", "h5"), 16 | UNKNOWN(-1); 17 | 18 | public final int type; 19 | public final String[] os; 20 | 21 | ClientType(int type, String... os) { 22 | this.type = type; 23 | this.os = os; 24 | } 25 | 26 | public boolean contains(String osName) { 27 | return Arrays.stream(os).anyMatch(osName::contains); 28 | } 29 | 30 | public static ClientType find(String osName) { 31 | for (ClientType type : values()) { 32 | if (type.contains(osName.toLowerCase())) return type; 33 | } 34 | return UNKNOWN; 35 | } 36 | 37 | public static boolean isSameClient(String osNameA, String osNameB) { 38 | if (osNameA.equals(osNameB)) return true; 39 | return find(osNameA).contains(osNameB); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/handler/AbstractHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.handler; 2 | 3 | import cn.ctodb.push.core.Connection; 4 | import cn.ctodb.push.dto.Message; 5 | import cn.ctodb.push.dto.Packet; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import org.msgpack.MessagePack; 8 | 9 | import java.io.IOException; 10 | import java.lang.reflect.ParameterizedType; 11 | 12 | /** 13 | * All rights Reserved, Designed By www.ctodb.cn 14 | * 15 | * @version V1.0 16 | * @author: lichaohn@163.com 17 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 18 | */ 19 | public abstract class AbstractHandler extends PacketHandler { 20 | 21 | public T decode(Packet packet) { 22 | try { 23 | return new MessagePack().read(packet.getBody(), getType()); 24 | } catch (IOException e) { 25 | e.printStackTrace(); 26 | } 27 | return null; 28 | } 29 | 30 | public abstract Class getType(); 31 | 32 | public abstract void handle(T message, Connection connection); 33 | 34 | public void handle(Packet packet, Connection connection) { 35 | T t = decode(packet); 36 | if (t != null) { 37 | handle(t, connection); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/dto/Message.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.dto; 2 | 3 | /** 4 | * All rights Reserved, Designed By www.ctodb.cn 5 | * 6 | * @version V1.0 7 | * @author: lichaohn@163.com 8 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 9 | */ 10 | public abstract class Message { 11 | private Long id; 12 | private String toUserName; 13 | private String fromUserName; 14 | private Long createTime; 15 | 16 | public Long getId() { 17 | return id; 18 | } 19 | 20 | public void setId(Long id) { 21 | this.id = id; 22 | } 23 | 24 | public String getToUserName() { 25 | return toUserName; 26 | } 27 | 28 | public void setToUserName(String toUserName) { 29 | this.toUserName = toUserName; 30 | } 31 | 32 | public String getFromUserName() { 33 | return fromUserName; 34 | } 35 | 36 | public void setFromUserName(String fromUserName) { 37 | this.fromUserName = fromUserName; 38 | } 39 | 40 | public Long getCreateTime() { 41 | return createTime; 42 | } 43 | 44 | public void setCreateTime(Long createTime) { 45 | this.createTime = createTime; 46 | } 47 | 48 | public abstract Command getCmd(); 49 | } 50 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/session/SessionManager.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.session; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.data.redis.core.BoundHashOperations; 8 | import org.springframework.data.redis.core.RedisTemplate; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class SessionManager { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(SessionManager.class); 15 | 16 | @Autowired 17 | private RedisTemplate redisTemplate; 18 | 19 | private final String cacheKey = "__SESSIONS__"; 20 | 21 | public void on(Session session) { 22 | logger.info("new session : {}", session.getId()); 23 | getCache().put(session.getId(), JSON.toJSONString(session)); 24 | } 25 | 26 | public void off(Session session) { 27 | getCache().delete(session.getId()); 28 | } 29 | 30 | public Session get(String sessionId) { 31 | return JSON.parseObject(getCache().get(sessionId), PushSession.class); 32 | } 33 | 34 | public BoundHashOperations getCache() { 35 | return redisTemplate.boundHashOps(cacheKey); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/test/java/cn/ctodb/push/server/test/TestUser.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.test; 2 | 3 | import cn.ctodb.push.server.UserManager; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | 12 | import cn.ctodb.push.server.PushServerApp; 13 | 14 | @RunWith(SpringJUnit4ClassRunner.class) 15 | @SpringBootTest(classes = PushServerApp.class) 16 | public class TestUser { 17 | private Logger logger = LoggerFactory.getLogger(TestRedis.class); 18 | 19 | @Autowired 20 | private UserManager userManager; 21 | 22 | @Test 23 | public void before() throws Exception { 24 | userManager.clearUserOnlineData(); 25 | logger.debug("清理缓存"); 26 | } 27 | 28 | @Test 29 | public void test() throws Exception { 30 | logger.debug("模拟添加用户"); 31 | // userManager.on("user001"); 32 | // userManager.on("user002"); 33 | // userManager.on("user003"); 34 | // userManager.on("user004"); 35 | // userManager.on("user005"); 36 | // userManager.on("user001"); 37 | // userManager.on("user001"); 38 | } 39 | 40 | @Test 41 | public void count() { 42 | logger.debug("在线人数 : {}", userManager.getOnlineUserNum()); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | hazelcast: null 3 | kafka: 4 | producer: 5 | bootstrap-servers: 6 | - server.ctodb.cn:9092 7 | retries: 0 8 | batch-size: 16384 9 | buffer-memory: 33554432 10 | key-serializer: org.apache.kafka.common.serialization.StringSerializer 11 | value-serializer: org.apache.kafka.common.serialization.StringSerializer 12 | consumer: 13 | bootstrap-servers: 14 | - server.ctodb.cn:9092 15 | group-id: foo 16 | auto-offset-reset: earliest 17 | enable-auto-commit: true 18 | auto-commit-interval: 100 19 | key-deserializer: org.apache.kafka.common.serialization.StringDeserializer 20 | value-deserializer: org.apache.kafka.common.serialization.StringDeserializer 21 | cache: 22 | type: redis 23 | redis: 24 | host: 10.16.51.2 25 | port: 6379 26 | password: 123456 27 | # cluster: 28 | # max-redirects: 3 29 | # nodes: 30 | # - 10.16.51.2:6379 31 | lettuce: 32 | pool: 33 | max-active: 8 # 连接池最大连接数(使用负值表示没有限制) 34 | max-idle: 8 # 连接池中的最大空闲连接 35 | min-idle: 0 # 连接池中的最小空闲连接 36 | max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制) 37 | 38 | application: 39 | server: 40 | port: -1 # 默认端口 9901 41 | center: http://localhost:8080 -------------------------------------------------------------------------------- /cn.ctodb.push.sdk/src/main/java/cn/ctodb/push/client/ClientHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.client; 2 | 3 | import java.io.IOException; 4 | 5 | import cn.ctodb.push.core.Connection; 6 | import cn.ctodb.push.core.PacketReceiver; 7 | import cn.ctodb.push.dto.Command; 8 | import cn.ctodb.push.dto.Packet; 9 | import io.netty.channel.ChannelInboundHandlerAdapter; 10 | 11 | import io.netty.channel.ChannelHandlerContext; 12 | import io.netty.channel.ChannelHandler.Sharable; 13 | 14 | @Sharable 15 | public class ClientHandler extends ChannelInboundHandlerAdapter { 16 | private PacketReceiver packetReceiver; 17 | 18 | public ClientHandler(PacketReceiver packetReceiver) { 19 | this.packetReceiver = packetReceiver; 20 | } 21 | 22 | /** 23 | * tcp链路简历成功后调用 24 | */ 25 | @Override 26 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 27 | System.out.println("成功连接服务器"); 28 | // TextMessage message = new TextMessage(); 29 | // message.setMessage("test"); 30 | // sendMsg(message); 31 | } 32 | 33 | /** 34 | * 收到消息后调用 35 | * 36 | * @throws IOException 37 | */ 38 | @Override 39 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws IOException { 40 | Packet packet = (Packet) msg; 41 | Connection connection = new Connection(); 42 | connection.setChc(ctx); 43 | packetReceiver.onReceive(packet, connection); 44 | } 45 | 46 | /** 47 | * 发生异常时调用 48 | */ 49 | @Override 50 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 51 | System.err.println("与服务器断开连接:" + cause); 52 | ctx.close(); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/core/PacketReceiver.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.core; 2 | 3 | import cn.ctodb.push.dto.Command; 4 | import cn.ctodb.push.dto.Packet; 5 | import cn.ctodb.push.handler.PacketHandler; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.util.ArrayList; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * All rights Reserved, Designed By www.ctodb.cn 16 | * @author: lichaohn@163.com 17 | * @version V1.0 18 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 19 | */ 20 | public final class PacketReceiver { 21 | 22 | private static final Logger logger = LoggerFactory.getLogger(PacketReceiver.class); 23 | private static List filterList = new ArrayList<>(); 24 | private static final Map handlers = new HashMap<>(); 25 | 26 | public void addHandler(PacketHandler handler) { 27 | logger.debug("register : {}", handler.cmd().name()); 28 | handlers.put(handler.cmd(), handler); 29 | } 30 | 31 | public void addFilter(Filter filter) { 32 | logger.debug("filter : {}", filter.getClass()); 33 | filterList.add(filter); 34 | } 35 | 36 | public void onReceive(Packet packet, Connection connection) { 37 | Command command = Command.toCMD(packet.getCmd()); 38 | PacketHandler handler = handlers.get(command); 39 | logger.debug("new packet : {}", command); 40 | if (handler == null) return; 41 | for (Filter filter : filterList) { 42 | if (filter.exec(packet, connection).equals(FilterResult.END)) return; 43 | } 44 | try { 45 | handler.handle(packet, connection); 46 | } catch (Throwable e) { 47 | e.printStackTrace(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/dto/ErrorCode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (C) Copyright 2015-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * Contributors: 17 | * ohun@live.cn (夜色) 18 | */ 19 | 20 | package cn.ctodb.push.dto; 21 | 22 | /** 23 | * All rights Reserved, Designed By www.ctodb.cn 24 | * 25 | * @version V1.0 26 | * @author: lichaohn@163.com 27 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 28 | */ 29 | public enum ErrorCode { 30 | OFFLINE(1, "user offline"), 31 | PUSH_CLIENT_FAILURE(2, "push to client failure"), 32 | ROUTER_CHANGE(3, "router change"), 33 | ACK_TIMEOUT(4, "ack timeout"), 34 | DISPATCH_ERROR(100, "handle message error"), 35 | UNSUPPORTED_CMD(101, "unsupported command"), 36 | UNKNOWN(-1, "unknown"); 37 | 38 | ErrorCode(int code, String errorMsg) { 39 | this.errorMsg = errorMsg; 40 | this.errorCode = (byte) code; 41 | } 42 | 43 | public final byte errorCode; 44 | public final String errorMsg; 45 | 46 | public static ErrorCode toEnum(int code) { 47 | for (ErrorCode errorCode : values()) { 48 | if (errorCode.errorCode == code) { 49 | return errorCode; 50 | } 51 | } 52 | return UNKNOWN; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /cn.ctodb.push.reg/src/main/java/cn/ctodb/push/reg/util/IpUtil.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.reg.util; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import java.net.InetAddress; 5 | import java.net.UnknownHostException; 6 | 7 | public class IpUtil { 8 | public static String getIpAddr(HttpServletRequest request) { 9 | String ipAddress = null; 10 | try { 11 | ipAddress = request.getHeader("x-forwarded-for"); 12 | if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { 13 | ipAddress = request.getHeader("Proxy-Client-IP"); 14 | } 15 | if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { 16 | ipAddress = request.getHeader("WL-Proxy-Client-IP"); 17 | } 18 | if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { 19 | ipAddress = request.getRemoteAddr(); 20 | if (ipAddress.equals("127.0.0.1")) { 21 | // 根据网卡取本机配置的IP 22 | InetAddress inet = null; 23 | try { 24 | inet = InetAddress.getLocalHost(); 25 | } catch (UnknownHostException e) { 26 | e.printStackTrace(); 27 | } 28 | ipAddress = inet.getHostAddress(); 29 | } 30 | } 31 | // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 32 | if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length() 33 | // = 15 34 | if (ipAddress.indexOf(",") > 0) { 35 | ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); 36 | } 37 | } 38 | } catch (Exception e) { 39 | ipAddress = ""; 40 | } 41 | // ipAddress = this.getRequest().getRemoteAddr(); 42 | 43 | return ipAddress; 44 | } 45 | } -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/handler/HandshakeHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.handler; 2 | 3 | import cn.ctodb.push.core.Connection; 4 | import cn.ctodb.push.dto.Command; 5 | import cn.ctodb.push.dto.HandshakeReq; 6 | import cn.ctodb.push.dto.HandshakeResp; 7 | import cn.ctodb.push.dto.Packet; 8 | import cn.ctodb.push.handler.AbstractHandler; 9 | import cn.ctodb.push.server.session.PushSession; 10 | import cn.ctodb.push.server.session.SessionManager; 11 | import cn.ctodb.push.server.util.SessionUtil; 12 | import org.msgpack.MessagePack; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | 15 | import java.io.IOException; 16 | 17 | /** 18 | * All rights Reserved, Designed By www.ctodb.cn 19 | * 20 | * @version V1.0 21 | * @author: lichaohn@163.com 22 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 23 | */ 24 | public class HandshakeHandler extends AbstractHandler { 25 | 26 | @Autowired 27 | private SessionManager sessionManager; 28 | @Autowired 29 | private MessagePack messagePack; 30 | 31 | @Override 32 | public Command cmd() { 33 | return Command.HANDSHAKE_REQ; 34 | } 35 | 36 | @Override 37 | public Class getType() { 38 | return HandshakeReq.class; 39 | } 40 | 41 | @Override 42 | public void handle(HandshakeReq message, Connection connection) { 43 | logger.debug("接收到新的握手请求:{}", message.deviceId); 44 | PushSession session = new PushSession(); 45 | session.setSessionId(SessionUtil.get()); 46 | session.setDeviceId(message.deviceId); 47 | session.setOsName(message.osName); 48 | session.setClientVersion(message.clientVersion); 49 | session.setOsVersion(message.osVersion); 50 | sessionManager.on(session); 51 | Packet packet = new Packet(Command.HANDSHAKE_RESP); 52 | HandshakeResp handshakeResp = new HandshakeResp(); 53 | handshakeResp.sessionId = session.getId(); 54 | try { 55 | packet.setBody(messagePack.write(handshakeResp)); 56 | } catch (IOException e) { 57 | e.printStackTrace(); 58 | } 59 | connection.send(packet); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/conf/ApplicationReadyEventListener.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.conf; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.context.event.ApplicationReadyEvent; 5 | import org.springframework.context.ApplicationListener; 6 | import org.springframework.core.Ordered; 7 | import org.springframework.stereotype.Component; 8 | 9 | import cn.ctodb.push.server.service.MgsServer; 10 | import org.springframework.util.LinkedMultiValueMap; 11 | import org.springframework.util.MultiValueMap; 12 | import org.springframework.web.client.RestTemplate; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | /** 18 | * All rights Reserved, Designed By www.ctodb.cn 19 | * 20 | * @version V1.0 21 | * @author: lichaohn@163.com 22 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 23 | */ 24 | @Component 25 | public class ApplicationReadyEventListener implements ApplicationListener, Ordered { 26 | 27 | @Autowired 28 | private MgsServer mgsServer; 29 | @Autowired 30 | private RestTemplate restTemplate; 31 | @Autowired 32 | private ApplicationProperties applicationProperties; 33 | 34 | @Override 35 | public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { 36 | new Thread(mgsServer).start(); 37 | new Thread(() -> { 38 | while (true) { 39 | try { 40 | MultiValueMap requestEntity = new LinkedMultiValueMap<>(); 41 | requestEntity.add("id", mgsServer.getId()); 42 | requestEntity.add("port", mgsServer.getPort() + ""); 43 | restTemplate.postForObject(applicationProperties.getServer().getCenter() + "/keep", requestEntity, Object.class); 44 | } catch (Exception e) { 45 | e.printStackTrace(); 46 | } 47 | try { 48 | Thread.sleep(1000 * 30l); 49 | } catch (InterruptedException e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | }).start(); 54 | } 55 | 56 | @Override 57 | public int getOrder() { 58 | return Ordered.LOWEST_PRECEDENCE; 59 | } 60 | } -------------------------------------------------------------------------------- /cn.ctodb.push.core/src/main/java/cn/ctodb/push/dto/Packet.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.dto; 2 | 3 | import org.msgpack.annotation.Message; 4 | 5 | /** 6 | * All rights Reserved, Designed By www.ctodb.cn 7 | * 8 | * @version V1.0 9 | * @author: lichaohn@163.com 10 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 11 | */ 12 | @Message 13 | public class Packet { 14 | 15 | private byte cmd; // 命令 16 | private short cc; // 校验码 暂时没有用到 17 | private byte flags; // 特性,如是否加密,是否压缩等 18 | private String sessionId; // 会话id。客户端生成。 19 | private byte lrc; // 校验,纵向冗余校验。只校验head 20 | private byte[] body; 21 | 22 | 23 | public Packet(byte cmd) { 24 | this.cmd = cmd; 25 | } 26 | 27 | public Packet() { 28 | } 29 | 30 | public Packet(byte cmd, String sessionId) { 31 | this.cmd = cmd; 32 | this.sessionId = sessionId; 33 | } 34 | 35 | public Packet(Command cmd) { 36 | this.cmd = cmd.cmd; 37 | } 38 | 39 | public Packet(Command cmd, String sessionId) { 40 | this.cmd = cmd.cmd; 41 | this.sessionId = sessionId; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "{" + "cmd=" + cmd + ", cc=" + cc + ", flags=" + flags + ", sessionId=" + sessionId + ", lrc=" + lrc 47 | + ", body=" + (body == null ? 0 : body.length) + '}'; 48 | } 49 | 50 | public byte getCmd() { 51 | return cmd; 52 | } 53 | 54 | public void setCmd(byte cmd) { 55 | this.cmd = cmd; 56 | } 57 | 58 | public short getCc() { 59 | return cc; 60 | } 61 | 62 | public void setCc(short cc) { 63 | this.cc = cc; 64 | } 65 | 66 | public byte getFlags() { 67 | return flags; 68 | } 69 | 70 | public void setFlags(byte flags) { 71 | this.flags = flags; 72 | } 73 | 74 | public String getSessionId() { 75 | return sessionId; 76 | } 77 | 78 | public void setSessionId(String sessionId) { 79 | this.sessionId = sessionId; 80 | } 81 | 82 | public byte getLrc() { 83 | return lrc; 84 | } 85 | 86 | public void setLrc(byte lrc) { 87 | this.lrc = lrc; 88 | } 89 | 90 | public byte[] getBody() { 91 | return body; 92 | } 93 | 94 | public void setBody(byte[] body) { 95 | this.body = body; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /cn.ctodb.push.reg/src/main/java/cn/ctodb/push/reg/conf/CacheConfiguration.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.reg.conf; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.PropertyAccessor; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.springframework.cache.CacheManager; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.data.redis.cache.RedisCacheConfiguration; 10 | import org.springframework.data.redis.cache.RedisCacheManager; 11 | import org.springframework.data.redis.cache.RedisCacheWriter; 12 | import org.springframework.data.redis.connection.RedisConnectionFactory; 13 | import org.springframework.data.redis.core.RedisTemplate; 14 | import org.springframework.data.redis.core.StringRedisTemplate; 15 | import org.springframework.data.redis.serializer.*; 16 | 17 | import java.time.Duration; 18 | 19 | /** 20 | * Created by cc on 2017/8/8. 21 | */ 22 | @Configuration 23 | public class CacheConfiguration { 24 | 25 | @Bean 26 | public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { 27 | 28 | RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() 29 | .entryTtl(Duration.ofSeconds(30)) 30 | .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer())) 31 | .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer())) 32 | .disableCachingNullValues(); 33 | 34 | RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory) 35 | .cacheDefaults(config) 36 | .transactionAware() 37 | .build(); 38 | 39 | return redisCacheManager; 40 | } 41 | 42 | @Bean 43 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) { 44 | RedisTemplate redisTemplate = new RedisTemplate<>(); 45 | redisTemplate.setConnectionFactory(factory); 46 | redisTemplate.setKeySerializer(keySerializer()); 47 | redisTemplate.setHashKeySerializer(keySerializer()); 48 | redisTemplate.setValueSerializer(valueSerializer()); 49 | redisTemplate.setHashValueSerializer(valueSerializer()); 50 | return redisTemplate; 51 | } 52 | 53 | 54 | private RedisSerializer keySerializer() { 55 | return new StringRedisSerializer(); 56 | } 57 | 58 | private RedisSerializer valueSerializer() { 59 | return new GenericJackson2JsonRedisSerializer(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/UserManager.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server; 2 | 3 | import cn.ctodb.push.dto.User; 4 | import cn.ctodb.push.server.service.MgsServer; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.redis.core.BoundHashOperations; 10 | import org.springframework.data.redis.core.RedisTemplate; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.HashMap; 14 | import java.util.HashSet; 15 | import java.util.Set; 16 | 17 | @Component 18 | public class UserManager { 19 | 20 | private static final Logger logger = LoggerFactory.getLogger(UserManager.class); 21 | 22 | private static HashMap onlineUser = new HashMap<>(); 23 | 24 | @Autowired 25 | private RedisTemplate redisTemplate; 26 | @Autowired 27 | private MgsServer mgsServer; 28 | 29 | private final String onlineUserListKey = "onlineUserListKey"; 30 | 31 | public void clearUserOnlineData() { 32 | redisTemplate.delete(onlineUserListKey); 33 | } 34 | 35 | public void on(User user, ChannelHandlerContext context) { 36 | // 查询是否有其他在线情况 37 | 38 | // 记录但前进程连接的用户信息 39 | onlineUser.put(user.id, context); 40 | User u = getUserHash().get(user.id); 41 | if (u != null && u.servers != null) { 42 | user.servers = u.servers; 43 | } else { 44 | user.servers = new HashSet<>(); 45 | } 46 | user.servers.add(mgsServer.getId()); 47 | // 记录信息进redis 48 | getUserHash().put(user.id, user); 49 | logger.info("user online {}", user.id); 50 | } 51 | 52 | public void off(String userId) { 53 | User u = getUserHash().get(userId); 54 | u.servers.remove(mgsServer.getId()); 55 | getUserHash().put(userId, u); 56 | onlineUser.remove(userId).disconnect(); 57 | logger.info("user offline {}", userId); 58 | } 59 | 60 | public ChannelHandlerContext getContext(String userId) { 61 | return onlineUser.get(userId); 62 | } 63 | 64 | //在线用户数量 65 | public int getOnlineUserNum() { 66 | Integer value = onlineUser.size(); 67 | return value == null ? 0 : value; 68 | } 69 | 70 | //在线用户列表 71 | public Set getOnlineUserList(String publicIP, int start, int end) { 72 | return onlineUser.keySet(); 73 | } 74 | 75 | public BoundHashOperations getUserHash() { 76 | return redisTemplate.boundHashOps(onlineUserListKey); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/ServerHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server; 2 | 3 | import cn.ctodb.push.core.Connection; 4 | import cn.ctodb.push.core.PacketReceiver; 5 | import cn.ctodb.push.dto.Packet; 6 | import io.netty.channel.Channel; 7 | import io.netty.channel.ChannelHandler.Sharable; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.ChannelInboundHandlerAdapter; 10 | import io.netty.channel.group.ChannelGroup; 11 | import io.netty.channel.group.DefaultChannelGroup; 12 | import io.netty.util.concurrent.GlobalEventExecutor; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | // 服务端消息处理统一入口 17 | @Sharable 18 | public class ServerHandler extends ChannelInboundHandlerAdapter { 19 | private Logger logger = LoggerFactory.getLogger(ServerHandler.class); 20 | public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 21 | 22 | private PacketReceiver packetReceiver; 23 | 24 | public ServerHandler(PacketReceiver packetReceiver) { 25 | this.packetReceiver = packetReceiver; 26 | } 27 | 28 | @Override 29 | public void handlerAdded(ChannelHandlerContext ctx) { // (2) 30 | Channel incoming = ctx.channel(); 31 | logger.debug("handlerAdded : {}", incoming.remoteAddress()); 32 | channels.add(ctx.channel()); 33 | } 34 | 35 | @Override 36 | public void handlerRemoved(ChannelHandlerContext ctx) { // (3) 37 | Channel incoming = ctx.channel(); 38 | logger.debug("handlerRemoved : {}", incoming.remoteAddress()); 39 | channels.remove(ctx.channel()); 40 | } 41 | 42 | @Override 43 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // (4) 44 | Packet packet = (Packet) msg; 45 | Connection connection = new Connection(); 46 | connection.setChc(ctx); 47 | Channel channel = ctx.channel(); 48 | packetReceiver.onReceive(packet, connection); 49 | } 50 | 51 | @Override 52 | public void channelActive(ChannelHandlerContext ctx) throws Exception { // (5) 53 | Channel incoming = ctx.channel(); 54 | logger.debug("channelActive : {}", incoming.remoteAddress()); 55 | } 56 | 57 | @Override 58 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { // (6) 59 | Channel incoming = ctx.channel(); 60 | logger.debug("channelInactive : {}", incoming.remoteAddress()); 61 | } 62 | 63 | @Override 64 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 65 | // 当出现异常就关闭连接 66 | Channel incoming = ctx.channel(); 67 | logger.error("exceptionCaught : " + incoming.remoteAddress(), cause); 68 | ctx.close(); 69 | } 70 | } -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/filter/AuthFilter.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.filter; 2 | 3 | import cn.ctodb.push.core.Connection; 4 | import cn.ctodb.push.core.Filter; 5 | import cn.ctodb.push.core.FilterResult; 6 | import cn.ctodb.push.dto.Command; 7 | import cn.ctodb.push.dto.Error; 8 | import cn.ctodb.push.dto.ErrorCode; 9 | import cn.ctodb.push.dto.Packet; 10 | import cn.ctodb.push.server.session.Session; 11 | import cn.ctodb.push.server.session.SessionManager; 12 | import org.msgpack.MessagePack; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | 17 | import java.io.IOException; 18 | import java.util.HashSet; 19 | import java.util.Set; 20 | 21 | /** 22 | * All rights Reserved, Designed By www.ctodb.cn 23 | * 24 | * @version V1.0 25 | * @author: lichaohn@163.com 26 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 27 | */ 28 | public class AuthFilter implements Filter { 29 | 30 | private Logger logger = LoggerFactory.getLogger(AuthFilter.class); 31 | 32 | public Set commands = new HashSet<>(); 33 | 34 | public void addCmd(Command command) { 35 | commands.add(command); 36 | } 37 | 38 | @Autowired 39 | private MessagePack messagePack; 40 | @Autowired 41 | private SessionManager sessionManager; 42 | 43 | @Override 44 | public FilterResult exec(Packet packet, Connection connection) { 45 | Command command = Command.toCMD(packet.getCmd()); 46 | logger.debug("command : {}", command); 47 | if (commands.contains(command)) { 48 | String sessionId = packet.getSessionId(); 49 | if (sessionId == null) { 50 | logger.info("sessionId is null"); 51 | sendOffline(packet,connection); 52 | return FilterResult.END; 53 | } else { 54 | logger.info("sessionId is [{}]", sessionId); 55 | Session session = sessionManager.get(sessionId); 56 | if (session == null) { 57 | logger.info("session is Invalid : {}", sessionId); 58 | sendOffline(packet,connection); 59 | return FilterResult.END; 60 | } 61 | logger.debug("session is {}", session); 62 | } 63 | } 64 | return FilterResult.NEXT; 65 | } 66 | 67 | public void sendOffline(Packet packet, Connection connection){ 68 | Packet re = new Packet(Command.ERROR); 69 | re.setSessionId(packet.getSessionId()); 70 | Error error = new Error(); 71 | error.setCode(ErrorCode.OFFLINE, "用户不在线"); 72 | try { 73 | re.setBody(messagePack.write(error)); 74 | } catch (IOException e) { 75 | e.printStackTrace(); 76 | } 77 | connection.send(re); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | cn.ctodb.push 6 | push-parent 7 | 1.0.0-SNAPSHOT 8 | pom 9 | 10 | 11 | cc 12 | lichaohn@163.com 13 | http://www.ctodb.cn 14 | https://github.com/ctodb/push 15 | 16 | 17 | 18 | 19 | 20 | io.netty 21 | netty-all 22 | 4.1.12.Final 23 | 24 | 25 | org.slf4j 26 | slf4j-api 27 | 28 | 29 | ch.qos.logback 30 | logback-classic 31 | 32 | 33 | org.msgpack 34 | msgpack 35 | 0.6.12 36 | 37 | 38 | junit 39 | junit 40 | test 41 | 42 | 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-dependencies 48 | 2.0.4.RELEASE 49 | pom 50 | import 51 | 52 | 53 | cn.ctodb.push 54 | push-core 55 | ${project.version} 56 | 57 | 58 | 59 | 60 | cn.ctodb.push.core 61 | cn.ctodb.push.server 62 | cn.ctodb.push.sdk 63 | cn.ctodb.push.reg 64 | 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-compiler-plugin 70 | 71 | 1.8 72 | 1.8 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-maven-plugin 78 | 79 | true 80 | 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-surefire-plugin 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/session/PushSession.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.session; 2 | 3 | import java.net.UnknownHostException; 4 | import java.security.cert.Certificate; 5 | import java.util.Date; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public class PushSession implements Session { 10 | 11 | private final Map sessionData = new HashMap<>(); 12 | private String osName; 13 | private String osVersion; 14 | private String clientVersion; 15 | private String deviceId; 16 | private String userId; 17 | private String sessionId; 18 | private long expireTime; 19 | private int status; 20 | private long startDate = System.currentTimeMillis(); 21 | private long lastActiveDate; 22 | 23 | @Override 24 | public String getId() { 25 | return sessionId; 26 | } 27 | 28 | @Override 29 | public int getStatus() { 30 | return 0; 31 | } 32 | 33 | @Override 34 | public Date getCreationDate() { 35 | return null; 36 | } 37 | 38 | @Override 39 | public Date getLastActiveDate() { 40 | return null; 41 | } 42 | 43 | @Override 44 | public long getNumClientPackets() { 45 | return 0; 46 | } 47 | 48 | @Override 49 | public long getNumServerPackets() { 50 | return 0; 51 | } 52 | 53 | @Override 54 | public void close() { 55 | 56 | } 57 | 58 | @Override 59 | public boolean isClosed() { 60 | return false; 61 | } 62 | 63 | @Override 64 | public boolean isSecure() { 65 | return false; 66 | } 67 | 68 | @Override 69 | public Certificate[] getPeerCertificates() { 70 | return new Certificate[0]; 71 | } 72 | 73 | @Override 74 | public String getHostAddress() throws UnknownHostException { 75 | return null; 76 | } 77 | 78 | @Override 79 | public String getHostName() throws UnknownHostException { 80 | return null; 81 | } 82 | 83 | @Override 84 | public boolean validate() { 85 | return false; 86 | } 87 | 88 | public void setSessionId(String sessionId) { 89 | this.sessionId = sessionId; 90 | } 91 | 92 | public String getDeviceId() { 93 | return deviceId; 94 | } 95 | 96 | public void setDeviceId(String deviceId) { 97 | this.deviceId = deviceId; 98 | } 99 | 100 | public String getOsVersion() { 101 | return osVersion; 102 | } 103 | 104 | public void setOsVersion(String osVersion) { 105 | this.osVersion = osVersion; 106 | } 107 | 108 | public String getClientVersion() { 109 | return clientVersion; 110 | } 111 | 112 | public void setClientVersion(String clientVersion) { 113 | this.clientVersion = clientVersion; 114 | } 115 | 116 | public String getOsName() { 117 | return osName; 118 | } 119 | 120 | public void setOsName(String osName) { 121 | this.osName = osName; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/conf/CacheConfiguration.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.conf; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.PropertyAccessor; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.springframework.cache.CacheManager; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.data.redis.cache.RedisCacheConfiguration; 10 | import org.springframework.data.redis.cache.RedisCacheManager; 11 | import org.springframework.data.redis.cache.RedisCacheWriter; 12 | import org.springframework.data.redis.connection.RedisConnectionFactory; 13 | import org.springframework.data.redis.core.RedisTemplate; 14 | import org.springframework.data.redis.core.StringRedisTemplate; 15 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 16 | 17 | import java.time.Duration; 18 | 19 | 20 | /** 21 | * All rights Reserved, Designed By www.ctodb.cn 22 | * 23 | * @version V1.0 24 | * @author: lichaohn@163.com 25 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 26 | */ 27 | @Configuration 28 | public class CacheConfiguration { 29 | 30 | @Bean 31 | public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { 32 | //初始化一个RedisCacheWriter 33 | RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory); 34 | //设置CacheManager的值序列化方式为JdkSerializationRedisSerializer,但其实RedisCacheConfiguration默认就是使用StringRedisSerializer序列化key,JdkSerializationRedisSerializer序列化value,所以以下注释代码为默认实现 35 | //ClassLoader loader = this.getClass().getClassLoader(); 36 | //JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer(loader); 37 | //RedisSerializationContext.SerializationPair pair = RedisSerializationContext.SerializationPair.fromSerializer(jdkSerializer); 38 | //RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair); 39 | RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig(); 40 | //设置默认超过期时间是30秒 41 | defaultCacheConfig.entryTtl(Duration.ofSeconds(30)); 42 | //初始化RedisCacheManager 43 | RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig); 44 | return cacheManager; 45 | } 46 | 47 | @Bean 48 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) { 49 | StringRedisTemplate template = new StringRedisTemplate(factory); 50 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); 51 | ObjectMapper om = new ObjectMapper(); 52 | om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 53 | om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 54 | jackson2JsonRedisSerializer.setObjectMapper(om); 55 | template.setValueSerializer(jackson2JsonRedisSerializer); 56 | template.afterPropertiesSet(); 57 | return template; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /cn.ctodb.push.reg/src/main/java/cn/ctodb/push/reg/controller/RegController.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.reg.controller; 2 | 3 | import cn.ctodb.push.reg.entity.ServerNode; 4 | import cn.ctodb.push.reg.util.IpUtil; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.data.redis.core.BoundHashOperations; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | import org.springframework.scheduling.annotation.Scheduled; 11 | import org.springframework.util.StringUtils; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestParam; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | import javax.servlet.http.HttpServletRequest; 18 | import java.util.*; 19 | 20 | @RestController 21 | public class RegController { 22 | private Logger logger = LoggerFactory.getLogger(RegController.class); 23 | 24 | @Autowired 25 | private RedisTemplate redisTemplate; 26 | 27 | @PostMapping("keep") 28 | public ServerNode keep(@RequestParam String id, @RequestParam(required = false) String ip, @RequestParam String port, HttpServletRequest request) { 29 | BoundHashOperations imKeep = getNodes(); 30 | ServerNode serverNode = null; 31 | if (!imKeep.hasKey(id)) { 32 | serverNode = new ServerNode(); 33 | serverNode.setId(id); 34 | if (StringUtils.isEmpty(ip)) 35 | ip = IpUtil.getIpAddr(request); 36 | serverNode.setUrl(ip + ":" + port); 37 | serverNode.setLastCheckTime(System.currentTimeMillis()); 38 | } else { 39 | serverNode = (ServerNode) imKeep.get(id); 40 | serverNode.setLastCheckTime(System.currentTimeMillis()); 41 | } 42 | logger.debug("keep:{} {} {}", id, ip, port); 43 | imKeep.put(id, serverNode); 44 | return serverNode; 45 | } 46 | 47 | @GetMapping("nodes") 48 | public Set nodes() { 49 | BoundHashOperations imKeep = getNodes(); 50 | Set set = new HashSet<>(); 51 | for (ServerNode node : Objects.requireNonNull(imKeep.values())) { 52 | set.add(node.getUrl()); 53 | } 54 | return set; 55 | } 56 | 57 | private BoundHashOperations getNodes() { 58 | BoundHashOperations imKeep = redisTemplate.boundHashOps("im_keep"); 59 | return imKeep; 60 | } 61 | 62 | @Scheduled(cron = "0/30 * * * * *") 63 | public void heartbeat() { 64 | logger.debug("heartbeat"); 65 | BoundHashOperations imKeep = getNodes(); 66 | for (Map.Entry node : imKeep.entries().entrySet()) { 67 | ServerNode serverNode = node.getValue(); 68 | if (System.currentTimeMillis() - serverNode.getLastCheckTime() > 70 * 1000) { 69 | imKeep.delete(node.getKey()); 70 | logger.debug("heartbeat delete:{} {}", node.getKey(), System.currentTimeMillis() - serverNode.getLastCheckTime()); 71 | } 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /cn.ctodb.push.reg/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.0.4.RELEASE 9 | 10 | 4.0.0 11 | push-reg 12 | 13 | 14 | cc 15 | lichaohn@163.com 16 | https://www.ctodb.cn 17 | https://github.com/ctodb/push 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-cache 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-data-redis 32 | 33 | 34 | org.apache.commons 35 | commons-pool2 36 | 37 | 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-maven-plugin 43 | 44 | 45 | 46 | repackage 47 | 48 | 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-compiler-plugin 54 | 55 | 1.8 56 | 1.8 57 | 58 | 59 | 60 | 61 | 62 | 63 | dev 64 | 65 | true 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-starter-undertow 71 | 72 | 73 | org.springframework.boot 74 | spring-boot-devtools 75 | true 76 | 77 | 78 | 79 | 80 | 81 | org.apache.maven.plugins 82 | maven-war-plugin 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | DEBUG 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | push-server 5 | 6 | cn.ctodb.push 7 | push-parent 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | 12 | cc 13 | lichaohn@163.com 14 | https://www.ctodb.cn 15 | https://github.com/ctodb/push 16 | 17 | 18 | 19 | 20 | cn.ctodb.push 21 | push-core 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-actuator 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-test 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-cache 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-data-redis 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-configuration-processor 46 | 47 | 48 | org.springframework.kafka 49 | spring-kafka 50 | 51 | 52 | 53 | com.alibaba 54 | fastjson 55 | 1.2.37 56 | 57 | 58 | org.apache.commons 59 | commons-pool2 60 | 61 | 62 | org.apache.commons 63 | commons-lang3 64 | 65 | 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-maven-plugin 71 | 72 | 73 | 74 | repackage 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | dev 84 | 85 | true 86 | 87 | 88 | 89 | org.springframework.boot 90 | spring-boot-starter-undertow 91 | 92 | 93 | org.springframework.boot 94 | spring-boot-devtools 95 | true 96 | 97 | 98 | 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-war-plugin 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | DEBUG 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /cn.ctodb.push.sdk/src/test/java/ClientTest.java: -------------------------------------------------------------------------------- 1 | import cn.ctodb.push.client.Client; 2 | import cn.ctodb.push.client.ClientProperties; 3 | import cn.ctodb.push.client.Util; 4 | import cn.ctodb.push.dto.Command; 5 | import cn.ctodb.push.dto.HandshakeReq; 6 | import cn.ctodb.push.dto.Packet; 7 | import cn.ctodb.push.dto.TextMessage; 8 | import org.json.JSONArray; 9 | import org.json.JSONObject; 10 | import org.junit.BeforeClass; 11 | import org.junit.Test; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.io.ByteArrayOutputStream; 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.net.HttpURLConnection; 19 | import java.net.URL; 20 | 21 | /** 22 | * Created by cc on 2017/7/7. 23 | */ 24 | public class ClientTest { 25 | 26 | private static Client client; 27 | private static Logger logger = LoggerFactory.getLogger(ClientTest.class); 28 | 29 | @BeforeClass 30 | public static void init() { 31 | logger.debug("init"); 32 | String re = getJsonByInternet("http://localhost:8080/nodes"); 33 | logger.debug("发现服务器地址:{}", re); 34 | JSONArray json = new JSONArray(re); 35 | String str = json.get(0).toString(); 36 | String[] serverUrl = str.split("[:]"); 37 | ClientProperties properties = new ClientProperties(); 38 | properties.setServerHost(serverUrl[0]); 39 | properties.setServerPort(Integer.parseInt(serverUrl[1])); 40 | client = new Client(properties); 41 | try { 42 | client.start(); 43 | } catch (IOException e) { 44 | e.printStackTrace(); 45 | } catch (InterruptedException e) { 46 | e.printStackTrace(); 47 | } 48 | while (true) { 49 | try { 50 | Thread.sleep(1000L); 51 | } catch (InterruptedException e) { 52 | e.printStackTrace(); 53 | } 54 | if (!client.getStatus().equals(Client.Status.STARTING)) break; 55 | } 56 | } 57 | 58 | @Test 59 | public void HandshakeHandler() throws IOException, InterruptedException { 60 | Packet packet = new Packet(Command.HANDSHAKE_REQ); 61 | HandshakeReq handshakeReq = new HandshakeReq(); 62 | handshakeReq.deviceId = "deviceId"; 63 | handshakeReq.osName = "ios"; 64 | handshakeReq.osVersion = "10.10"; 65 | handshakeReq.clientVersion = "1.0.0"; 66 | packet.setBody(Util.msg2bytes(handshakeReq)); 67 | client.send(packet); 68 | Thread.sleep(2000L); 69 | } 70 | 71 | @Test 72 | public void sendMsg() throws InterruptedException { 73 | TextMessage textMessage = new TextMessage(); 74 | textMessage.setContent("测试文本消息"); 75 | logger.debug(client.getStatus() + " : " + "测试文本消息"); 76 | client.sendMessage(textMessage); 77 | Thread.sleep(2000L); 78 | } 79 | 80 | public static String getJsonByInternet(String path) { 81 | try { 82 | URL url = new URL(path.trim()); 83 | //打开连接 84 | HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); 85 | 86 | if (200 == urlConnection.getResponseCode()) { 87 | //得到输入流 88 | InputStream is = urlConnection.getInputStream(); 89 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 90 | byte[] buffer = new byte[1024]; 91 | int len = 0; 92 | while (-1 != (len = is.read(buffer))) { 93 | baos.write(buffer, 0, len); 94 | baos.flush(); 95 | } 96 | return baos.toString("utf-8"); 97 | } 98 | } catch (IOException e) { 99 | e.printStackTrace(); 100 | } 101 | 102 | return null; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/conf/ServerConfiguration.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.conf; 2 | 3 | import cn.ctodb.push.core.PacketReceiver; 4 | import cn.ctodb.push.dto.Command; 5 | import cn.ctodb.push.server.filter.AuthFilter; 6 | import cn.ctodb.push.server.handler.HandshakeHandler; 7 | import cn.ctodb.push.server.handler.HeartBeatHandler; 8 | import cn.ctodb.push.server.handler.TextMessageHandler; 9 | import org.msgpack.MessagePack; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 14 | import org.springframework.boot.web.client.RestTemplateBuilder; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | 18 | import cn.ctodb.push.server.ServerHandler; 19 | import cn.ctodb.push.server.service.MgsServer; 20 | import cn.ctodb.push.utils.MsgPackDecode; 21 | import cn.ctodb.push.utils.MsgPackEncode; 22 | import org.springframework.web.client.RestTemplate; 23 | 24 | import java.io.IOException; 25 | import java.net.ServerSocket; 26 | 27 | /** 28 | * All rights Reserved, Designed By www.ctodb.cn 29 | * 30 | * @version V1.0 31 | * @author: lichaohn@163.com 32 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 33 | */ 34 | @Configuration 35 | @EnableConfigurationProperties(ApplicationProperties.class) 36 | public class ServerConfiguration { 37 | private Logger logger = LoggerFactory.getLogger(ServerConfiguration.class); 38 | 39 | @Autowired 40 | private ApplicationProperties serverProperties; 41 | 42 | @Autowired 43 | private RestTemplateBuilder builder; 44 | 45 | @Bean 46 | public RestTemplate restTemplate() { 47 | return builder.build(); 48 | } 49 | 50 | @Bean 51 | public MgsServer mgsServer() throws IOException { 52 | if (serverProperties.getServer().getPort() < 0) { 53 | ServerSocket serverSocket = new ServerSocket(0); //读取空闲的可用端口 54 | int port = serverSocket.getLocalPort(); 55 | serverProperties.getServer().setPort(port); 56 | logger.info("使用随机端口号:" + port); 57 | } 58 | return new MgsServer(serverProperties.getServer().getPort(), serverHandler(), msgPackDecode(), msgPackEncode()); 59 | } 60 | 61 | @Bean 62 | public ServerHandler serverHandler() { 63 | return new ServerHandler(packetReceiver()); 64 | } 65 | 66 | @Bean 67 | public MsgPackEncode msgPackEncode() { 68 | return new MsgPackEncode(); 69 | } 70 | 71 | @Bean 72 | public MessagePack messagePack() { 73 | return new MessagePack(); 74 | } 75 | 76 | @Bean 77 | public MsgPackDecode msgPackDecode() { 78 | return new MsgPackDecode(); 79 | } 80 | 81 | @Bean 82 | public PacketReceiver packetReceiver() { 83 | PacketReceiver packetReceiver = new PacketReceiver(); 84 | packetReceiver.addHandler(heartBeatHandler()); 85 | packetReceiver.addHandler(handshakeHandler()); 86 | packetReceiver.addHandler(textMessageHandler()); 87 | packetReceiver.addFilter(authFilter()); 88 | return packetReceiver; 89 | } 90 | 91 | @Bean 92 | public AuthFilter authFilter() { 93 | AuthFilter authFilter = new AuthFilter(); 94 | authFilter.addCmd(Command.TEXT_MESSAGE); 95 | return authFilter; 96 | } 97 | 98 | @Bean 99 | public HeartBeatHandler heartBeatHandler() { 100 | return new HeartBeatHandler(); 101 | } 102 | 103 | @Bean 104 | public TextMessageHandler textMessageHandler() { 105 | return new TextMessageHandler(); 106 | } 107 | 108 | @Bean 109 | public HandshakeHandler handshakeHandler() { 110 | return new HandshakeHandler(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /cn.ctodb.push.sdk/src/main/java/cn/ctodb/push/client/Client.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.client; 2 | 3 | import cn.ctodb.push.client.conf.ClientConfiguration; 4 | import cn.ctodb.push.dto.Message; 5 | import cn.ctodb.push.dto.Packet; 6 | import cn.ctodb.push.utils.MsgPackDecode; 7 | import cn.ctodb.push.utils.MsgPackEncode; 8 | import io.netty.bootstrap.Bootstrap; 9 | import io.netty.channel.Channel; 10 | import io.netty.channel.ChannelInitializer; 11 | import io.netty.channel.ChannelPipeline; 12 | import io.netty.channel.EventLoopGroup; 13 | import io.netty.channel.nio.NioEventLoopGroup; 14 | import io.netty.channel.socket.SocketChannel; 15 | import io.netty.channel.socket.nio.NioSocketChannel; 16 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 17 | import io.netty.handler.codec.LengthFieldPrepender; 18 | import org.msgpack.MessagePack; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import java.io.IOException; 23 | 24 | /** 25 | * Netty 服务端代码 26 | * 27 | * @author lihzh 28 | * @alia OneCoder 29 | * @blog http://www.coderli.com 30 | */ 31 | public class Client implements Runnable { 32 | 33 | private Logger logger = LoggerFactory.getLogger(Client.class); 34 | 35 | private Status status = Status.STOP; 36 | private String sessionId = null; 37 | 38 | private ClientProperties clientProperties; 39 | private Channel channel; 40 | private EventLoopGroup workerGroup; 41 | 42 | public Client(ClientProperties clientProperties) { 43 | this.clientProperties = clientProperties; 44 | } 45 | 46 | public void start() throws IOException, InterruptedException { 47 | if (status.equals(Status.STOP)) { 48 | status = Status.STARTING; 49 | new Thread(this).start(); 50 | new Thread(new HeartBeatTasker(this)).start(); 51 | logger.info("client start..."); 52 | } 53 | } 54 | 55 | public void send(Packet packet) { 56 | channel.writeAndFlush(packet); 57 | } 58 | 59 | public void sendMessage(Message message) { 60 | Packet packet = new Packet(message.getCmd(), ClientInfo.getSessionId()); 61 | try { 62 | packet.setBody(new MessagePack().write(message)); 63 | channel.writeAndFlush(packet); 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | 69 | public void run() { 70 | workerGroup = new NioEventLoopGroup(); 71 | try { 72 | Bootstrap b = new Bootstrap(); 73 | b.group(workerGroup); 74 | b.channel(NioSocketChannel.class); 75 | // b.option(ChannelOption.SO_KEEPALIVE, true); 76 | b.handler(new ChannelInitializer() { 77 | @Override 78 | public void initChannel(SocketChannel ch) throws Exception { 79 | ChannelPipeline pipeline = ch.pipeline(); 80 | pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65536, 0, 4, 0, 4)); 81 | pipeline.addLast("frameEncoder", new LengthFieldPrepender(4)); 82 | pipeline.addLast("decoder", new MsgPackDecode()); 83 | pipeline.addLast("encoder", new MsgPackEncode()); 84 | pipeline.addLast(ClientConfiguration.clientHandler()); 85 | } 86 | }); 87 | channel = b.connect(clientProperties.getServerHost(), clientProperties.getServerPort()).sync().channel(); 88 | status = Status.START; 89 | channel.closeFuture().sync(); 90 | } catch (Exception e) { 91 | e.printStackTrace(); 92 | } 93 | status = Status.STOP; 94 | } 95 | 96 | public void stop() { 97 | if (workerGroup != null) { 98 | channel.close(); 99 | workerGroup.shutdownGracefully(); 100 | } 101 | status = Status.STOP; 102 | } 103 | 104 | public Status getStatus() { 105 | return status; 106 | } 107 | 108 | public String getSessionId() { 109 | return sessionId; 110 | } 111 | 112 | public void setSessionId(String sessionId) { 113 | this.sessionId = sessionId; 114 | } 115 | 116 | public enum Status { 117 | STOP, START, STARTING 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /cn.ctodb.push.server/src/main/java/cn/ctodb/push/server/service/MgsServer.java: -------------------------------------------------------------------------------- 1 | package cn.ctodb.push.server.service; 2 | 3 | import cn.ctodb.push.server.ServerHandler; 4 | import cn.ctodb.push.utils.MsgPackDecode; 5 | import cn.ctodb.push.utils.MsgPackEncode; 6 | import io.netty.bootstrap.ServerBootstrap; 7 | import io.netty.channel.*; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 12 | import io.netty.handler.codec.LengthFieldPrepender; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.beans.factory.DisposableBean; 16 | 17 | import java.util.UUID; 18 | 19 | /** 20 | * All rights Reserved, Designed By www.ctodb.cn 21 | * 22 | * @version V1.0 23 | * @author: lichaohn@163.com 24 | * @Copyright: 2018 www.ctodb.cn Inc. All rights reserved. 25 | */ 26 | public class MgsServer implements DisposableBean, Runnable { 27 | 28 | private static final Logger logger = LoggerFactory.getLogger(MgsServer.class); 29 | 30 | /** 31 | * 用于分配处理业务线程的线程组个数 32 | */ 33 | protected static final int BIZGROUPSIZE = Runtime.getRuntime().availableProcessors() * 2; // 默认 34 | /** 35 | * 业务出现线程大小 36 | */ 37 | protected static final int BIZTHREADSIZE = 4; 38 | private EventLoopGroup bossGroup; 39 | private EventLoopGroup workerGroup; 40 | private Channel channel; 41 | private final int port; 42 | private String status = "stop"; 43 | private String id; 44 | 45 | public MgsServer(int port, ServerHandler serverHandler, MsgPackDecode msgPackDecode, MsgPackEncode msgPackEncode) { 46 | super(); 47 | this.id = UUID.randomUUID().toString(); 48 | this.port = port; 49 | this.serverHandler = serverHandler; 50 | this.msgPackDecode = msgPackDecode; 51 | this.msgPackEncode = msgPackEncode; 52 | } 53 | 54 | private final ServerHandler serverHandler; 55 | private final MsgPackDecode msgPackDecode; 56 | private final MsgPackEncode msgPackEncode; 57 | 58 | @Override 59 | public void run() { 60 | ServerBootstrap b = new ServerBootstrap();// 引导辅助程序 61 | 62 | bossGroup = new NioEventLoopGroup(BIZGROUPSIZE); 63 | workerGroup = new NioEventLoopGroup(BIZTHREADSIZE); 64 | try { 65 | b.group(bossGroup, workerGroup); 66 | b.channel(NioServerSocketChannel.class);// 设置nio类型的channel 67 | b.childHandler(new ChannelInitializer() {// 有连接到达时会创建一个channel 68 | protected void initChannel(SocketChannel ch) throws Exception { 69 | logger.debug("客户端:{} 初始化", ch.remoteAddress()); 70 | // pipeline管理channel中的Handler,在channel队列中添加一个handler来处理业务 71 | ch.pipeline().addLast("frameDecoder", 72 | new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)); 73 | ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(4)); 74 | ch.pipeline().addLast("decoder", msgPackDecode); 75 | ch.pipeline().addLast("encoder", msgPackEncode); 76 | ch.pipeline().addLast(serverHandler); 77 | } 78 | }); 79 | b.option(ChannelOption.SO_BACKLOG, 128); 80 | b.childOption(ChannelOption.SO_KEEPALIVE, true); 81 | logger.info("server start : {}", port); 82 | status = "run"; 83 | ChannelFuture f = b.bind(port).sync();// 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功 84 | channel = f.channel(); 85 | f.channel().closeFuture().sync();// 应用程序会一直等待,直到channel关闭 86 | } catch (Exception e) { 87 | status = "error"; 88 | e.printStackTrace(); 89 | } 90 | 91 | } 92 | 93 | public void stop() { 94 | status = "stop"; 95 | logger.info("destroy server resources"); 96 | if (null == channel) { 97 | logger.error("server channel is null"); 98 | } 99 | bossGroup.shutdownGracefully(); 100 | workerGroup.shutdownGracefully(); 101 | channel.closeFuture().syncUninterruptibly(); 102 | channel = null; 103 | } 104 | 105 | @Override 106 | public void destroy() throws Exception { 107 | stop(); 108 | } 109 | 110 | public String getId() { 111 | return id; 112 | } 113 | 114 | public int getPort() { 115 | return port; 116 | } 117 | 118 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "{}" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright 2017 CC 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. --------------------------------------------------------------------------------