├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── dictionaries │ └── zhza4586.xml ├── encodings.xml ├── libraries │ ├── Maven__aopalliance_aopalliance_1_0.xml │ ├── Maven__ch_qos_logback_logback_classic_1_0_13.xml │ ├── Maven__ch_qos_logback_logback_core_1_0_13.xml │ ├── Maven__com_google_guava_guava_15_0.xml │ ├── Maven__com_google_protobuf_protobuf_java_2_5_0.xml │ ├── Maven__com_ning_async_http_client_1_9_0_BETA23.xml │ ├── Maven__commons_logging_commons_logging_1_1_1.xml │ ├── Maven__commons_pool_commons_pool_1_6.xml │ ├── Maven__io_netty_netty_3_9_5_Final.xml │ ├── Maven__io_netty_netty_all_4_0_23_Final.xml │ ├── Maven__jline_jline_0_9_94.xml │ ├── Maven__junit_junit_3_8_1.xml │ ├── Maven__org_apache_commons_commons_lang3_3_2_1.xml │ ├── Maven__org_apache_curator_curator_client_2_6_0.xml │ ├── Maven__org_apache_curator_curator_framework_2_6_0.xml │ ├── Maven__org_apache_curator_curator_recipes_2_6_0.xml │ ├── Maven__org_apache_zookeeper_zookeeper_3_4_6.xml │ ├── Maven__org_slf4j_jul_to_slf4j_1_5_11.xml │ ├── Maven__org_slf4j_slf4j_api_1_7_5.xml │ ├── Maven__org_springframework_spring_aop_4_0_0_RELEASE.xml │ ├── Maven__org_springframework_spring_beans_4_0_0_RELEASE.xml │ ├── Maven__org_springframework_spring_context_4_0_0_RELEASE.xml │ ├── Maven__org_springframework_spring_core_4_0_0_RELEASE.xml │ ├── Maven__org_springframework_spring_expression_4_0_0_RELEASE.xml │ ├── Maven__org_yaml_snakeyaml_1_13.xml │ └── Maven__redis_clients_jedis_2_2_1.xml ├── misc.xml ├── modules.xml ├── scopes │ └── scope_settings.xml ├── uiDesigner.xml ├── vcs.xml └── workspace.xml ├── NewIM.iml ├── README.md ├── demo ├── demo.iml ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── whosbean │ │ │ ├── App.java │ │ │ └── ChatClient.java │ └── resources │ │ └── logback.xml │ └── test │ └── java │ └── com │ └── whosbean │ └── AppTest.java ├── newim-chat ├── bin │ ├── copy.sh │ ├── scpfile.sh │ └── start.sh ├── newim-chat.iml ├── pom.xml └── src │ ├── main │ ├── assembly │ │ └── chatter.xml │ ├── java │ │ └── com │ │ │ └── whosbean │ │ │ └── newim │ │ │ ├── ChatterMain.java │ │ │ └── chatter │ │ │ ├── ChatterConfig.java │ │ │ ├── RouterServerNode.java │ │ │ ├── exchange │ │ │ ├── ExchangeClient.java │ │ │ └── ExchangeClientManager.java │ │ │ └── router │ │ │ ├── BoxMsgListenThread.java │ │ │ ├── ChatMessageListener.java │ │ │ ├── NewMemberNotifyThread.java │ │ │ └── NewMessageNotifyThread.java │ └── resources │ │ ├── chatter.yaml │ │ ├── logback.xml │ │ ├── redis.yaml │ │ ├── spring-chatter.xml │ │ └── zookeeper.yaml │ └── test │ └── java │ └── com │ └── whosbean │ └── AppTest.java ├── newim-common ├── newim-common.iml ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── whosbean │ │ │ └── newim │ │ │ ├── common │ │ │ └── AppContextHolder.java │ │ │ ├── config │ │ │ ├── AbstractConfig.java │ │ │ └── ConfigYamlManager.java │ │ │ ├── redis │ │ │ ├── RedisBucket.java │ │ │ ├── RedisConfig.java │ │ │ └── RedisMessageService.java │ │ │ ├── server │ │ │ ├── ChatServerNode.java │ │ │ ├── ServerNode.java │ │ │ ├── ServerNodeRoles.java │ │ │ └── ServerStarter.java │ │ │ ├── service │ │ │ ├── ChatMessageService.java │ │ │ └── ChatMessageServiceFactory.java │ │ │ └── zookeeper │ │ │ ├── ZKPaths.java │ │ │ └── ZookeeperConfig.java │ └── resources │ │ ├── redis.yaml │ │ └── zookeeper.yaml │ └── test │ └── java │ └── com │ └── whosbean │ └── AppTest.java ├── newim-gateway ├── bin │ ├── copy.sh │ ├── scpfile.sh │ └── start.sh ├── newim-gateway.iml ├── pom.xml └── src │ ├── main │ ├── assembly │ │ └── gateway.xml │ ├── java │ │ └── com │ │ │ └── whosbean │ │ │ └── newim │ │ │ ├── GatewayMain.java │ │ │ └── gateway │ │ │ ├── GatewayConfig.java │ │ │ ├── GatewayServerNode.java │ │ │ ├── connection │ │ │ ├── ChannelsHolder.java │ │ │ └── WebSession.java │ │ │ ├── exchange │ │ │ └── MessageExchangeHandler.java │ │ │ └── handler │ │ │ ├── ChannelAttributes.java │ │ │ ├── HttpSessionHandler.java │ │ │ ├── WsConnectedHandler.java │ │ │ ├── WsMessageHandler.java │ │ │ └── WsServerProtocolHandler.java │ └── resources │ │ ├── gateway.yaml │ │ ├── logback.xml │ │ ├── redis.yaml │ │ ├── spring-gateway.xml │ │ └── zookeeper.yaml │ └── test │ └── java │ └── com │ └── whosbean │ └── AppTest.java ├── newim-pb ├── newim-pb.iml ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── whosbean │ │ │ └── newim │ │ │ └── entity │ │ │ ├── ChatMessage.java │ │ │ ├── ChatMessageOrBuilder.java │ │ │ ├── ChatStatus.java │ │ │ ├── ChatStatusOrBuilder.java │ │ │ ├── EntitySchema.java │ │ │ ├── ExchangeMessage.java │ │ │ └── ExchangeMessageOrBuilder.java │ ├── pb │ │ ├── entity_schema.pb.cc │ │ ├── entity_schema.pb.h │ │ └── entity_schema.proto │ └── resources │ │ ├── redis.yaml │ │ └── zookeeper.yaml │ └── test │ └── java │ └── com │ └── whosbean │ └── AppTest.java ├── pom.xml └── src ├── main └── java │ └── com │ └── whosbean │ └── App.java └── test └── java └── com └── whosbean └── AppTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .gitignore support plugin (hsz.mobi) 2 | ### Java template 3 | *.class 4 | 5 | # Mobile Tools for Java (J2ME) 6 | .mtj.tmp/ 7 | 8 | # Package Files # 9 | *.jar 10 | *.war 11 | *.ear 12 | 13 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 14 | hs_err_pid* 15 | logs 16 | target 17 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | NewIM -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/dictionaries/zhza4586.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__aopalliance_aopalliance_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__ch_qos_logback_logback_classic_1_0_13.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__ch_qos_logback_logback_core_1_0_13.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_google_guava_guava_15_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_google_protobuf_protobuf_java_2_5_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_ning_async_http_client_1_9_0_BETA23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__commons_logging_commons_logging_1_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__commons_pool_commons_pool_1_6.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__io_netty_netty_3_9_5_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__io_netty_netty_all_4_0_23_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__jline_jline_0_9_94.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__junit_junit_3_8_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_commons_commons_lang3_3_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_curator_curator_client_2_6_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_curator_curator_framework_2_6_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_curator_curator_recipes_2_6_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_zookeeper_zookeeper_3_4_6.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_slf4j_jul_to_slf4j_1_5_11.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_aop_4_0_0_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_beans_4_0_0_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_context_4_0_0_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_core_4_0_0_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_expression_4_0_0_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_yaml_snakeyaml_1_13.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__redis_clients_jedis_2_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Abstraction issues 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /NewIM.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NewIM 2 | ===== 3 | 4 | This is a simple NewIM, created for Mobile App. 5 | by using Netty + WebSocket, Zookeeper, Redis and Protobuf. 6 | 7 | ### Gateway 8 | this node receives connections from Mobile and holds them, receives message from Mobile and then deliver the message to Chat Node. 9 | It uses zookeeper to store connection info. 10 | 11 | ### Chat 12 | this node routes message to all those mobile client in P2P, P2G way. 13 | it uses Zookeeper to get connections on Gateway node. 14 | 15 | ### Zookeeper 16 | it is a No 1 component in NewIM. it can deliver updates to clients in less than a second and support HA. 17 | so it is a Keeper. 18 | 19 | ### Redis 20 | this is where message save when Gateway got those messages. 21 | 22 | 23 | ### Protobuf Definition 24 | App talks to Gateway with Protobuf, Gateway sends messages to App with Protobuf. 25 | Gateway talks to Chat Node with Protobuf. 26 | 27 | ``` 28 | message ChatMessage { 29 | required string boxid = 1; 30 | required int32 group = 2 [default = 0]; 31 | optional string uuid = 3; 32 | optional string sender = 4; 33 | optional string receiver = 5; 34 | optional string body = 6; 35 | enum MessageType { 36 | SmallText = 0; 37 | LongText = 1; 38 | AUDIO = 2; 39 | IMAGE = 3; 40 | VIDEO = 4; 41 | FILE = 5; 42 | LINK = 6; 43 | SYNC = 7; 44 | } 45 | required MessageType mtype = 7 [default = SYNC]; 46 | 47 | enum ChatOp { 48 | JOIN = 0; 49 | QUIT = 1; 50 | CHAT = 2; 51 | ACK = 3; 52 | } 53 | required ChatOp op = 8 [default = ACK]; 54 | } 55 | 56 | message ChatStatus { 57 | required string sender = 1; 58 | required int32 syncMark = 2; 59 | } 60 | 61 | message ExchangeMessage { 62 | required string messageId = 1; 63 | optional string message = 2; 64 | optional string chatPath = 3; 65 | optional string chatRoomId = 4; 66 | optional string msgPath = 5; 67 | repeated int32 channelId = 6; 68 | } 69 | 70 | ``` 71 | 72 | ### TODO 73 | 1. receive image, audio, video file from clients 74 | 2. support node monitor -------------------------------------------------------------------------------- /demo/demo.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /demo/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | newim 5 | com.whosbean 6 | 1.0-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | newim-demo 11 | jar 12 | 13 | newim-demo 14 | http://maven.apache.org 15 | 16 | 17 | UTF-8 18 | 19 | 20 | 21 | 22 | junit 23 | junit 24 | 3.8.1 25 | test 26 | 27 | 28 | com.ning 29 | async-http-client 30 | 1.9.0-BETA23 31 | 32 | 33 | com.whosbean 34 | newim-pb 35 | 1.0-SNAPSHOT 36 | 37 | 38 | com.google.protobuf 39 | protobuf-java 40 | 2.5.0 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /demo/src/main/java/com/whosbean/App.java: -------------------------------------------------------------------------------- 1 | package com.whosbean; 2 | 3 | import com.whosbean.newim.entity.ChatMessage; 4 | 5 | /** 6 | * Hello world! 7 | * 8 | */ 9 | public class App 10 | { 11 | 12 | public static final String BOXID = "123"; 13 | private static ChatClient cc0; 14 | private static ChatClient cc1; 15 | 16 | public static void main( String[] args ) throws Exception { 17 | System.out.println( "Hello World!" ); 18 | 19 | cc0 = new ChatClient("john-1", "ws://localhost:8180/websocket"); 20 | cc0.start(); 21 | 22 | cc1 = new ChatClient("john-2", "ws://localhost:8180/websocket"); 23 | cc1.start(); 24 | 25 | System.out.println("to join chat room: " + BOXID); 26 | 27 | cc0.join(BOXID); 28 | cc1.join(BOXID); 29 | 30 | System.out.println("to send message: " + BOXID); 31 | 32 | ChatMessage.Builder message = ChatMessage.newBuilder(); 33 | message.setBoxid(BOXID); 34 | message.setBody("Chat Message Body"); 35 | message.setSender("john-1"); 36 | message.setReceiver("john-2"); 37 | message.setGroup(0); 38 | message.setOp(ChatMessage.ChatOp.CHAT); 39 | message.setMtype(ChatMessage.MessageType.SmallText); 40 | 41 | cc0.send(message.build()); 42 | 43 | Thread.sleep(30 * 1000); 44 | 45 | System.out.println("DONE OK."); 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /demo/src/main/java/com/whosbean/ChatClient.java: -------------------------------------------------------------------------------- 1 | package com.whosbean; 2 | 3 | import com.ning.http.client.AsyncHttpClient; 4 | import com.ning.http.client.AsyncHttpClientConfig; 5 | import com.ning.http.client.cookie.Cookie; 6 | import com.ning.http.client.websocket.WebSocket; 7 | import com.ning.http.client.websocket.WebSocketByteListener; 8 | import com.ning.http.client.websocket.WebSocketUpgradeHandler; 9 | import com.whosbean.newim.entity.ChatMessage; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.IOException; 14 | import java.util.concurrent.CountDownLatch; 15 | import java.util.concurrent.ExecutionException; 16 | 17 | /** 18 | * Created by Yaming on 2014/10/17. 19 | */ 20 | public class ChatClient extends Thread { 21 | 22 | protected Logger logger = null; 23 | 24 | private String url; 25 | private String uname; 26 | private AsyncHttpClient c; 27 | private WebSocket connection; 28 | 29 | private CountDownLatch countDownLatch = null; 30 | 31 | public ChatClient(String name, String url) { 32 | this.uname = name; 33 | this.url = url; 34 | logger = LoggerFactory.getLogger(ChatClient.class.getName()+"."+name); 35 | AsyncHttpClientConfig cf = new AsyncHttpClientConfig.Builder().build(); 36 | c = new AsyncHttpClient(cf); 37 | } 38 | 39 | public void lostConnection(){ 40 | 41 | } 42 | 43 | public void close(){ 44 | if(connection.isOpen()){ 45 | System.out.println("closing websocket"); 46 | connection.close(); 47 | connection = null; 48 | System.out.println("websocket closed"); 49 | } 50 | } 51 | 52 | public void join(String boxid) throws Exception { 53 | if (countDownLatch != null){ 54 | countDownLatch.await(); 55 | } 56 | 57 | ChatMessage.Builder chatbox = ChatMessage.newBuilder(); 58 | chatbox.setBoxid(boxid); 59 | chatbox.setOp(ChatMessage.ChatOp.JOIN); 60 | chatbox.setSender(this.uname); 61 | chatbox.setGroup(0); 62 | chatbox.setMtype(ChatMessage.MessageType.SYNC); 63 | connection.sendMessage(chatbox.build().toByteArray()); 64 | } 65 | 66 | public void quit(String boxid) throws Exception { 67 | if (countDownLatch != null){ 68 | countDownLatch.await(); 69 | } 70 | 71 | ChatMessage.Builder chatbox = ChatMessage.newBuilder(); 72 | chatbox.setBoxid(boxid); 73 | chatbox.setOp(ChatMessage.ChatOp.QUIT); 74 | chatbox.setSender(this.uname); 75 | chatbox.setMtype(ChatMessage.MessageType.SYNC); 76 | connection.sendMessage(chatbox.build().toByteArray()); 77 | } 78 | 79 | public void send(ChatMessage chatMessage) throws Exception { 80 | if (countDownLatch != null){ 81 | countDownLatch.await(); 82 | } 83 | logger.info("to send message. msg=" + chatMessage); 84 | connection.sendMessage(chatMessage.toByteArray()); 85 | } 86 | 87 | @Override 88 | public void run() { 89 | try { 90 | 91 | connect(); 92 | 93 | logger.info("websocket connected."); 94 | while (true){ 95 | Thread.sleep(1000); 96 | } 97 | 98 | } catch (InterruptedException e) { 99 | e.printStackTrace(); 100 | } catch (ExecutionException e) { 101 | e.printStackTrace(); 102 | } catch (IOException e) { 103 | e.printStackTrace(); 104 | } 105 | 106 | } 107 | 108 | private final WebSocketByteListener webSocketByteListener = new WebSocketByteListener() { 109 | 110 | @Override 111 | public void onMessage(byte[] message) { 112 | try { 113 | ChatMessage chatMessage = ChatMessage.parseFrom(message); 114 | logger.info("onMessage: {}", chatMessage); 115 | } catch (IOException e) { 116 | e.printStackTrace(); 117 | } 118 | } 119 | 120 | @Override 121 | public void onOpen(WebSocket websocket) { 122 | connection = websocket; 123 | countDownLatch.countDown(); 124 | countDownLatch = null; 125 | //byte[] message = null; 126 | //websocket.sendMessage(message); 127 | logger.info("onOpen: {}", websocket); 128 | } 129 | 130 | @Override 131 | public void onClose(WebSocket websocket) { 132 | logger.info("onClose: {}", websocket); 133 | lostConnection(); 134 | } 135 | 136 | @Override 137 | public void onError(Throwable t) { 138 | logger.error("onError: ", t); 139 | } 140 | 141 | }; 142 | 143 | public void connect() throws InterruptedException, ExecutionException, IOException { 144 | countDownLatch = new CountDownLatch(1); 145 | Cookie cookie = Cookie.newValidCookie("sid", uname, "127.0.0.1", uname, "/", Integer.MAX_VALUE, 0, false, true); 146 | WebSocket websocket = c.prepareGet(this.url).addCookie(cookie) 147 | .execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener( 148 | webSocketByteListener).build()).get(); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /demo/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | utf-8 5 | 6 | 7 | %date [%thread] %-5level %logger{50} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | true 15 | UTF-8 16 | 17 | 18 | logs/demo.%d{yyyy-MM-dd}.log 19 | 20 | 21 | 30 22 | 23 | 24 | %d{HH:mm:ss} [%thread] %-5level %logger{80} - %msg%n 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /demo/src/test/java/com/whosbean/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.whosbean; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /newim-chat/bin/copy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | source /etc/profile 4 | export LC_ALL=C 5 | 6 | folder="$1" 7 | name="$2" 8 | dest="$3" 9 | 10 | echo "$folder" 11 | echo "$name" 12 | echo "$dest" 13 | 14 | user="$4" 15 | host="$5" 16 | 17 | # e.g. /data/in/zhenai/t_fw_00005/20140918 18 | cd "$folder" 19 | scp -q "$name" "$user"@"$host":"$dest" 20 | 21 | # e.g. /data/in/zhenai/t_fw_00005/20140918 22 | # to /data/up/zhenai/t_fw_00005/20140918 23 | up="${folder/\/in\///up/}" 24 | echo "up=$up" 25 | mkdir -p "$up" 26 | 27 | touch "$name.check" 28 | check="${dest/\/in//check}" 29 | echo "check=$check" 30 | scp -q "$name.check" "$user"@"$host":"$check" 31 | 32 | rm -f "$name.check" 33 | 34 | mv "$name" "$up" 35 | 36 | echo "SUCCESS" -------------------------------------------------------------------------------- /newim-chat/bin/scpfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source /etc/profile 4 | export LC_ALL=zh_CN.utf8 5 | 6 | function scpfile() 7 | { 8 | local source_file="$1/$2" 9 | local source_name="$2" 10 | local target_dir="$3" 11 | local target_name="$4" 12 | 13 | #判断是否存在源文件,存在则建立check文件 14 | if [ -f "$source_file" ]; then 15 | touch "$source_file.check" 16 | if [ $? -eq 0 ]; then 17 | scp -q "$source_file" "$target_dir/in/$target_name" 18 | if [ $? -eq 0 ]; then 19 | scp -q "$source_file.check" "$target_dir/check/$target_name.check" 20 | rm "$source_file.check" 21 | [ $? -eq 0 ] && return 0 || return 1 22 | fi 23 | else 24 | return 1 25 | fi 26 | else 27 | return 1 28 | fi 29 | } 30 | 31 | #参数 source_file: 源文件目录 32 | #参数 source_name: 源文件名称 33 | #在上传成功后,把文件移动到上传成功目录 34 | function mvfile() 35 | { 36 | local source_file="$1" 37 | local source_name="$2" 38 | 39 | upfolder="${source_file/\/in\///up/}" 40 | mkdir -p "$upfolder" 41 | 42 | mv "$source_file/$source_name" "$upfolder" 43 | 44 | } 45 | 46 | function main() 47 | { 48 | local src_file="$1" 49 | local src_name="$2" 50 | local tar_dir="$3" 51 | local tar_name="$4" 52 | local user="$5" 53 | local tar_ip="$6" 54 | 55 | scpfile "$src_file" "$src_name" "$user@$tar_ip:$tar_dir" "$tar_name" 56 | ret=$? 57 | echo "$ret" 58 | 59 | if [ $ret -eq 0 ]; then 60 | mvfile "$src_file" "$src_name" 61 | echo "SUCCESS" 62 | else 63 | echo "FAILED" 64 | fi 65 | 66 | [ $ret -eq 0 ] && return 0 || return 1 67 | } 68 | 69 | main "$1" "$2" "$3" "$4" "$5" "$6" 70 | -------------------------------------------------------------------------------- /newim-chat/bin/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #------------------------------------------------------------------- 4 | # Mb Bootstrap Script 5 | #------------------------------------------------------------------- 6 | 7 | # find Mb home. 8 | CURR_DIR=`pwd` 9 | RESV_HOME=`pwd` 10 | 11 | echo $CURR_DIR 12 | cd $CURR_DIR 13 | 14 | if [ -z "$RESV_HOME" ] ; then 15 | echo 16 | echo Must set RESV_HOME 17 | echo 18 | exit 1 19 | fi 20 | 21 | 22 | for i in $RESV_HOME/lib/*.jar; do 23 | CLASSPATH=$i:$CLASSPATH; 24 | done 25 | 26 | CLASSPATH=$RESV_HOME/classes:$CLASSPATH 27 | 28 | DEBUG_INFO=" -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n" 29 | DEBUG="" 30 | 31 | MAIN_CLASS="bin"; 32 | DEFAULT_OPTS="-server -Xms8G -Xmx8G -Xmn1G -XX:PermSize=50M -XX:MaxPermSize=50M -Xss256k -Dio.netty.leakDetectionLevel=advanced" ; 33 | 34 | 35 | DEFAULT_OPTS="$DEFAULT_OPTS -XX:+DisableExplicitGC -XX:SurvivorRatio=1 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:LargePageSizeInBytes=128M -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -Dfile.encoding=UTF-8" 36 | DEFAULT_OPTS="$DEFAULT_OPTS -DMB.home=\"$RESV_HOME\"" 37 | 38 | echo java $DEBUG $DEFAULT_OPTS -classpath $CLASSPATH $MAIN_CLASS 39 | java $DEBUG $DEFAULT_OPTS -classpath $CLASSPATH $MAIN_CLASS & -------------------------------------------------------------------------------- /newim-chat/newim-chat.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /newim-chat/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | newim 5 | com.whosbean 6 | 1.0-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | newim-chat 11 | jar 12 | 13 | newim-chat 14 | http://maven.apache.org 15 | 16 | 17 | UTF-8 18 | 19 | 20 | 21 | 22 | junit 23 | junit 24 | 3.8.1 25 | test 26 | 27 | 28 | com.whosbean 29 | newim-common 30 | 1.0-SNAPSHOT 31 | 32 | 33 | 34 | 35 | 36 | 37 | maven-compiler-plugin 38 | 39 | 1.6 40 | 1.6 41 | 42 | 43 | 44 | maven-assembly-plugin 45 | 2.2-beta-5 46 | 47 | 48 | src/main/assembly/chatter.xml 49 | 50 | 55 | 56 | 57 | 58 | make-assembly 59 | package 60 | 61 | single 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /newim-chat/src/main/assembly/chatter.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | server 6 | 7 | zip 8 | 9 | false 10 | 11 | 12 | false 13 | runtime 14 | lib 15 | 16 | *:sources 17 | org.apache.hadoop:* 18 | org.apache.mahout:* 19 | 20 | 21 | 22 | true 23 | 24 | org.apache.mahout:* 25 | 26 | 27 | 28 | 29 | 30 | ${basedir}/target/classes 31 | /classes 32 | 33 | *.jar 34 | 35 | 36 | 37 | ${basedir}/bin 38 | / 39 | 40 | *.jar 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /newim-chat/src/main/java/com/whosbean/newim/ChatterMain.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim; 2 | 3 | import com.whosbean.newim.chatter.ChatterConfig; 4 | import com.whosbean.newim.server.ServerStarter; 5 | import org.springframework.context.support.ClassPathXmlApplicationContext; 6 | import org.springframework.util.Assert; 7 | 8 | /** 9 | * Created by yaming_deng on 14-9-9. 10 | */ 11 | public class ChatterMain implements ServerStarter { 12 | 13 | public class ServerThread extends Thread { 14 | 15 | @Override 16 | public void run() { 17 | while (true){ 18 | try { 19 | Thread.sleep(600 * 1000); 20 | } catch (InterruptedException e) { 21 | e.printStackTrace(); 22 | } 23 | } 24 | } 25 | } 26 | 27 | /** 28 | * 启动推送服务 8080端口 29 | */ 30 | public void start() { 31 | ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-chatter.xml"); 32 | ChatterConfig prop = context.getBean("chatterConfig", ChatterConfig.class); 33 | Assert.notNull(prop, "chatterConfig bean is NULL."); 34 | new ServerThread().start(); 35 | System.out.println("Chatter Node start."); 36 | } 37 | 38 | /** 39 | * 入口 40 | * 41 | * @param args 42 | */ 43 | public static void main(String[] args) { 44 | new ChatterMain().start(); 45 | } 46 | } -------------------------------------------------------------------------------- /newim-chat/src/main/java/com/whosbean/newim/chatter/ChatterConfig.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.chatter; 2 | 3 | import com.whosbean.newim.config.AbstractConfig; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by yaming_deng on 2014/9/28. 10 | */ 11 | @Component 12 | public class ChatterConfig extends AbstractConfig { 13 | 14 | public static ChatterConfig current = null; 15 | 16 | private String host; 17 | private Integer port; 18 | private String sig; 19 | 20 | @Override 21 | public void afterPropertiesSet() throws Exception { 22 | super.afterPropertiesSet(); 23 | current = this; 24 | this.initGatewayInfo(); 25 | } 26 | 27 | @Override 28 | public String getConfName() { 29 | return "chatter"; 30 | } 31 | 32 | private void initGatewayInfo(){ 33 | Map server = this.get(Map.class, "server"); 34 | host = (String)server.get("ip"); 35 | port = (Integer)server.get("port"); 36 | sig = host+":"+ port; 37 | } 38 | 39 | public String getHost() { 40 | return host; 41 | } 42 | 43 | public Integer getPort() { 44 | return port; 45 | } 46 | 47 | public String getSig() { 48 | return sig; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /newim-chat/src/main/java/com/whosbean/newim/chatter/RouterServerNode.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.chatter; 2 | 3 | import com.whosbean.newim.server.ServerNode; 4 | import com.whosbean.newim.server.ServerNodeRoles; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created by yaming_deng on 14-9-9. 11 | */ 12 | @Component 13 | public class RouterServerNode extends ServerNode { 14 | 15 | public static RouterServerNode current = null; 16 | 17 | @Override 18 | public void afterPropertiesSet() throws Exception { 19 | super.afterPropertiesSet(); 20 | current = this; 21 | } 22 | 23 | @Override 24 | protected String getRole() { 25 | return ServerNodeRoles.ROLE_ROUTER; 26 | } 27 | 28 | @Override 29 | protected String getName() { 30 | return ChatterConfig.current.getSig(); 31 | } 32 | 33 | @Override 34 | protected String getConf() { 35 | return ChatterConfig.current.getSig(); 36 | } 37 | 38 | public List getExchangeServer() throws Exception { 39 | return this.getServers(ServerNodeRoles.ROLE_EXCHANGE); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /newim-chat/src/main/java/com/whosbean/newim/chatter/exchange/ExchangeClient.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.chatter.exchange; 2 | 3 | import com.whosbean.newim.entity.ExchangeMessage; 4 | import io.netty.bootstrap.Bootstrap; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.*; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.SocketChannel; 9 | import io.netty.channel.socket.nio.NioSocketChannel; 10 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 11 | import io.netty.handler.codec.LengthFieldPrepender; 12 | import io.netty.handler.codec.bytes.ByteArrayDecoder; 13 | import io.netty.handler.codec.bytes.ByteArrayEncoder; 14 | import io.netty.util.concurrent.Future; 15 | import io.netty.util.concurrent.GenericFutureListener; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | /** 20 | * Created by yaming_deng on 14-9-9. 21 | */ 22 | public class ExchangeClient { 23 | 24 | protected Logger logger = null; 25 | 26 | private volatile boolean enabled; 27 | private String host; 28 | private Integer port; 29 | 30 | private final Bootstrap b = new Bootstrap(); // (1) 31 | private NioEventLoopGroup workerGroup; 32 | 33 | public ExchangeClient(String host, Integer port) { 34 | this.host = host; 35 | this.port = port; 36 | this.logger = LoggerFactory.getLogger(this.getClass().getName()+"."+host+"."+port); 37 | this.enabled = true; 38 | } 39 | 40 | public boolean isEnabled() { 41 | return enabled; 42 | } 43 | 44 | public void setEnabled(boolean enabled) { 45 | this.enabled = enabled; 46 | } 47 | 48 | public class ClientConnectHandler extends ChannelInboundHandlerAdapter { 49 | 50 | @Override 51 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 52 | logger.info("channelActive: " + ctx.channel()); 53 | 54 | } 55 | 56 | @Override 57 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 58 | String jsonString = new String((byte[])msg); 59 | logger.info("channelRead: " + ctx.channel() + " --> " + jsonString); 60 | ctx.fireChannelRead(msg); 61 | } 62 | 63 | @Override 64 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 65 | cause.printStackTrace(); 66 | ctx.close(); 67 | 68 | } 69 | 70 | @Override 71 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 72 | logger.info("channelInactive: " + ctx.channel() + ", "); 73 | //channelList.remove(ctx.channel()); 74 | ctx.fireChannelInactive(); 75 | 76 | } 77 | 78 | } 79 | 80 | public void start(){ 81 | 82 | Thread thread = new Thread(new Runnable() { 83 | @Override 84 | public void run() { 85 | logger.info("Start Exchange Client. host=" + host+":"+port); 86 | startConnect(); 87 | logger.info("DONE Start Exchange Client. host=" + host+":"+port); 88 | } 89 | }); 90 | 91 | thread.start(); 92 | } 93 | 94 | private void startConnect(){ 95 | workerGroup = new NioEventLoopGroup(); 96 | try { 97 | b.group(workerGroup); // (2) 98 | b.channel(NioSocketChannel.class); // (3) 99 | b.option(ChannelOption.SO_KEEPALIVE, true); // (4) 100 | b.option(ChannelOption.TCP_NODELAY, true); 101 | b.handler(new ChannelInitializer() { 102 | @Override 103 | public void initChannel(SocketChannel ch) throws Exception { 104 | ChannelPipeline pipeline = ch.pipeline(); 105 | pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)); 106 | pipeline.addLast("bytesDecoder",new ByteArrayDecoder()); 107 | 108 | pipeline.addLast("frameEncoder", new LengthFieldPrepender(4, false)); 109 | pipeline.addLast("bytesEncoder", new ByteArrayEncoder()); 110 | 111 | pipeline.addLast("handler", new ClientConnectHandler()); 112 | } 113 | }); 114 | 115 | } catch (Exception e){ 116 | e.printStackTrace(); 117 | workerGroup.shutdownGracefully(); 118 | } 119 | } 120 | 121 | public void send(final ExchangeMessage message) throws Exception { 122 | if (!this.enabled){ 123 | throw new Exception("Exchange Client has been disabled. host=" + host + ":" + port); 124 | } 125 | 126 | postSent(message, message.toByteArray(), 3); 127 | 128 | } 129 | 130 | private void postSent(final ExchangeMessage message, final byte[] bytes, final int trylimit) { 131 | final ChannelFuture f = b.connect(host, port); // (5) 132 | f.addListener(new GenericFutureListener>() { 133 | @Override 134 | public void operationComplete(Future future) throws Exception { 135 | if(future.cause() != null){ 136 | future.cause().printStackTrace(); 137 | }else { 138 | final Channel c = f.channel(); 139 | final ByteBuf data = c.config().getAllocator().buffer(bytes.length); // (2) 140 | data.writeBytes(bytes); 141 | ChannelFuture cf = c.writeAndFlush(data); 142 | cf.addListener(new GenericFutureListener>() { 143 | @Override 144 | public void operationComplete(Future future) throws Exception { 145 | if (future.cause() != null) { 146 | logger.error("发送消息错误. host=" + host + ":" + port + ", messageId=" + message.getMessageId(), future.cause()); 147 | c.close(); 148 | if (trylimit > 0){ 149 | postSent(message, bytes, trylimit - 1); 150 | } 151 | }else{ 152 | logger.info("发送消息成功. messageId=" + message.getMessageId()); 153 | } 154 | } 155 | }); 156 | } 157 | } 158 | }); 159 | 160 | } 161 | 162 | public void stop(){ 163 | workerGroup.shutdownGracefully(); 164 | logger.info("Stop Exchange Client. host=" + host+":"+port); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /newim-chat/src/main/java/com/whosbean/newim/chatter/exchange/ExchangeClientManager.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.chatter.exchange; 2 | 3 | import com.whosbean.newim.chatter.RouterServerNode; 4 | import com.whosbean.newim.zookeeper.ZKPaths; 5 | import org.apache.curator.framework.api.CuratorWatcher; 6 | import org.apache.zookeeper.WatchedEvent; 7 | import org.apache.zookeeper.Watcher; 8 | import org.apache.zookeeper.data.Stat; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.DisposableBean; 12 | import org.springframework.beans.factory.InitializingBean; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.stereotype.Component; 15 | 16 | import java.nio.charset.Charset; 17 | import java.util.Iterator; 18 | import java.util.List; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | import java.util.concurrent.ConcurrentSkipListSet; 21 | 22 | /** 23 | * http://blog.sina.com.cn/s/blog_616e189f01018axz.html 24 | * Created by yaming_deng on 14-9-9. 25 | */ 26 | @Component 27 | public class ExchangeClientManager implements InitializingBean, DisposableBean { 28 | 29 | protected Logger logger = LoggerFactory.getLogger(this.getClass()); 30 | 31 | public static ExchangeClientManager instance; 32 | 33 | @Autowired 34 | private RouterServerNode routerServerNode; 35 | 36 | private ConcurrentSkipListSet servers = new ConcurrentSkipListSet(); 37 | 38 | private ConcurrentHashMap clients = new ConcurrentHashMap(); 39 | 40 | @Override 41 | public void destroy() throws Exception { 42 | Iterator itor = clients.keySet().iterator(); 43 | while (itor.hasNext()){ 44 | String name = itor.next(); 45 | ExchangeClient client = clients.get(name); 46 | if (client != null){ 47 | client.stop(); 48 | } 49 | } 50 | } 51 | 52 | public class ServerNodeWatcher implements CuratorWatcher { 53 | 54 | private final String path; 55 | 56 | public String getPath() { 57 | return path; 58 | } 59 | 60 | public ServerNodeWatcher(String path) { 61 | this.path = path; 62 | } 63 | 64 | @Override 65 | public void process(WatchedEvent event) throws Exception { 66 | logger.info("process Event: {}", event); 67 | if(event.getType() == Watcher.Event.EventType.NodeDataChanged){ 68 | byte[] data = null; 69 | System.out.println(path+":"+new String(data, Charset.forName("utf-8"))); 70 | }else if(event.getType() == Watcher.Event.EventType.NodeChildrenChanged){ 71 | List list = routerServerNode.getExchangeServer(); 72 | //compare 73 | for (String item : list){ 74 | if (servers.contains(item)){ 75 | continue; 76 | } 77 | logger.info("New Server Found. " + item); 78 | newExchangeClient(item); 79 | } 80 | for (String item : servers){ 81 | if (list.contains(item)){ 82 | continue; 83 | } 84 | logger.info("Server was Removed. " + item); 85 | removeExchangeClient(item); 86 | } 87 | servers.clear(); 88 | servers.addAll(list); 89 | } 90 | } 91 | 92 | } 93 | 94 | public class ListenThread extends Thread{ 95 | 96 | @Override 97 | public void run() { 98 | Stat stat = null; 99 | while (stat == null){ 100 | try { 101 | String path = ZKPaths.NS_ROOT + ZKPaths.PATH_SERVERS; 102 | stat = routerServerNode.getZkClient() 103 | .checkExists() 104 | .usingWatcher(new ServerNodeWatcher(path)) 105 | .forPath(path); 106 | if (stat != null){ 107 | List list = routerServerNode.getExchangeServer(); 108 | for (String host : list){ 109 | newExchangeClient(host); 110 | } 111 | servers.addAll(list); 112 | break; 113 | }else{ 114 | logger.info("wait for path. " + path); 115 | Thread.sleep(1 * 1000); 116 | } 117 | } catch (Exception e) { 118 | e.printStackTrace(); 119 | } 120 | } 121 | } 122 | } 123 | 124 | protected void newExchangeClient(String host){ 125 | String[] temp = host.split(":"); 126 | ExchangeClient client = new ExchangeClient(temp[0], Integer.parseInt(temp[1])); 127 | client.start(); 128 | logger.info("newExchangeClient. host=" + host); 129 | this.clients.put(host, client); 130 | } 131 | 132 | protected void removeExchangeClient(String host){ 133 | ExchangeClient client = this.clients.get(host); 134 | if (client != null) { 135 | client.setEnabled(false); 136 | } 137 | this.clients.remove(host); 138 | } 139 | 140 | @Override 141 | public void afterPropertiesSet() throws Exception { 142 | instance = this; 143 | new ListenThread().start(); 144 | logger.info("ExchangeClientManager start."); 145 | } 146 | 147 | public ExchangeClient find(String host){ 148 | ExchangeClient client = this.clients.get(host); 149 | return client; 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /newim-chat/src/main/java/com/whosbean/newim/chatter/router/BoxMsgListenThread.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.chatter.router; 2 | 3 | import com.whosbean.newim.chatter.RouterServerNode; 4 | import com.whosbean.newim.zookeeper.ZKPaths; 5 | import org.apache.curator.framework.api.CuratorWatcher; 6 | import org.apache.zookeeper.CreateMode; 7 | import org.apache.zookeeper.WatchedEvent; 8 | import org.apache.zookeeper.Watcher; 9 | import org.apache.zookeeper.data.Stat; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 13 | 14 | import java.nio.charset.Charset; 15 | import java.util.List; 16 | 17 | /** 18 | * Created by Yaming on 2014/10/17. 19 | */ 20 | public class BoxMsgListenThread extends Thread implements CuratorWatcher { 21 | 22 | protected Logger logger = null; 23 | 24 | private String boxid; 25 | private RouterServerNode routerServerNode; 26 | private ThreadPoolTaskExecutor executors; 27 | private String path; 28 | 29 | public BoxMsgListenThread(ThreadPoolTaskExecutor executors, String boxid) { 30 | this.boxid = boxid; 31 | this.routerServerNode = RouterServerNode.current; 32 | this.executors = executors; 33 | this.logger = LoggerFactory.getLogger(this.getClass().getName() + "." + boxid); 34 | } 35 | 36 | @Override 37 | public void run() { 38 | this.path = ZKPaths.NS_ROOT + ZKPaths.PATH_INBOX + "/" + boxid; 39 | 40 | try { 41 | hook(path); 42 | } catch (Exception e) { 43 | e.printStackTrace(); 44 | } 45 | 46 | 47 | while (true){ 48 | try { 49 | Thread.sleep(3600 * 1000); 50 | } catch (InterruptedException e) { 51 | e.printStackTrace(); 52 | } 53 | } 54 | } 55 | 56 | private void hook(final String path) throws Exception { 57 | Stat stat = routerServerNode.getZkClient() 58 | .checkExists().forPath(path); 59 | if (stat == null){ 60 | routerServerNode.getZkClient().create() 61 | .creatingParentsIfNeeded() 62 | .withMode(CreateMode.PERSISTENT_SEQUENTIAL).forPath(path); 63 | } 64 | 65 | routerServerNode.getZkClient() 66 | .getChildren().usingWatcher(this).forPath(path); 67 | } 68 | 69 | @Override 70 | public void process(WatchedEvent event) throws Exception { 71 | logger.info("process Event: {}", event); 72 | if(event.getType() == Watcher.Event.EventType.NodeDataChanged){ 73 | String data = getData(event.getPath()); 74 | logger.info(path + ":" + data); 75 | }else if(event.getType() == Watcher.Event.EventType.NodeChildrenChanged){ 76 | List childs = routerServerNode.getZkClient() 77 | .getChildren().usingWatcher(this).forPath(path); 78 | logger.info("Found Message. total={}", childs.size()); 79 | for(String seqid : childs){ 80 | executors.submit(new NewMessageNotifyThread(routerServerNode, boxid, seqid)); 81 | } 82 | } 83 | } 84 | 85 | public String getData(String path) throws Exception { 86 | byte[] bytes = routerServerNode.getZkClient().getData().forPath(path); 87 | return new String(bytes, Charset.forName("utf-8")); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /newim-chat/src/main/java/com/whosbean/newim/chatter/router/ChatMessageListener.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.chatter.router; 2 | 3 | import com.whosbean.newim.chatter.RouterServerNode; 4 | import com.whosbean.newim.chatter.exchange.ExchangeClientManager; 5 | import com.whosbean.newim.zookeeper.ZKPaths; 6 | import org.apache.curator.framework.api.CuratorWatcher; 7 | import org.apache.zookeeper.CreateMode; 8 | import org.apache.zookeeper.KeeperException; 9 | import org.apache.zookeeper.WatchedEvent; 10 | import org.apache.zookeeper.Watcher; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.beans.factory.DisposableBean; 14 | import org.springframework.beans.factory.InitializingBean; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 17 | import org.springframework.stereotype.Component; 18 | 19 | import java.nio.charset.Charset; 20 | import java.util.List; 21 | import java.util.concurrent.ConcurrentHashMap; 22 | 23 | /** 24 | * Created by Yaming on 2014/10/14. 25 | */ 26 | @Component 27 | public class ChatMessageListener implements InitializingBean, DisposableBean { 28 | 29 | protected Logger logger = LoggerFactory.getLogger(this.getClass()); 30 | 31 | public static ChatMessageListener current; 32 | 33 | @Autowired 34 | private RouterServerNode routerServerNode; 35 | 36 | @Autowired 37 | private ExchangeClientManager exchangeClientManager; 38 | 39 | private ThreadPoolTaskExecutor executors = null; 40 | private ConcurrentHashMap boxMap = new ConcurrentHashMap(); 41 | 42 | private volatile boolean stopped = false; 43 | 44 | @Override 45 | public void destroy() throws Exception { 46 | stopped = true; 47 | } 48 | 49 | @Override 50 | public void afterPropertiesSet() throws Exception { 51 | int limit = Runtime.getRuntime().availableProcessors() * 2; 52 | executors = new ThreadPoolTaskExecutor(); 53 | executors.setCorePoolSize(limit/5); 54 | executors.setMaxPoolSize(limit); 55 | executors.setWaitForTasksToCompleteOnShutdown(true); 56 | executors.afterPropertiesSet(); 57 | new ChatListenThread().start(); 58 | current = this; 59 | logger.info("ChatMessageListner start."); 60 | } 61 | 62 | public String getData(String path) throws Exception { 63 | byte[] bytes = routerServerNode.getZkClient().getData().forPath(path); 64 | return new String(bytes, Charset.forName("utf-8")); 65 | } 66 | 67 | public void remove(String boxid){ 68 | boxMap.remove(boxid); 69 | logger.info("Box Listener quit. " + boxid); 70 | } 71 | 72 | public class NewMemberWatcher implements CuratorWatcher { 73 | 74 | private final String path; 75 | 76 | public String getPath() { 77 | return path; 78 | } 79 | 80 | public NewMemberWatcher(String path) { 81 | this.path = path; 82 | } 83 | 84 | @Override 85 | public void process(WatchedEvent event) throws Exception { 86 | logger.info("process Event: {}", event); 87 | if(event.getType() == Watcher.Event.EventType.NodeDataChanged){ 88 | String data = getData(event.getPath()); 89 | logger.info(path + ":" + data); 90 | }else if(event.getType() == Watcher.Event.EventType.NodeChildrenChanged){ 91 | List childs = routerServerNode.getZkClient().getChildren().forPath(path); 92 | for(String boxid : childs){ 93 | if (!boxMap.contains(boxid)){ 94 | boxMap.put(boxid, 1); 95 | new BoxMsgListenThread(executors, boxid).start(); 96 | } 97 | } 98 | } 99 | } 100 | 101 | } 102 | 103 | public class ChatListenThread extends Thread{ 104 | 105 | @Override 106 | public void run() { 107 | String path = ZKPaths.NS_ROOT + ZKPaths.PATH_CHATS; 108 | List childs = null; 109 | while (childs == null){ 110 | try { 111 | childs = routerServerNode.getZkClient() 112 | .getChildren() 113 | .usingWatcher(new NewMemberWatcher(path)) 114 | .forPath(path); 115 | if (childs != null){ 116 | logger.info("Found path. {}, size {}", path, childs.size()); 117 | if (childs.size() > 0){ 118 | //start boxMessageListen 119 | for(String boxid : childs){ 120 | boxMap.put(boxid, 1); 121 | new BoxMsgListenThread(executors, boxid).start(); 122 | } 123 | } 124 | break; 125 | }else{ 126 | logger.info("Wait for path. " + path); 127 | Thread.sleep(1 * 1000); 128 | } 129 | } catch (Exception e) { 130 | if (e instanceof KeeperException){ 131 | KeeperException ex = (KeeperException)e; 132 | if (ex.code().equals(KeeperException.Code.NONODE)){ 133 | try { 134 | routerServerNode.getZkClient().create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path); 135 | } catch (Exception e1) { 136 | logger.error(e1.getMessage(), e1); 137 | } 138 | } 139 | }else { 140 | logger.error(e.getMessage(), e); 141 | } 142 | } 143 | } 144 | } 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /newim-chat/src/main/java/com/whosbean/newim/chatter/router/NewMemberNotifyThread.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.chatter.router; 2 | 3 | import com.whosbean.newim.chatter.RouterServerNode; 4 | import com.whosbean.newim.chatter.exchange.ExchangeClient; 5 | import com.whosbean.newim.chatter.exchange.ExchangeClientManager; 6 | import com.whosbean.newim.entity.ExchangeMessage; 7 | import com.whosbean.newim.zookeeper.ZKPaths; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Created by Yaming on 2014/10/14. 15 | */ 16 | public class NewMemberNotifyThread implements Runnable { 17 | 18 | protected Logger logger = LoggerFactory.getLogger(this.getClass()); 19 | 20 | private RouterServerNode routerServerNode; 21 | private String boxid; 22 | private String[] data; 23 | 24 | public NewMemberNotifyThread(RouterServerNode routerServerNode, String boxid, String[] data) { 25 | this.routerServerNode = routerServerNode; 26 | this.boxid = boxid; 27 | this.data = data; 28 | } 29 | 30 | @Override 31 | public void run() { 32 | String path = ZKPaths.getMemberPath(boxid); 33 | String msgId = this.data[this.data.length-1]; 34 | try { 35 | List members = this.routerServerNode.getZkClient().getChildren().forPath(path); 36 | //TODO:too many members then split 37 | ExchangeMessage.Builder message = ExchangeMessage.newBuilder(); 38 | message.setChatRoomId(this.boxid); 39 | for(String host : members){ 40 | ExchangeClient client = ExchangeClientManager.instance.find(host); 41 | message.setMessageId(msgId); 42 | List channels = this.routerServerNode.getZkClient().getChildren().forPath(path + "/" + host); 43 | for(String cid : channels){ 44 | message.addChannelId(new Integer(cid)); 45 | } 46 | message.setChatPath(path + "/" + host); 47 | logger.info("ExchangeClient send message. " + message); 48 | client.send(message.build()); 49 | } 50 | } catch (Exception e) { 51 | e.printStackTrace(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /newim-chat/src/main/java/com/whosbean/newim/chatter/router/NewMessageNotifyThread.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.chatter.router; 2 | 3 | import com.whosbean.newim.chatter.RouterServerNode; 4 | import com.whosbean.newim.chatter.exchange.ExchangeClient; 5 | import com.whosbean.newim.chatter.exchange.ExchangeClientManager; 6 | import com.whosbean.newim.entity.ExchangeMessage; 7 | import com.whosbean.newim.zookeeper.ZKPaths; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.nio.charset.Charset; 12 | import java.util.List; 13 | 14 | /** 15 | * Created by Yaming on 2014/10/14. 16 | */ 17 | public class NewMessageNotifyThread implements Runnable { 18 | 19 | protected Logger logger = LoggerFactory.getLogger(this.getClass()); 20 | 21 | private RouterServerNode routerServerNode; 22 | private String boxid; 23 | private String seqid; 24 | 25 | 26 | public NewMessageNotifyThread(RouterServerNode routerServerNode, String boxid, String seqid) { 27 | this.routerServerNode = routerServerNode; 28 | this.boxid = boxid; 29 | this.seqid = seqid; 30 | } 31 | 32 | @Override 33 | public void run() { 34 | String msgpath = ZKPaths.getInboxPath(boxid, seqid); 35 | String path = ZKPaths.getMemberPath(boxid); 36 | try { 37 | String msgid = this.getData(msgpath); 38 | List members = this.routerServerNode.getZkClient().getChildren().forPath(path); 39 | //TODO:too many members then split 40 | ExchangeMessage.Builder message = ExchangeMessage.newBuilder(); 41 | message.setChatRoomId(this.boxid); 42 | message.setMsgPath(msgpath); 43 | for(String host : members){ 44 | ExchangeClient client = ExchangeClientManager.instance.find(host); 45 | message.setMessageId(msgid); 46 | List channels = this.routerServerNode.getZkClient().getChildren().forPath(path + "/" + host); 47 | for(String cid : channels){ 48 | message.addChannelId(new Integer(cid)); 49 | } 50 | message.setChatPath(boxid + "/" + host); 51 | logger.info("ExchangeClient send message. " + message); 52 | client.send(message.build()); 53 | } 54 | } catch (Exception e) { 55 | e.printStackTrace(); 56 | } 57 | } 58 | 59 | public String getData(String path) throws Exception { 60 | byte[] bytes = routerServerNode.getZkClient().getData().forPath(path); 61 | return new String(bytes, Charset.forName("utf-8")); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /newim-chat/src/main/resources/chatter.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | ip: 127.0.0.1 3 | actors: 10 4 | workers: 10 5 | port: 8180 6 | 7 | bucket: 8 | engine: redis -------------------------------------------------------------------------------- /newim-chat/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | utf-8 5 | 6 | 7 | %date [%thread] %-5level %logger{50} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | true 15 | UTF-8 16 | 17 | 18 | logs/chatter.%d{yyyy-MM-dd}.log 19 | 20 | 21 | 30 22 | 23 | 24 | %d{HH:mm:ss} [%thread] %-5level %logger{80} - %msg%n 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /newim-chat/src/main/resources/redis.yaml: -------------------------------------------------------------------------------- 1 | host: 127.0.0.1 2 | port: 6379 3 | maxActive: 50 4 | maxIdle: 10 5 | maxWait: 5000 -------------------------------------------------------------------------------- /newim-chat/src/main/resources/spring-chatter.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /newim-chat/src/main/resources/zookeeper.yaml: -------------------------------------------------------------------------------- 1 | servers: 127.0.0.1:2181 #,127.0.0.1:2182,127.0.0.l:2183 2 | retry: 3 | interval: 1000 4 | limit: 3 -------------------------------------------------------------------------------- /newim-chat/src/test/java/com/whosbean/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.whosbean; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /newim-common/newim-common.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /newim-common/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | newim 5 | com.whosbean 6 | 1.0-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | newim-common 11 | jar 12 | 13 | newim-common 14 | http://maven.apache.org 15 | 16 | 17 | UTF-8 18 | 19 | 20 | 21 | 22 | junit 23 | junit 24 | 3.8.1 25 | test 26 | 27 | 28 | redis.clients 29 | jedis 30 | 2.2.1 31 | 32 | 33 | com.google.protobuf 34 | protobuf-java 35 | 2.5.0 36 | 37 | 38 | com.whosbean 39 | newim-pb 40 | 1.0-SNAPSHOT 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /newim-common/src/main/java/com/whosbean/newim/common/AppContextHolder.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.common; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.beans.factory.InitializingBean; 5 | import org.springframework.context.ApplicationContext; 6 | import org.springframework.context.ApplicationContextAware; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | * Created by yaming_deng on 14-9-11. 11 | */ 12 | @Component 13 | public class AppContextHolder implements ApplicationContextAware, InitializingBean { 14 | 15 | public static AppContextHolder context; 16 | 17 | private ApplicationContext applicationContext; 18 | 19 | @Override 20 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 21 | this.applicationContext = applicationContext; 22 | } 23 | 24 | 25 | @Override 26 | public void afterPropertiesSet() throws Exception { 27 | context = this; 28 | } 29 | 30 | public T get(Class clazz, String name){ 31 | return this.applicationContext.getBean(name, clazz); 32 | } 33 | 34 | public T get(Class clazz){ 35 | return this.applicationContext.getBean(clazz); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /newim-common/src/main/java/com/whosbean/newim/config/AbstractConfig.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.config; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.InitializingBean; 6 | import org.springframework.util.Assert; 7 | 8 | import java.io.IOException; 9 | import java.util.*; 10 | 11 | /** 12 | * 配置规范如下: 13 | * /resources/$env/$sample.yaml 14 | * /resources/$env/$app/$sample.yaml 15 | * $env=dev,test,prod 16 | * $app=具体的业务, 如elasticsearch, db etc. 17 | * $sample=具体文件名 18 | * User: yamingdeng 19 | * Date: 13-11-25 20 | * Time: 下午9:58 21 | */ 22 | public abstract class AbstractConfig implements InitializingBean{ 23 | protected String cfgFile; 24 | protected Map cfg = null; 25 | protected Logger logger = null; 26 | 27 | protected Logger getLogger(){ 28 | if (logger == null){ 29 | logger = LoggerFactory.getLogger(this.getClass()); 30 | } 31 | return logger; 32 | } 33 | 34 | @Override 35 | public void afterPropertiesSet() throws Exception { 36 | this.cfgFile = this.getConfName(); 37 | Assert.notNull(this.cfgFile, "cfgFile should not be NULL."); 38 | this.load(); 39 | } 40 | 41 | protected synchronized void load() throws IOException { 42 | this.cfgFile = String.format("%s.yaml", this.cfgFile); 43 | if (this.cfg != null){ 44 | return; 45 | } 46 | this.cfg = ConfigYamlManager.load(this.cfgFile, this.getConfName()); 47 | } 48 | 49 | public String getEnvName(){ 50 | return "dev"; 51 | } 52 | 53 | public abstract String getConfName(); 54 | 55 | public boolean isEnabled(){ 56 | return this.cfg != null; 57 | } 58 | 59 | public String getCfgFile() { 60 | return cfgFile; 61 | } 62 | 63 | public void setCfgFile(String cfgFile) { 64 | this.cfgFile = cfgFile; 65 | } 66 | 67 | public void setMap(Map map){ 68 | if (map == null){ 69 | return; 70 | } 71 | if (this.cfg == null){ 72 | this.cfg = new HashMap(); 73 | } 74 | for (String key : map.keySet()){ 75 | this.cfg.put(key, map.get(key)); 76 | } 77 | } 78 | 79 | @SuppressWarnings("unchecked") 80 | public T get(Class type, String key){ 81 | Object temp = this.cfg.get(key); 82 | if(temp == null){ 83 | return null; 84 | } 85 | return (T) temp; 86 | } 87 | 88 | public T get(Class type, String key, T defaultValue){ 89 | Object temp = this.cfg.get(key); 90 | if(temp == null){ 91 | return defaultValue; 92 | } 93 | return (T) temp; 94 | } 95 | 96 | public class KeyComparable implements Comparator { 97 | @Override 98 | public int compare(Object o1, Object o2) { 99 | String v1 = Objects.toString(o1); 100 | String v2 = Objects.toString(o2); 101 | return v1.compareToIgnoreCase(v2); 102 | } 103 | } 104 | 105 | public List getOrderedKeys(){ 106 | List list = new ArrayList(this.cfg.keySet()); 107 | Collections.sort(list, new KeyComparable()); 108 | return list; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /newim-common/src/main/java/com/whosbean/newim/config/ConfigYamlManager.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.config; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.core.io.ClassPathResource; 6 | import org.yaml.snakeyaml.Yaml; 7 | 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * Created with IntelliJ IDEA. 15 | * User: yamingdeng 16 | * Date: 13-12-15 17 | * Time: 下午4:35 18 | */ 19 | public class ConfigYamlManager { 20 | 21 | private static Map> maps = new HashMap>(); 22 | 23 | private static Logger logger = LoggerFactory.getLogger(ConfigYamlManager.class); 24 | 25 | public static void add(String name, Map map){ 26 | maps.put(name, map); 27 | } 28 | 29 | public static void set(String name, String key, Object value){ 30 | Map map = maps.get(name); 31 | if (map!=null){ 32 | map.put(key, value); 33 | } 34 | } 35 | 36 | public static Map load(String fileName, String name){ 37 | Map map = maps.get(name); 38 | if (map == null){ 39 | try { 40 | map = _doLoad(fileName); 41 | } catch (IOException e) { 42 | logger.error("can't load config: " + fileName); 43 | } 44 | maps.put(name, map); 45 | } 46 | return map; 47 | } 48 | 49 | private static Map _doLoad(String fileName) throws IOException { 50 | System.out.println("Config loading. " + fileName); 51 | ClassPathResource classPathResource = new ClassPathResource(fileName); 52 | if (classPathResource==null){ 53 | logger.error("can't load config: " + fileName); 54 | return null; 55 | }else{ 56 | InputStream input = classPathResource.getInputStream(); 57 | Yaml yaml = new Yaml(); 58 | return (Map) yaml.load(input); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /newim-common/src/main/java/com/whosbean/newim/redis/RedisBucket.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.redis; 2 | 3 | import com.google.common.collect.Lists; 4 | import org.apache.commons.pool.BasePoolableObjectFactory; 5 | import org.springframework.beans.factory.InitializingBean; 6 | import org.springframework.stereotype.Component; 7 | import redis.clients.jedis.*; 8 | import redis.clients.util.Hashing; 9 | import redis.clients.util.Pool; 10 | 11 | import java.util.List; 12 | import java.util.regex.Pattern; 13 | 14 | /** 15 | * Created by yaming_deng on 14-9-5. 16 | */ 17 | @Component 18 | public class RedisBucket extends Pool implements InitializingBean { 19 | 20 | private RedisConfig jedisConfig; 21 | 22 | private JedisPoolConfig jedisPoolConfig; 23 | private JedisShardInfo jedisShardInfo; 24 | 25 | public RedisBucket(){ 26 | this.jedisConfig = new RedisConfig(); 27 | try { 28 | this.jedisConfig.afterPropertiesSet(); 29 | } catch (Exception e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | 34 | @Override 35 | public void afterPropertiesSet() throws Exception { 36 | jedisPoolConfig = new JedisPoolConfig(); 37 | jedisPoolConfig.setMaxActive(jedisConfig.get(Integer.class,"maxActive", 50)); 38 | jedisPoolConfig.setMaxIdle(jedisConfig.get(Integer.class,"maxIdle", 10)); 39 | jedisPoolConfig.setMaxWait(jedisConfig.get(Integer.class,"maxWait", 5000)); 40 | 41 | String host = jedisConfig.get(String.class, "host"); 42 | int port = jedisConfig.get(Integer.class, "port", 6379); 43 | jedisShardInfo = new JedisShardInfo(host, port); 44 | 45 | List shards = Lists.newArrayList(); 46 | shards.add(jedisShardInfo); 47 | 48 | this.initPool(jedisPoolConfig, new ShardedJedisFactory(shards, Hashing.MURMUR_HASH, null)); 49 | } 50 | 51 | /** 52 | * PoolableObjectFactory custom impl. 53 | */ 54 | private static class ShardedJedisFactory extends BasePoolableObjectFactory { 55 | private List shards; 56 | private Hashing algo; 57 | private Pattern keyTagPattern; 58 | 59 | public ShardedJedisFactory(List shards, Hashing algo, 60 | Pattern keyTagPattern) { 61 | this.shards = shards; 62 | this.algo = algo; 63 | this.keyTagPattern = keyTagPattern; 64 | } 65 | 66 | public Object makeObject() throws Exception { 67 | BinaryShardedJedis jedis = new BinaryShardedJedis(shards, algo, keyTagPattern); 68 | return jedis; 69 | } 70 | 71 | public void destroyObject(final Object obj) throws Exception { 72 | if ((obj != null) && (obj instanceof ShardedJedis)) { 73 | ShardedJedis shardedJedis = (ShardedJedis) obj; 74 | for (Jedis jedis : shardedJedis.getAllShards()) { 75 | try { 76 | try { 77 | jedis.quit(); 78 | } catch (Exception e) { 79 | 80 | } 81 | jedis.disconnect(); 82 | } catch (Exception e) { 83 | 84 | } 85 | } 86 | } 87 | } 88 | 89 | public boolean validateObject(final Object obj) { 90 | try { 91 | ShardedJedis jedis = (ShardedJedis) obj; 92 | for (Jedis shard : jedis.getAllShards()) { 93 | if (!shard.ping().equals("PONG")) { 94 | return false; 95 | } 96 | } 97 | return true; 98 | } catch (Exception ex) { 99 | return false; 100 | } 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /newim-common/src/main/java/com/whosbean/newim/redis/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.redis; 2 | 3 | import com.whosbean.newim.config.AbstractConfig; 4 | 5 | /** 6 | * Created by yaming_deng on 14-9-11. 7 | */ 8 | public class RedisConfig extends AbstractConfig { 9 | 10 | @Override 11 | public String getConfName() { 12 | return "redis"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /newim-common/src/main/java/com/whosbean/newim/redis/RedisMessageService.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.redis; 2 | 3 | import com.whosbean.newim.entity.ChatMessage; 4 | import com.whosbean.newim.service.ChatMessageService; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | import redis.clients.jedis.BinaryShardedJedis; 10 | 11 | /** 12 | * Created by yaming_deng on 14-9-11. 13 | */ 14 | @Service 15 | public class RedisMessageService implements ChatMessageService { 16 | 17 | public static final String IM_PENDING = "im:qs:"; 18 | public static final byte[] IM_PENDING_COUNT = "im:qsc".getBytes(); 19 | public static final byte[] IM_PK = "im:pk".getBytes(); 20 | 21 | public static final int TTS = 600; 22 | 23 | protected Logger logger = LoggerFactory.getLogger(this.getClass()); 24 | 25 | @Autowired 26 | private RedisBucket redisBucket; 27 | 28 | @Override 29 | public void save(ChatMessage message) { 30 | BinaryShardedJedis jedis = redisBucket.getResource(); 31 | try { 32 | long id = jedis.incr(IM_PK); 33 | ChatMessage.Builder builder = ChatMessage.newBuilder(message); 34 | builder.setUuid(id + ""); 35 | message = builder.build(); 36 | byte[] key = (IM_PENDING + id).getBytes(); 37 | jedis.set(key, message.toByteArray()); 38 | jedis.expire(key, TTS); 39 | long total = jedis.incr(IM_PENDING_COUNT); 40 | redisBucket.returnResource(jedis); 41 | logger.info("pending total = " + total); 42 | } catch (Exception e) { 43 | logger.error("添加消息进Redis错误", e); 44 | redisBucket.returnBrokenResource(jedis); 45 | } 46 | } 47 | 48 | @Override 49 | public ChatMessage get(String uuid) { 50 | BinaryShardedJedis jedis = redisBucket.getResource(); 51 | try { 52 | byte[] key = (IM_PENDING + uuid).getBytes(); 53 | byte[] data = jedis.get(key); 54 | ChatMessage message = ChatMessage.newBuilder().mergeFrom(data).build(); 55 | redisBucket.returnResource(jedis); 56 | return message; 57 | } catch (Exception e) { 58 | logger.error("添加消息进Redis错误", e); 59 | redisBucket.returnBrokenResource(jedis); 60 | } 61 | return null; 62 | } 63 | 64 | @Override 65 | public byte[] getBytes(String uuid) { 66 | BinaryShardedJedis jedis = redisBucket.getResource(); 67 | try { 68 | byte[] key = (IM_PENDING + uuid).getBytes(); 69 | byte[] data = jedis.get(key); 70 | redisBucket.returnResource(jedis); 71 | return data; 72 | } catch (Exception e) { 73 | logger.error("添加消息进Redis错误", e); 74 | redisBucket.returnBrokenResource(jedis); 75 | } 76 | return null; 77 | } 78 | 79 | @Override 80 | public boolean remove(String uuid) { 81 | BinaryShardedJedis jedis = redisBucket.getResource(); 82 | try { 83 | byte[] key = (IM_PENDING + uuid).getBytes(); 84 | long ret = jedis.del(key); 85 | redisBucket.returnResource(jedis); 86 | return ret > 0; 87 | } catch (Exception e) { 88 | logger.error("添加消息进Redis错误", e); 89 | redisBucket.returnBrokenResource(jedis); 90 | } 91 | return false; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /newim-common/src/main/java/com/whosbean/newim/server/ChatServerNode.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.server; 2 | 3 | import com.whosbean.newim.entity.ChatMessage; 4 | import com.whosbean.newim.zookeeper.ZKPaths; 5 | import io.netty.channel.Channel; 6 | import org.apache.zookeeper.CreateMode; 7 | import org.apache.zookeeper.KeeperException; 8 | import org.apache.zookeeper.data.Stat; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Created by yaming_deng on 14-9-9. 14 | */ 15 | public class ChatServerNode extends ServerNode { 16 | 17 | /** 18 | * 写入新消息到Zookeeper. Router会得到通知,并读取消息,路由给多个客户端. 19 | * @param chatMessage 20 | * @throws Exception 21 | */ 22 | public void newMessage(final ChatMessage chatMessage) throws Exception { 23 | //notify exchange a new-added message 24 | if (this.client == null){ 25 | logger.error("Zookeeper Client is Lost"); 26 | return; 27 | } 28 | 29 | String path = ZKPaths.getInboxPath(chatMessage.getBoxid()); 30 | Stat stat = this.client.checkExists().forPath(path); 31 | if (stat == null){ 32 | this.client.create().creatingParentsIfNeeded() 33 | .withMode(CreateMode.PERSISTENT).forPath(path); 34 | } 35 | 36 | String data = chatMessage.getUuid(); 37 | if (logger.isDebugEnabled()){ 38 | logger.debug("newMessage. path0={}", path); 39 | } 40 | if (data == null){ 41 | data = "NULL"; 42 | } 43 | path = path + "/" + chatMessage.getBoxid() + "-"; 44 | if (logger.isDebugEnabled()){ 45 | logger.debug("newMessage. path1={}", path); 46 | } 47 | this.client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT_SEQUENTIAL) 48 | .inBackground().forPath(path, data.getBytes("UTF-8")); 49 | } 50 | 51 | public void outMessage(final String msgPath) throws Exception { 52 | //notify exchange a new-added message 53 | if (this.client == null){ 54 | logger.error("Zookeeper Client is Lost"); 55 | return; 56 | } 57 | if (logger.isDebugEnabled()){ 58 | logger.debug("outMessage. path=" + msgPath); 59 | } 60 | byte[] data; 61 | try { 62 | data = this.client.getData().forPath(msgPath); 63 | } catch (KeeperException e) { 64 | if (e.code().equals(KeeperException.Code.NONODE)){ 65 | this.delete(msgPath); 66 | return; 67 | }else{ 68 | throw e; 69 | } 70 | } 71 | String path1 = msgPath.replace("/inbox/", "/outbox/"); 72 | 73 | this.client.create().creatingParentsIfNeeded() 74 | .withMode(CreateMode.PERSISTENT).forPath(path1, data); 75 | this.delete(msgPath); 76 | } 77 | 78 | /** 79 | * 当客户端断开连接后,把链接从chat room中移除. 80 | * @param chatPath 81 | * @throws Exception 82 | */ 83 | public void remConnection(String chatPath, Integer channelId) throws Exception { 84 | //lost a connection 85 | //a member quit from a chat 86 | if (this.client == null){ 87 | logger.error("Zookeeper Client is Lost"); 88 | return; 89 | } 90 | String path = ZKPaths.getMemberPath(chatPath, channelId); 91 | if (logger.isDebugEnabled()){ 92 | logger.debug("remConnection. path=" + path); 93 | } 94 | this.delete(path); 95 | } 96 | 97 | /** 98 | * 加入chat room 99 | * @param channel 100 | * @param message 101 | * @throws Exception 102 | */ 103 | public void join(final Channel channel, final ChatMessage message) throws Exception { 104 | //new member join a chat 105 | if (this.client == null){ 106 | logger.error("Zookeeper Client is Lost"); 107 | return; 108 | } 109 | String path = ZKPaths.getMemberPath(message.getBoxid(), this.getName(), channel.hashCode()); 110 | if (logger.isDebugEnabled()){ 111 | logger.debug("join. path={}", path); 112 | } 113 | String data = message.getSender(); 114 | this.client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).inBackground().forPath(path, data.getBytes("UTF-8")); 115 | } 116 | 117 | public List getMembers(final String boxid) throws Exception { 118 | if (this.client == null){ 119 | logger.error("Zookeeper Client is Lost"); 120 | return null; 121 | } 122 | String path = ZKPaths.getMemberPath(boxid); 123 | if (logger.isDebugEnabled()){ 124 | logger.debug("getMembers. path={}", path); 125 | } 126 | List list = this.client.getChildren().forPath(path); 127 | return list; 128 | } 129 | 130 | /** 131 | * 退出chat room 132 | * @param channel 133 | * @param message 134 | * @throws Exception 135 | */ 136 | public void quit(final Channel channel, final ChatMessage message) throws Exception { 137 | //a member quit from a chat 138 | if (this.client == null){ 139 | logger.error("Zookeeper Client is Lost"); 140 | return; 141 | } 142 | String path = ZKPaths.getMemberPath(message.getBoxid(), this.getName(), channel.hashCode()); 143 | if (logger.isDebugEnabled()){ 144 | logger.debug("quit. path={}", path); 145 | } 146 | this.delete(path); 147 | } 148 | 149 | public void delete(String path){ 150 | try { 151 | this.client.delete().inBackground().forPath(path); 152 | }catch (KeeperException e){ 153 | if (e.code().equals(KeeperException.Code.NONODE)){ 154 | 155 | }else{ 156 | logger.error(e.getMessage(), e); 157 | } 158 | }catch (Exception e) { 159 | logger.error(e.getMessage(), e); 160 | } 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /newim-common/src/main/java/com/whosbean/newim/server/ServerNode.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.server; 2 | 3 | import com.whosbean.newim.zookeeper.ZKPaths; 4 | import com.whosbean.newim.zookeeper.ZookeeperConfig; 5 | import org.apache.curator.RetryPolicy; 6 | import org.apache.curator.framework.CuratorFramework; 7 | import org.apache.curator.framework.CuratorFrameworkFactory; 8 | import org.apache.curator.retry.ExponentialBackoffRetry; 9 | import org.apache.curator.utils.EnsurePath; 10 | import org.apache.zookeeper.CreateMode; 11 | import org.apache.zookeeper.data.Stat; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.beans.factory.DisposableBean; 15 | import org.springframework.beans.factory.InitializingBean; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | 18 | import java.util.List; 19 | 20 | /** 21 | * Created by yaming_deng on 14-9-9. 22 | */ 23 | public abstract class ServerNode implements InitializingBean, DisposableBean { 24 | 25 | protected Logger logger = LoggerFactory.getLogger(this.getClass()); 26 | 27 | @Autowired 28 | protected ZookeeperConfig zookeeperConfig; 29 | 30 | protected CuratorFramework client; 31 | 32 | @Override 33 | public void afterPropertiesSet() throws Exception { 34 | logger.info("Connecting to Zookeeper. " + zookeeperConfig.getServers()); 35 | this.connectZookeeper(); 36 | logger.info("Registry this server to Zookeeper."); 37 | this.registryAtZookeeper(); 38 | logger.info("Startup Done. sig = " + this.getName()); 39 | } 40 | 41 | @Override 42 | public void destroy() throws Exception { 43 | if (client != null){ 44 | client.delete().forPath(this.getZkPath()); 45 | client.close(); 46 | } 47 | } 48 | 49 | protected void connectZookeeper() throws Exception { 50 | String zookeeperConnectionString = zookeeperConfig.getServers(); //"localhost:2181,localhost:2182,localhost:2183"; 51 | int limit = zookeeperConfig.getRetryLimit(); 52 | int wait = zookeeperConfig.getRetryInterval(); 53 | RetryPolicy retryPolicy = new ExponentialBackoffRetry(wait, limit); 54 | client = CuratorFrameworkFactory.newClient(zookeeperConnectionString, retryPolicy); 55 | client.start(); 56 | EnsurePath ensurePath = client.newNamespaceAwareEnsurePath(ZKPaths.NS_ROOT); 57 | ensurePath.ensure(client.getZookeeperClient()); 58 | } 59 | 60 | protected void registryAtZookeeper() throws Exception { 61 | //ip+":"+port 62 | String path = getZkPath(); 63 | try { 64 | byte[] data = this.getConf().getBytes("UTF-8"); 65 | Stat stat = client.checkExists().forPath(path); 66 | if (stat != null){ 67 | client.delete().forPath(path); 68 | } 69 | client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground().forPath(path, data); 70 | } catch (Exception e) { 71 | logger.error("registryAtZookeeper Error. sig=" + this.getName(), e); 72 | throw e; 73 | } 74 | } 75 | 76 | private String getZkPath() { 77 | return ZKPaths.getServerPath(this.getRole(), this.getName()); 78 | } 79 | 80 | public List getServers(String role) throws Exception { 81 | String path = ZKPaths.getServerPath(role); 82 | return client.getChildren().forPath(path); 83 | } 84 | 85 | public CuratorFramework getZkClient(){ 86 | return client; 87 | } 88 | 89 | protected String getRole(){ 90 | return null; 91 | } 92 | 93 | protected String getName(){ 94 | return null; 95 | } 96 | 97 | protected String getConf(){ 98 | return null; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /newim-common/src/main/java/com/whosbean/newim/server/ServerNodeRoles.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.server; 2 | 3 | /** 4 | * Created by yaming_deng on 14-9-9. 5 | */ 6 | public class ServerNodeRoles { 7 | 8 | public static final String ROLE_EXCHANGE = "exchange"; 9 | 10 | public static final String ROLE_ROUTER = "router"; 11 | } 12 | -------------------------------------------------------------------------------- /newim-common/src/main/java/com/whosbean/newim/server/ServerStarter.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.server; 2 | 3 | /** 4 | * Created by yaming_deng on 14-9-9. 5 | */ 6 | public interface ServerStarter { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /newim-common/src/main/java/com/whosbean/newim/service/ChatMessageService.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.service; 2 | 3 | import com.whosbean.newim.entity.ChatMessage; 4 | 5 | /** 6 | * Created by yaming_deng on 14-9-11. 7 | */ 8 | public interface ChatMessageService { 9 | 10 | /** 11 | * 12 | * @param message 13 | */ 14 | void save(final ChatMessage message); 15 | 16 | /** 17 | * 18 | * @param uuid 19 | * @return 20 | */ 21 | ChatMessage get(String uuid); 22 | 23 | /** 24 | * 读取字节 25 | * @param uuid 26 | * @return 27 | */ 28 | byte[] getBytes(String uuid); 29 | /** 30 | * 31 | * @param uuid 32 | */ 33 | boolean remove(String uuid); 34 | } 35 | -------------------------------------------------------------------------------- /newim-common/src/main/java/com/whosbean/newim/service/ChatMessageServiceFactory.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.service; 2 | 3 | import com.whosbean.newim.common.AppContextHolder; 4 | import com.whosbean.newim.config.AbstractConfig; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by yaming_deng on 14-9-11. 10 | */ 11 | public class ChatMessageServiceFactory { 12 | 13 | public static final String MESSAGE_SERVICE = "MessageService"; 14 | 15 | public static ChatMessageService get(String name){ 16 | String beanName = name + MESSAGE_SERVICE; 17 | ChatMessageService service = AppContextHolder.context.get(ChatMessageService.class, beanName); 18 | return service; 19 | } 20 | 21 | public static ChatMessageService get(AbstractConfig config){ 22 | Map map = config.get(Map.class, "bucket"); 23 | String beanName = map.get("engine") + MESSAGE_SERVICE; 24 | ChatMessageService service = AppContextHolder.context.get(ChatMessageService.class, beanName); 25 | return service; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /newim-common/src/main/java/com/whosbean/newim/zookeeper/ZKPaths.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.zookeeper; 2 | 3 | /** 4 | * Created by yaming_deng on 14-9-11. 5 | */ 6 | public class ZKPaths { 7 | /** 8 | * ROOT 9 | */ 10 | public static final String NS_ROOT = "/newim"; 11 | /** 12 | * /newim/servers/exchange/ip:port-for-exchange(data=ip:port-for-gateway) 13 | * /newim/servers/routers/ip:port(data=ip:port) 14 | */ 15 | public static final String PATH_SERVERS = "/servers"; 16 | /** 17 | * /newim/chats/0000x/channel-id(data=ip:port-for-exchange) 18 | */ 19 | public static final String PATH_CHATS = "/chats"; 20 | /** 21 | * new message 22 | * /newim/inbox/{boxid}/{msgid} 23 | */ 24 | public static final String PATH_INBOX = "/inbox"; 25 | /** 26 | * message out 27 | * /newim/outbox/{boxid}/{msgid} 28 | */ 29 | public static final String PATH_OUTBOX = "/outbox"; 30 | 31 | public static String getServerPath(String role){ 32 | String path = String.format("%s%s/%s", ZKPaths.NS_ROOT, ZKPaths.PATH_SERVERS, role); 33 | return path; 34 | } 35 | 36 | public static String getServerPath(String role, String name){ 37 | String path = String.format("%s%s/%s/%s", ZKPaths.NS_ROOT, ZKPaths.PATH_SERVERS, role, name); 38 | return path; 39 | } 40 | 41 | public static String getMemberPath(String boxid, String serverName, Integer channelid){ 42 | String path = String.format("%s%s/%s/%s/%s", ZKPaths.NS_ROOT, ZKPaths.PATH_CHATS, boxid, serverName, channelid); 43 | return path; 44 | } 45 | 46 | public static String getMemberPath(String chatPath, Integer channelid){ 47 | String path = String.format("%s%s/%s/%s", ZKPaths.NS_ROOT, ZKPaths.PATH_CHATS, chatPath, channelid); 48 | return path; 49 | } 50 | 51 | public static String getMemberPath(String boxid){ 52 | String path = String.format("%s%s/%s", ZKPaths.NS_ROOT, ZKPaths.PATH_CHATS, boxid); 53 | return path; 54 | } 55 | 56 | public static String getInboxPath(String boxid, String msgid){ 57 | String path = String.format("%s%s/%s/%s", ZKPaths.NS_ROOT, ZKPaths.PATH_INBOX, boxid, msgid); 58 | return path; 59 | } 60 | 61 | public static String getOutboxPath(String boxid, String msgid){ 62 | String path = String.format("%s%s/%s/%s", ZKPaths.NS_ROOT, ZKPaths.PATH_OUTBOX, boxid, msgid); 63 | return path; 64 | } 65 | 66 | public static String getInboxPath(String msgpath){ 67 | String path = String.format("%s%s/%s", ZKPaths.NS_ROOT, ZKPaths.PATH_INBOX, msgpath); 68 | return path; 69 | } 70 | 71 | public static String getOutboxPath(String msgpath){ 72 | String path = String.format("%s%s/%s", ZKPaths.NS_ROOT, ZKPaths.PATH_OUTBOX, msgpath); 73 | return path; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /newim-common/src/main/java/com/whosbean/newim/zookeeper/ZookeeperConfig.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.zookeeper; 2 | 3 | import com.whosbean.newim.config.AbstractConfig; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by yaming_deng on 14-9-5. 10 | */ 11 | @Component 12 | public class ZookeeperConfig extends AbstractConfig { 13 | 14 | public static final String CONF_ZOOKEEPER = "zookeeper"; 15 | 16 | private Map retrys; 17 | 18 | @Override 19 | public String getConfName() { 20 | return CONF_ZOOKEEPER; 21 | } 22 | 23 | @Override 24 | public void afterPropertiesSet() throws Exception { 25 | this.cfgFile = CONF_ZOOKEEPER; 26 | super.afterPropertiesSet(); 27 | this.retrys = this.get(Map.class, "retry"); 28 | } 29 | 30 | /** 31 | String zookeeperConnectionString = "localhost:2181,localhost:2182,localhost:2183"; 32 | RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); 33 | CuratorFramework client = CuratorFrameworkFactory.newClient( 34 | zookeeperConnectionString, retryPolicy); 35 | client.start(); 36 | */ 37 | 38 | public String getServers(){ 39 | String servs = this.get(String.class, "servers"); 40 | return servs; 41 | } 42 | 43 | public Integer getRetryInterval(){ 44 | Integer r = this.retrys.get("interval"); 45 | return r; 46 | } 47 | 48 | public Integer getRetryLimit(){ 49 | Integer r = this.retrys.get("limit"); 50 | return r; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /newim-common/src/main/resources/redis.yaml: -------------------------------------------------------------------------------- 1 | host: 127.0.0.1 2 | port: 6379 3 | maxActive: 50 4 | maxIdle: 10 5 | maxWait: 5000 -------------------------------------------------------------------------------- /newim-common/src/main/resources/zookeeper.yaml: -------------------------------------------------------------------------------- 1 | servers: 127.0.0.1:2181,127.0.0.1:2182,127.0.0.l:2183 2 | retry: 3 | interval: 1000 4 | limit: 3 -------------------------------------------------------------------------------- /newim-common/src/test/java/com/whosbean/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.whosbean; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /newim-gateway/bin/copy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | source /etc/profile 4 | export LC_ALL=C 5 | 6 | folder="$1" 7 | name="$2" 8 | dest="$3" 9 | 10 | echo "$folder" 11 | echo "$name" 12 | echo "$dest" 13 | 14 | user="$4" 15 | host="$5" 16 | 17 | # e.g. /data/in/zhenai/t_fw_00005/20140918 18 | cd "$folder" 19 | scp -q "$name" "$user"@"$host":"$dest" 20 | 21 | # e.g. /data/in/zhenai/t_fw_00005/20140918 22 | # to /data/up/zhenai/t_fw_00005/20140918 23 | up="${folder/\/in\///up/}" 24 | echo "up=$up" 25 | mkdir -p "$up" 26 | 27 | touch "$name.check" 28 | check="${dest/\/in//check}" 29 | echo "check=$check" 30 | scp -q "$name.check" "$user"@"$host":"$check" 31 | 32 | rm -f "$name.check" 33 | 34 | mv "$name" "$up" 35 | 36 | echo "SUCCESS" -------------------------------------------------------------------------------- /newim-gateway/bin/scpfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source /etc/profile 4 | export LC_ALL=zh_CN.utf8 5 | 6 | function scpfile() 7 | { 8 | local source_file="$1/$2" 9 | local source_name="$2" 10 | local target_dir="$3" 11 | local target_name="$4" 12 | 13 | #判断是否存在源文件,存在则建立check文件 14 | if [ -f "$source_file" ]; then 15 | touch "$source_file.check" 16 | if [ $? -eq 0 ]; then 17 | scp -q "$source_file" "$target_dir/in/$target_name" 18 | if [ $? -eq 0 ]; then 19 | scp -q "$source_file.check" "$target_dir/check/$target_name.check" 20 | rm "$source_file.check" 21 | [ $? -eq 0 ] && return 0 || return 1 22 | fi 23 | else 24 | return 1 25 | fi 26 | else 27 | return 1 28 | fi 29 | } 30 | 31 | #参数 source_file: 源文件目录 32 | #参数 source_name: 源文件名称 33 | #在上传成功后,把文件移动到上传成功目录 34 | function mvfile() 35 | { 36 | local source_file="$1" 37 | local source_name="$2" 38 | 39 | upfolder="${source_file/\/in\///up/}" 40 | mkdir -p "$upfolder" 41 | 42 | mv "$source_file/$source_name" "$upfolder" 43 | 44 | } 45 | 46 | function main() 47 | { 48 | local src_file="$1" 49 | local src_name="$2" 50 | local tar_dir="$3" 51 | local tar_name="$4" 52 | local user="$5" 53 | local tar_ip="$6" 54 | 55 | scpfile "$src_file" "$src_name" "$user@$tar_ip:$tar_dir" "$tar_name" 56 | ret=$? 57 | echo "$ret" 58 | 59 | if [ $ret -eq 0 ]; then 60 | mvfile "$src_file" "$src_name" 61 | echo "SUCCESS" 62 | else 63 | echo "FAILED" 64 | fi 65 | 66 | [ $ret -eq 0 ] && return 0 || return 1 67 | } 68 | 69 | main "$1" "$2" "$3" "$4" "$5" "$6" 70 | -------------------------------------------------------------------------------- /newim-gateway/bin/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #------------------------------------------------------------------- 4 | # Mb Bootstrap Script 5 | #------------------------------------------------------------------- 6 | 7 | # find Mb home. 8 | CURR_DIR=`pwd` 9 | RESV_HOME=`pwd` 10 | 11 | echo $CURR_DIR 12 | cd $CURR_DIR 13 | 14 | if [ -z "$RESV_HOME" ] ; then 15 | echo 16 | echo Must set RESV_HOME 17 | echo 18 | exit 1 19 | fi 20 | 21 | 22 | for i in $RESV_HOME/lib/*.jar; do 23 | CLASSPATH=$i:$CLASSPATH; 24 | done 25 | 26 | CLASSPATH=$RESV_HOME/classes:$CLASSPATH 27 | 28 | DEBUG_INFO=" -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n" 29 | DEBUG="" 30 | 31 | MAIN_CLASS="com.whosbean.newim.gateway.GatewayServerMain"; 32 | DEFAULT_OPTS="-server -Xms8G -Xmx8G -Xmn1G -XX:PermSize=50M -XX:MaxPermSize=50M -Xss256k -Dio.netty.leakDetectionLevel=advanced" ; 33 | 34 | 35 | DEFAULT_OPTS="$DEFAULT_OPTS -XX:+DisableExplicitGC -XX:SurvivorRatio=1 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:LargePageSizeInBytes=128M -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -Dfile.encoding=UTF-8" 36 | DEFAULT_OPTS="$DEFAULT_OPTS -DMB.home=\"$RESV_HOME\"" 37 | 38 | echo java $DEBUG $DEFAULT_OPTS -classpath $CLASSPATH $MAIN_CLASS 39 | java $DEBUG $DEFAULT_OPTS -classpath $CLASSPATH $MAIN_CLASS & -------------------------------------------------------------------------------- /newim-gateway/newim-gateway.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /newim-gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | newim 5 | com.whosbean 6 | 1.0-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | newim-gateway 11 | jar 12 | 13 | newim-gateway 14 | http://maven.apache.org 15 | 16 | 17 | UTF-8 18 | 19 | 20 | 21 | 22 | junit 23 | junit 24 | 3.8.1 25 | test 26 | 27 | 28 | com.whosbean 29 | newim-common 30 | 1.0-SNAPSHOT 31 | 32 | 33 | 34 | 35 | 36 | 37 | maven-compiler-plugin 38 | 39 | 1.6 40 | 1.6 41 | 42 | 43 | 44 | maven-assembly-plugin 45 | 2.2-beta-5 46 | 47 | 48 | src/main/assembly/gateway.xml 49 | 50 | 55 | 56 | 57 | 58 | make-assembly 59 | package 60 | 61 | single 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /newim-gateway/src/main/assembly/gateway.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | server 6 | 7 | zip 8 | 9 | false 10 | 11 | 12 | false 13 | runtime 14 | lib 15 | 16 | *:sources 17 | org.apache.hadoop:* 18 | org.apache.mahout:* 19 | 20 | 21 | 22 | true 23 | 24 | org.apache.mahout:* 25 | 26 | 27 | 28 | 29 | 30 | ${basedir}/target/classes 31 | /classes 32 | 33 | *.jar 34 | 35 | 36 | 37 | ${basedir}/bin 38 | / 39 | 40 | *.jar 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /newim-gateway/src/main/java/com/whosbean/newim/GatewayMain.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim; 2 | 3 | import com.whosbean.newim.gateway.GatewayConfig; 4 | import com.whosbean.newim.gateway.exchange.MessageExchangeHandler; 5 | import com.whosbean.newim.gateway.handler.HttpSessionHandler; 6 | import com.whosbean.newim.gateway.handler.WsConnectedHandler; 7 | import com.whosbean.newim.server.ServerStarter; 8 | import io.netty.bootstrap.ServerBootstrap; 9 | import io.netty.channel.*; 10 | import io.netty.channel.nio.NioEventLoopGroup; 11 | import io.netty.channel.socket.SocketChannel; 12 | import io.netty.channel.socket.nio.NioServerSocketChannel; 13 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 14 | import io.netty.handler.codec.LengthFieldPrepender; 15 | import io.netty.handler.codec.bytes.ByteArrayDecoder; 16 | import io.netty.handler.codec.bytes.ByteArrayEncoder; 17 | import io.netty.handler.codec.http.HttpObjectAggregator; 18 | import io.netty.handler.codec.http.HttpRequestDecoder; 19 | import io.netty.handler.codec.http.HttpResponseEncoder; 20 | import io.netty.handler.codec.http.websocketx.WebSocket13FrameEncoder; 21 | import org.springframework.context.support.ClassPathXmlApplicationContext; 22 | import org.springframework.util.Assert; 23 | 24 | import java.util.Map; 25 | 26 | /** 27 | * Created by yaming_deng on 14-9-9. 28 | */ 29 | public class GatewayMain implements ServerStarter { 30 | 31 | public class WebsocketServerThread extends Thread{ 32 | 33 | @Override 34 | public void run() { 35 | startWebsocketServer(GatewayConfig.current); 36 | } 37 | } 38 | 39 | public class MessageSenderServerThread extends Thread{ 40 | 41 | @Override 42 | public void run() { 43 | startSenderServer(GatewayConfig.current); 44 | } 45 | } 46 | 47 | /** 48 | * 启动推送服务 8080端口 49 | */ 50 | public void start() { 51 | ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-gateway.xml"); 52 | GatewayConfig prop = context.getBean("gatewayConfig", GatewayConfig.class); 53 | Assert.notNull(prop, "gatewayConfig bean is NULL."); 54 | new WebsocketServerThread().start(); 55 | new MessageSenderServerThread().start(); 56 | } 57 | 58 | private void startWebsocketServer(GatewayConfig prop) { 59 | Map server = prop.get(Map.class, "websocket"); 60 | Integer actSize = (Integer)server.get("actors"); 61 | Integer workerSize = (Integer)server.get("workers"); 62 | String host = (String)server.get("ip"); 63 | int port = (Integer)server.get("port"); 64 | 65 | EventLoopGroup parentGroup = new NioEventLoopGroup(actSize); // 用于接收发来的连接请求 66 | EventLoopGroup childGroup = new NioEventLoopGroup(workerSize); // 用于处理parentGroup接收并注册给child的连接中的信息 67 | try { 68 | ServerBootstrap serverBootstrap = new ServerBootstrap(); // 服务器助手类 69 | // 简历新的accept连接,用于构建serverSocketChannel的工厂类 70 | serverBootstrap.group(parentGroup, childGroup) 71 | .channel(NioServerSocketChannel.class) 72 | .childHandler(new ChannelInitializer() { 73 | @Override 74 | public void initChannel(SocketChannel ch) 75 | throws Exception { 76 | ch.pipeline().addLast( 77 | new HttpRequestDecoder(), 78 | new HttpObjectAggregator(65536), 79 | new HttpSessionHandler(), 80 | new WsConnectedHandler(), 81 | new HttpResponseEncoder(), 82 | new WebSocket13FrameEncoder(false) 83 | ); 84 | } 85 | }); 86 | 87 | serverBootstrap.option(ChannelOption.SO_KEEPALIVE, true); 88 | serverBootstrap.option(ChannelOption.TCP_NODELAY, true); 89 | serverBootstrap.option(ChannelOption.SO_REUSEADDR, true); 90 | 91 | System.out.println("start Websocket server " + host + ":" + port + " ... "); 92 | ChannelFuture f = serverBootstrap.bind(host, port).sync(); 93 | f.channel().closeFuture().sync(); 94 | } catch (Exception e) { 95 | e.printStackTrace(); 96 | } finally { 97 | childGroup.shutdownGracefully(); 98 | parentGroup.shutdownGracefully(); 99 | } 100 | } 101 | 102 | private void startSenderServer(GatewayConfig prop) { 103 | Map server = prop.get(Map.class, "exchange"); 104 | Integer actSize = (Integer)server.get("actors"); 105 | Integer workerSize = (Integer)server.get("workers"); 106 | String host = (String)server.get("ip"); 107 | int port = (Integer)server.get("port"); 108 | 109 | EventLoopGroup parentGroup = new NioEventLoopGroup(actSize); // 用于接收发来的连接请求 110 | EventLoopGroup childGroup = new NioEventLoopGroup(workerSize); // 用于处理parentGroup接收并注册给child的连接中的信息 111 | try { 112 | ServerBootstrap serverBootstrap = new ServerBootstrap(); // 服务器助手类 113 | // 简历新的accept连接,用于构建serverSocketChannel的工厂类 114 | serverBootstrap.group(parentGroup, childGroup) 115 | .channel(NioServerSocketChannel.class) 116 | .childHandler(new ChannelInitializer() { 117 | @Override 118 | public void initChannel(SocketChannel ch) 119 | throws Exception { 120 | ChannelPipeline pipeline = ch.pipeline(); 121 | pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)); 122 | pipeline.addLast("bytesDecoder",new ByteArrayDecoder()); 123 | 124 | pipeline.addLast("frameEncoder", new LengthFieldPrepender(4, false)); 125 | pipeline.addLast("bytesEncoder", new ByteArrayEncoder()); 126 | 127 | pipeline.addLast("handler", new MessageExchangeHandler()); 128 | } 129 | }); 130 | 131 | serverBootstrap.option(ChannelOption.SO_KEEPALIVE, true); 132 | serverBootstrap.option(ChannelOption.TCP_NODELAY, true); 133 | serverBootstrap.option(ChannelOption.SO_REUSEADDR, true); 134 | 135 | System.out.println("start Message Sender server " + host+":"+port + " ... "); 136 | ChannelFuture f = serverBootstrap.bind(host, port).sync(); 137 | f.channel().closeFuture().sync(); 138 | } catch (Exception e) { 139 | e.printStackTrace(); 140 | } finally { 141 | childGroup.shutdownGracefully(); 142 | parentGroup.shutdownGracefully(); 143 | } 144 | } 145 | 146 | /** 147 | * 入口 148 | * @param args 149 | */ 150 | public static void main(String[] args) { 151 | new GatewayMain().start(); 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /newim-gateway/src/main/java/com/whosbean/newim/gateway/GatewayConfig.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.gateway; 2 | 3 | import com.whosbean.newim.config.AbstractConfig; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by yaming_deng on 14-9-9. 10 | */ 11 | @Component 12 | public class GatewayConfig extends AbstractConfig { 13 | 14 | public static GatewayConfig current = null; 15 | 16 | private String gatewayHost; 17 | private Integer gatewayPort; 18 | private String gatewaySig; 19 | private String exchangeSig; 20 | 21 | @Override 22 | public String getConfName() { 23 | return "gateway"; 24 | } 25 | 26 | @Override 27 | public void afterPropertiesSet() throws Exception { 28 | super.afterPropertiesSet(); 29 | current = this; 30 | this.initGatewayInfo(); 31 | } 32 | 33 | private void initGatewayInfo(){ 34 | Map server = this.get(Map.class, "websocket"); 35 | gatewayHost = (String)server.get("ip"); 36 | gatewayPort = (Integer)server.get("port"); 37 | gatewaySig = gatewayHost+":"+ gatewayPort; 38 | 39 | server = this.get(Map.class, "exchange"); 40 | String host = (String)server.get("ip"); 41 | Integer port = (Integer)server.get("port"); 42 | exchangeSig = host+":"+ port; 43 | } 44 | 45 | public String getGatewayHost() { 46 | return gatewayHost; 47 | } 48 | 49 | public Integer getGatewayPort() { 50 | return gatewayPort; 51 | } 52 | 53 | public String getGatewaySig() { 54 | return gatewaySig; 55 | } 56 | 57 | public String getExchangeSig() { 58 | return exchangeSig; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /newim-gateway/src/main/java/com/whosbean/newim/gateway/GatewayServerNode.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.gateway; 2 | 3 | import com.whosbean.newim.server.ChatServerNode; 4 | import com.whosbean.newim.server.ServerNodeRoles; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * Created by yaming_deng on 14-9-9. 10 | */ 11 | @Component 12 | public class GatewayServerNode extends ChatServerNode { 13 | 14 | public static GatewayServerNode current = null; 15 | 16 | @Autowired 17 | private GatewayConfig gatewayConfig; 18 | 19 | @Override 20 | public void afterPropertiesSet() throws Exception { 21 | super.afterPropertiesSet(); 22 | current = this; 23 | } 24 | 25 | @Override 26 | protected String getRole() { 27 | return ServerNodeRoles.ROLE_EXCHANGE; 28 | } 29 | 30 | @Override 31 | protected String getName() { 32 | return gatewayConfig.getExchangeSig(); 33 | } 34 | 35 | @Override 36 | protected String getConf() { 37 | return gatewayConfig.getGatewaySig(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /newim-gateway/src/main/java/com/whosbean/newim/gateway/connection/ChannelsHolder.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.gateway.connection; 2 | 3 | import com.whosbean.newim.gateway.GatewayServerNode; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import io.netty.channel.Channel; 7 | import io.netty.channel.ChannelFuture; 8 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 9 | import io.netty.util.concurrent.Future; 10 | import io.netty.util.concurrent.GenericFutureListener; 11 | import org.slf4j.Logger; 12 | 13 | import java.util.concurrent.ConcurrentHashMap; 14 | 15 | /** 16 | * Created by yaming_deng on 14-9-9. 17 | */ 18 | public class ChannelsHolder { 19 | 20 | private static ConcurrentHashMap mapping = new ConcurrentHashMap(1024); 21 | 22 | /** 23 | * Add 24 | * @param c 25 | */ 26 | public static void add(final Channel c){ 27 | Channel exists = mapping.putIfAbsent(c.hashCode(), c); 28 | if (exists == null){ 29 | 30 | } 31 | } 32 | 33 | /** 34 | * 35 | * Remove 36 | * 37 | * @param c 38 | * @return 39 | */ 40 | public static boolean remove(final Channel c){ 41 | boolean flag = mapping.remove(c.hashCode()) != null; 42 | return flag; 43 | } 44 | 45 | /** 46 | * Get 47 | * 48 | * @param id 49 | * @return 50 | */ 51 | public static Channel get(final Integer id){ 52 | return mapping.get(id); 53 | } 54 | 55 | public static void ack(final Logger logger, final Channel ctx, String msg) { 56 | //回复客户端. 57 | if (logger.isDebugEnabled()){ 58 | logger.debug("ack: {}/{}", msg, ctx); 59 | } 60 | byte[] bytes = msg.getBytes(); 61 | ack(logger, ctx, bytes, null); 62 | } 63 | 64 | public static void ack(final Logger logger, final Channel ctx, byte[] bytes, final String chatPath) { 65 | if (ctx == null){ 66 | logger.error("argument ctx is NULL. "); 67 | return; 68 | } 69 | if (bytes == null || bytes.length == 0){ 70 | logger.error("argument bytes is NULL or size=0"); 71 | return; 72 | } 73 | 74 | final ByteBuf data = Unpooled.copiedBuffer(bytes); 75 | final ChannelFuture cf = ctx.writeAndFlush(new BinaryWebSocketFrame(data)); 76 | cf.addListener(new GenericFutureListener>() { 77 | @Override 78 | public void operationComplete(Future future) throws Exception { 79 | if(future.cause() != null){ 80 | if (chatPath != null){ 81 | //remove this client from members. 82 | GatewayServerNode.current.remConnection(chatPath, ctx.hashCode()); 83 | } 84 | logger.error("发送消息错误. ctx=" + ctx, future.cause()); 85 | remove(ctx); 86 | ctx.close(); 87 | }else{ 88 | if (logger.isDebugEnabled()){ 89 | logger.debug("发送消息成功. ctx={}, chat={}", ctx, chatPath); 90 | } 91 | } 92 | } 93 | }); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /newim-gateway/src/main/java/com/whosbean/newim/gateway/connection/WebSession.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.gateway.connection; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by yaming_deng on 14-9-9. 7 | */ 8 | public class WebSession implements Serializable { 9 | 10 | private String uid; 11 | 12 | public WebSession(String uid) { 13 | this.uid = uid; 14 | } 15 | 16 | public String getUid() { 17 | return uid; 18 | } 19 | 20 | public void setUid(String uid) { 21 | this.uid = uid; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /newim-gateway/src/main/java/com/whosbean/newim/gateway/exchange/MessageExchangeHandler.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.gateway.exchange; 2 | 3 | import com.google.common.base.Charsets; 4 | import com.whosbean.newim.entity.ExchangeMessage; 5 | import com.whosbean.newim.gateway.GatewayConfig; 6 | import com.whosbean.newim.gateway.GatewayServerNode; 7 | import com.whosbean.newim.gateway.connection.ChannelsHolder; 8 | import com.whosbean.newim.service.ChatMessageService; 9 | import com.whosbean.newim.service.ChatMessageServiceFactory; 10 | import io.netty.buffer.ByteBuf; 11 | import io.netty.buffer.Unpooled; 12 | import io.netty.channel.Channel; 13 | import io.netty.channel.ChannelFuture; 14 | import io.netty.channel.ChannelHandlerContext; 15 | import io.netty.channel.SimpleChannelInboundHandler; 16 | import io.netty.util.concurrent.Future; 17 | import io.netty.util.concurrent.GenericFutureListener; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | /** 22 | * Created by yaming_deng on 14-9-9. 23 | */ 24 | public class MessageExchangeHandler extends SimpleChannelInboundHandler { 25 | 26 | protected static Logger logger = LoggerFactory.getLogger(MessageExchangeHandler.class); 27 | 28 | private ChatMessageService chatMessageService; 29 | 30 | public MessageExchangeHandler(){ 31 | chatMessageService = ChatMessageServiceFactory.get(GatewayConfig.current); 32 | } 33 | 34 | /** 35 | * 接收到新的连接 36 | */ 37 | @Override 38 | public void channelActive(final ChannelHandlerContext ctx) throws Exception { 39 | logger.info("channelActive: " + ctx.channel().hashCode()); 40 | } 41 | 42 | @Override 43 | protected void channelRead0(ChannelHandlerContext ctx, byte[] msg) throws Exception { 44 | logger.info("channelRead: " + ctx.channel().hashCode()); 45 | /** 46 | * after receiving 47 | * 1. find client's channel 48 | * 2. send message in async way 49 | * 3. ack back to 50 | */ 51 | ExchangeMessage message = ExchangeMessage.newBuilder().mergeFrom(msg).build(); 52 | int total = 0; 53 | byte[] bytes = chatMessageService.getBytes(message.getMessageId()); 54 | if (bytes == null){ 55 | //JOIN, QUIT 56 | }else { 57 | for (Integer cid : message.getChannelIdList()) { 58 | Channel c = ChannelsHolder.get(cid); 59 | if (c != null) { 60 | ChannelsHolder.ack(logger, c, bytes, message.getChatPath()); 61 | total++; 62 | } else { 63 | GatewayServerNode.current.remConnection(message.getChatPath(), cid); 64 | } 65 | } 66 | } 67 | 68 | //TODO:分布式协同是否发完 69 | //GatewayServerNode.current.outMessage(message.msgPath); 70 | 71 | ack(ctx.channel(), total + ""); 72 | } 73 | 74 | private void ack(final Channel ctx, String msg) { 75 | final ByteBuf data = Unpooled.copiedBuffer(msg.getBytes(Charsets.UTF_8)); 76 | final ChannelFuture cf = ctx.writeAndFlush(data); 77 | cf.addListener(new GenericFutureListener>() { 78 | @Override 79 | public void operationComplete(Future future) throws Exception { 80 | if (future.cause() != null) { 81 | logger.error("发送消息错误. ctx=" + ctx, future.cause()); 82 | ctx.close(); 83 | } else { 84 | if (logger.isDebugEnabled()) { 85 | logger.debug("发送消息成功. ctx={}", ctx); 86 | } 87 | } 88 | } 89 | }); 90 | } 91 | 92 | /** 93 | * 连接异常 94 | */ 95 | @Override 96 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 97 | cause.printStackTrace(); 98 | ctx.close(); 99 | } 100 | 101 | /** 102 | * 连接断开,移除连接影射,客户端发起重连 103 | */ 104 | @Override 105 | public void channelInactive(ChannelHandlerContext ctx) 106 | throws Exception { 107 | logger.info("channelInactive: " + ctx.channel().hashCode()); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /newim-gateway/src/main/java/com/whosbean/newim/gateway/handler/ChannelAttributes.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.gateway.handler; 2 | 3 | import com.whosbean.newim.gateway.connection.WebSession; 4 | import io.netty.util.AttributeKey; 5 | 6 | /** 7 | * Created by yaming_deng on 14-9-9. 8 | */ 9 | public class ChannelAttributes { 10 | 11 | public static final AttributeKey SESSIOON_ATTR_KEY = 12 | AttributeKey.valueOf(ChannelAttributes.class.getName() + ".Session"); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /newim-gateway/src/main/java/com/whosbean/newim/gateway/handler/HttpSessionHandler.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.gateway.handler; 2 | 3 | import com.whosbean.newim.gateway.connection.WebSession; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import io.netty.handler.codec.http.FullHttpRequest; 7 | 8 | 9 | /** 10 | * Will attempt to find the requestor's session cookie. If the cookie is not 11 | * found an error message will be returned with a HTTP 400 response code. 12 | */ 13 | public class HttpSessionHandler extends SimpleChannelInboundHandler { 14 | 15 | public HttpSessionHandler() { 16 | super(false); 17 | } 18 | 19 | @Override 20 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 21 | throws Exception { 22 | cause.printStackTrace(); 23 | ctx.close(); 24 | } 25 | 26 | @Override 27 | protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) 28 | throws Exception { 29 | if (req.headers().contains("Cookie")) { 30 | String cookie = req.headers().get("Cookie"); 31 | System.out.println("Got Cookie: " + cookie); 32 | if (cookie != null) { 33 | String[] uid = cookie.split("="); 34 | ctx.channel().attr(ChannelAttributes.SESSIOON_ATTR_KEY).set(new WebSession(uid[1])); 35 | } 36 | } else { 37 | System.out.println("No Cookie in websocket request"); 38 | } 39 | 40 | ctx.pipeline().remove(this); // remConnection after auth'd 41 | ctx.fireChannelRead(req); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /newim-gateway/src/main/java/com/whosbean/newim/gateway/handler/WsConnectedHandler.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.gateway.handler; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelPipeline; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import io.netty.handler.codec.http.FullHttpRequest; 9 | import io.netty.util.CharsetUtil; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | * Created by yaming_deng on 14-9-5. 15 | */ 16 | public class WsConnectedHandler extends SimpleChannelInboundHandler 17 | { 18 | protected static Logger logger = LoggerFactory.getLogger(WsConnectedHandler.class); 19 | 20 | public WsConnectedHandler() { 21 | super(false); 22 | } 23 | 24 | private static final ByteBuf NOT_FOUND = 25 | Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("NOT FOUND", CharsetUtil.US_ASCII)); 26 | 27 | @Override 28 | protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) 29 | throws Exception 30 | { 31 | String uri = req.getUri(); 32 | // add websocket handler for the request uri where app lives 33 | ChannelPipeline pipeline = ctx.pipeline(); 34 | pipeline.addLast(new WsServerProtocolHandler(uri)); 35 | // now add our application handler 36 | pipeline.addLast(new WsMessageHandler()); 37 | // remConnection, app is attached and websocket handler in place 38 | pipeline.remove(this); 39 | // pass the request on 40 | ctx.fireChannelRead(req); 41 | } 42 | 43 | @Override 44 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 45 | super.channelInactive(ctx); 46 | logger.info("WsConnection inactive."); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /newim-gateway/src/main/java/com/whosbean/newim/gateway/handler/WsMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.gateway.handler; 2 | 3 | import com.whosbean.newim.entity.ChatMessage; 4 | import com.whosbean.newim.gateway.GatewayConfig; 5 | import com.whosbean.newim.gateway.GatewayServerNode; 6 | import com.whosbean.newim.gateway.connection.ChannelsHolder; 7 | import com.whosbean.newim.gateway.connection.WebSession; 8 | import com.whosbean.newim.service.ChatMessageService; 9 | import com.whosbean.newim.service.ChatMessageServiceFactory; 10 | import io.netty.buffer.ByteBuf; 11 | import io.netty.channel.ChannelFuture; 12 | import io.netty.channel.ChannelHandlerContext; 13 | import io.netty.channel.SimpleChannelInboundHandler; 14 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 15 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 16 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 17 | import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; 18 | import io.netty.util.concurrent.Future; 19 | import io.netty.util.concurrent.GenericFutureListener; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | public class WsMessageHandler extends SimpleChannelInboundHandler 24 | { 25 | 26 | protected static Logger logger = LoggerFactory.getLogger(WsMessageHandler.class); 27 | 28 | protected ChatMessageService chatMessageService; 29 | 30 | public WsMessageHandler() { 31 | chatMessageService = ChatMessageServiceFactory.get(GatewayConfig.current); 32 | } 33 | 34 | @Override 35 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 36 | throws Exception 37 | { 38 | ctx.close(); 39 | } 40 | 41 | /** 42 | * We implement this to catch the websocket handshake completing 43 | * successfully. At that point we'll setup this client connection. 44 | */ 45 | @Override 46 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) 47 | throws Exception 48 | { 49 | if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) { 50 | configureClient(ctx); 51 | } 52 | } 53 | 54 | /** 55 | * Should end up being called after websocket handshake completes. Will 56 | * configure this client for communication with the application. 57 | */ 58 | protected void configureClient(ChannelHandlerContext ctx) { 59 | logger.info("configureClient. to Check auth. ctx={}", ctx.channel()); 60 | ChannelsHolder.add(ctx.channel()); 61 | } 62 | 63 | /** 64 | * When a message is sent into the app by the connected user this is 65 | * invoked. 66 | */ 67 | @Override 68 | protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception 69 | { 70 | logger.info("GOT. " + frame); 71 | if (frame instanceof TextWebSocketFrame){ 72 | TextWebSocketFrame text = (TextWebSocketFrame)frame; 73 | logger.info(text.text()); 74 | ChannelFuture future = ctx.channel().writeAndFlush(new TextWebSocketFrame(text.text())); 75 | future.addListener(new GenericFutureListener>() { 76 | @Override 77 | public void operationComplete(Future future) throws Exception { 78 | logger.info("write to channels successful"); 79 | } 80 | }); 81 | 82 | }else if(frame instanceof BinaryWebSocketFrame){ 83 | BinaryWebSocketFrame b = (BinaryWebSocketFrame)frame; 84 | try { 85 | this.handleMessage(ctx, b.content()); 86 | } catch (Exception e) { 87 | logger.error(e.getMessage(), e); 88 | } 89 | } 90 | } 91 | 92 | private WebSession getSession(ChannelHandlerContext ctx){ 93 | WebSession session = ctx.channel().attr(ChannelAttributes.SESSIOON_ATTR_KEY).get(); 94 | return session; 95 | } 96 | 97 | protected void handleMessage(ChannelHandlerContext ctx, ByteBuf bytes) throws Exception { 98 | byte[] dd = new byte[bytes.readableBytes()]; 99 | bytes.readBytes(dd); 100 | WebSession session = getSession(ctx); 101 | ChatMessage.Builder builder = ChatMessage.newBuilder().mergeFrom(dd); 102 | ChatMessage chatMessage = builder.setSender(session.getUid()).build(); 103 | int value = chatMessage.getOp().getNumber(); 104 | if (value == ChatMessage.ChatOp.JOIN_VALUE){ 105 | this.chatMessageService.save(chatMessage); 106 | GatewayServerNode.current.join(ctx.channel(), chatMessage); 107 | GatewayServerNode.current.newMessage(chatMessage); 108 | }else if (value == ChatMessage.ChatOp.QUIT_VALUE){ 109 | this.chatMessageService.save(chatMessage); 110 | GatewayServerNode.current.quit(ctx.channel(), chatMessage); 111 | GatewayServerNode.current.newMessage(chatMessage); 112 | }else if (value == ChatMessage.ChatOp.CHAT_VALUE){ 113 | this.chatMessageService.save(chatMessage); 114 | GatewayServerNode.current.newMessage(chatMessage); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /newim-gateway/src/main/java/com/whosbean/newim/gateway/handler/WsServerProtocolHandler.java: -------------------------------------------------------------------------------- 1 | package com.whosbean.newim.gateway.handler; 2 | 3 | import com.whosbean.newim.gateway.connection.ChannelsHolder; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; 6 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 7 | import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Created by yaming_deng on 14-9-9. 15 | */ 16 | public class WsServerProtocolHandler extends WebSocketServerProtocolHandler { 17 | 18 | protected static Logger logger = LoggerFactory.getLogger(WsServerProtocolHandler.class); 19 | 20 | public WsServerProtocolHandler(String websocketPath) { 21 | super(websocketPath); 22 | } 23 | 24 | public WsServerProtocolHandler(String websocketPath, String subprotocols) { 25 | super(websocketPath, subprotocols); 26 | } 27 | 28 | public WsServerProtocolHandler(String websocketPath, String subprotocols, boolean allowExtensions) { 29 | super(websocketPath, subprotocols, allowExtensions); 30 | } 31 | 32 | @Override 33 | protected void decode(ChannelHandlerContext ctx, WebSocketFrame frame, List out) throws Exception { 34 | if (frame instanceof CloseWebSocketFrame) { 35 | ChannelsHolder.remove(ctx.channel()); 36 | super.decode(ctx, frame, out); 37 | return; 38 | } 39 | super.decode(ctx, frame, out); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /newim-gateway/src/main/resources/gateway.yaml: -------------------------------------------------------------------------------- 1 | websocket: 2 | ip: 127.0.0.1 3 | actors: 10 4 | workers: 10 5 | port: 8180 6 | 7 | exchange: 8 | ip: 127.0.0.1 9 | actors: 10 10 | workers: 10 11 | port: 8181 12 | 13 | bucket: 14 | engine: redis -------------------------------------------------------------------------------- /newim-gateway/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | utf-8 5 | 6 | 7 | %date [%thread] %-5level %logger{50} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | true 15 | UTF-8 16 | 17 | 18 | logs/gateway.%d{yyyy-MM-dd}.log 19 | 20 | 21 | 30 22 | 23 | 24 | %d{HH:mm:ss} [%thread] %-5level %logger{80} - %msg%n 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /newim-gateway/src/main/resources/redis.yaml: -------------------------------------------------------------------------------- 1 | host: 127.0.0.1 2 | port: 6379 3 | maxActive: 50 4 | maxIdle: 10 5 | maxWait: 5000 -------------------------------------------------------------------------------- /newim-gateway/src/main/resources/spring-gateway.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /newim-gateway/src/main/resources/zookeeper.yaml: -------------------------------------------------------------------------------- 1 | servers: 127.0.0.1:2181 #,127.0.0.1:2182,127.0.0.l:2183 2 | retry: 3 | interval: 1000 4 | limit: 3 -------------------------------------------------------------------------------- /newim-gateway/src/test/java/com/whosbean/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.whosbean; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /newim-pb/newim-pb.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /newim-pb/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | newim 5 | com.whosbean 6 | 1.0-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | newim-pb 11 | jar 12 | 13 | newim-pb 14 | http://maven.apache.org 15 | 16 | 17 | UTF-8 18 | 19 | 20 | 21 | 22 | junit 23 | junit 24 | 3.8.1 25 | test 26 | 27 | 28 | com.google.protobuf 29 | protobuf-java 30 | 2.5.0 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /newim-pb/src/main/java/com/whosbean/newim/entity/ChatMessageOrBuilder.java: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: entity_schema.proto 3 | 4 | package com.whosbean.newim.entity; 5 | 6 | public interface ChatMessageOrBuilder 7 | extends com.google.protobuf.MessageOrBuilder { 8 | 9 | // required string boxid = 1; 10 | /** 11 | * required string boxid = 1; 12 | */ 13 | boolean hasBoxid(); 14 | /** 15 | * required string boxid = 1; 16 | */ 17 | java.lang.String getBoxid(); 18 | /** 19 | * required string boxid = 1; 20 | */ 21 | com.google.protobuf.ByteString 22 | getBoxidBytes(); 23 | 24 | // required int32 group = 2 [default = 0]; 25 | /** 26 | * required int32 group = 2 [default = 0]; 27 | */ 28 | boolean hasGroup(); 29 | /** 30 | * required int32 group = 2 [default = 0]; 31 | */ 32 | int getGroup(); 33 | 34 | // optional string uuid = 3; 35 | /** 36 | * optional string uuid = 3; 37 | */ 38 | boolean hasUuid(); 39 | /** 40 | * optional string uuid = 3; 41 | */ 42 | java.lang.String getUuid(); 43 | /** 44 | * optional string uuid = 3; 45 | */ 46 | com.google.protobuf.ByteString 47 | getUuidBytes(); 48 | 49 | // optional string sender = 4; 50 | /** 51 | * optional string sender = 4; 52 | */ 53 | boolean hasSender(); 54 | /** 55 | * optional string sender = 4; 56 | */ 57 | java.lang.String getSender(); 58 | /** 59 | * optional string sender = 4; 60 | */ 61 | com.google.protobuf.ByteString 62 | getSenderBytes(); 63 | 64 | // optional string receiver = 5; 65 | /** 66 | * optional string receiver = 5; 67 | */ 68 | boolean hasReceiver(); 69 | /** 70 | * optional string receiver = 5; 71 | */ 72 | java.lang.String getReceiver(); 73 | /** 74 | * optional string receiver = 5; 75 | */ 76 | com.google.protobuf.ByteString 77 | getReceiverBytes(); 78 | 79 | // optional string body = 6; 80 | /** 81 | * optional string body = 6; 82 | */ 83 | boolean hasBody(); 84 | /** 85 | * optional string body = 6; 86 | */ 87 | java.lang.String getBody(); 88 | /** 89 | * optional string body = 6; 90 | */ 91 | com.google.protobuf.ByteString 92 | getBodyBytes(); 93 | 94 | // required .im.ChatMessage.MessageType mtype = 7 [default = SYNC]; 95 | /** 96 | * required .im.ChatMessage.MessageType mtype = 7 [default = SYNC]; 97 | */ 98 | boolean hasMtype(); 99 | /** 100 | * required .im.ChatMessage.MessageType mtype = 7 [default = SYNC]; 101 | */ 102 | com.whosbean.newim.entity.ChatMessage.MessageType getMtype(); 103 | 104 | // required .im.ChatMessage.ChatOp op = 8 [default = ACK]; 105 | /** 106 | * required .im.ChatMessage.ChatOp op = 8 [default = ACK]; 107 | */ 108 | boolean hasOp(); 109 | /** 110 | * required .im.ChatMessage.ChatOp op = 8 [default = ACK]; 111 | */ 112 | com.whosbean.newim.entity.ChatMessage.ChatOp getOp(); 113 | } 114 | -------------------------------------------------------------------------------- /newim-pb/src/main/java/com/whosbean/newim/entity/ChatStatusOrBuilder.java: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: entity_schema.proto 3 | 4 | package com.whosbean.newim.entity; 5 | 6 | public interface ChatStatusOrBuilder 7 | extends com.google.protobuf.MessageOrBuilder { 8 | 9 | // required string sender = 1; 10 | /** 11 | * required string sender = 1; 12 | */ 13 | boolean hasSender(); 14 | /** 15 | * required string sender = 1; 16 | */ 17 | java.lang.String getSender(); 18 | /** 19 | * required string sender = 1; 20 | */ 21 | com.google.protobuf.ByteString 22 | getSenderBytes(); 23 | 24 | // required int32 syncMark = 2; 25 | /** 26 | * required int32 syncMark = 2; 27 | */ 28 | boolean hasSyncMark(); 29 | /** 30 | * required int32 syncMark = 2; 31 | */ 32 | int getSyncMark(); 33 | } 34 | -------------------------------------------------------------------------------- /newim-pb/src/main/java/com/whosbean/newim/entity/EntitySchema.java: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: entity_schema.proto 3 | 4 | package com.whosbean.newim.entity; 5 | 6 | public final class EntitySchema { 7 | private EntitySchema() {} 8 | public static void registerAllExtensions( 9 | com.google.protobuf.ExtensionRegistry registry) { 10 | } 11 | static com.google.protobuf.Descriptors.Descriptor 12 | internal_static_im_ChatMessage_descriptor; 13 | static 14 | com.google.protobuf.GeneratedMessage.FieldAccessorTable 15 | internal_static_im_ChatMessage_fieldAccessorTable; 16 | static com.google.protobuf.Descriptors.Descriptor 17 | internal_static_im_ChatStatus_descriptor; 18 | static 19 | com.google.protobuf.GeneratedMessage.FieldAccessorTable 20 | internal_static_im_ChatStatus_fieldAccessorTable; 21 | static com.google.protobuf.Descriptors.Descriptor 22 | internal_static_im_ExchangeMessage_descriptor; 23 | static 24 | com.google.protobuf.GeneratedMessage.FieldAccessorTable 25 | internal_static_im_ExchangeMessage_fieldAccessorTable; 26 | 27 | public static com.google.protobuf.Descriptors.FileDescriptor 28 | getDescriptor() { 29 | return descriptor; 30 | } 31 | private static com.google.protobuf.Descriptors.FileDescriptor 32 | descriptor; 33 | static { 34 | java.lang.String[] descriptorData = { 35 | "\n\023entity_schema.proto\022\002im\"\343\002\n\013ChatMessag" + 36 | "e\022\r\n\005boxid\030\001 \002(\t\022\020\n\005group\030\002 \002(\005:\0010\022\014\n\004uu" + 37 | "id\030\003 \001(\t\022\016\n\006sender\030\004 \001(\t\022\020\n\010receiver\030\005 \001" + 38 | "(\t\022\014\n\004body\030\006 \001(\t\0220\n\005mtype\030\007 \002(\0162\033.im.Cha" + 39 | "tMessage.MessageType:\004SYNC\022\'\n\002op\030\010 \002(\0162\026" + 40 | ".im.ChatMessage.ChatOp:\003ACK\"i\n\013MessageTy" + 41 | "pe\022\r\n\tSmallText\020\000\022\014\n\010LongText\020\001\022\t\n\005AUDIO" + 42 | "\020\002\022\t\n\005IMAGE\020\003\022\t\n\005VIDEO\020\004\022\010\n\004FILE\020\005\022\010\n\004LI" + 43 | "NK\020\006\022\010\n\004SYNC\020\007\"/\n\006ChatOp\022\010\n\004JOIN\020\000\022\010\n\004QU" + 44 | "IT\020\001\022\010\n\004CHAT\020\002\022\007\n\003ACK\020\003\".\n\nChatStatus\022\016\n", 45 | "\006sender\030\001 \002(\t\022\020\n\010syncMark\030\002 \002(\005\"\177\n\017Excha" + 46 | "ngeMessage\022\021\n\tmessageId\030\001 \002(\t\022\017\n\007message" + 47 | "\030\002 \001(\t\022\020\n\010chatPath\030\003 \001(\t\022\022\n\nchatRoomId\030\004" + 48 | " \001(\t\022\017\n\007msgPath\030\005 \001(\t\022\021\n\tchannelId\030\006 \003(\005" + 49 | "B\035\n\031com.whosbean.newim.entityP\001" 50 | }; 51 | com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = 52 | new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { 53 | public com.google.protobuf.ExtensionRegistry assignDescriptors( 54 | com.google.protobuf.Descriptors.FileDescriptor root) { 55 | descriptor = root; 56 | internal_static_im_ChatMessage_descriptor = 57 | getDescriptor().getMessageTypes().get(0); 58 | internal_static_im_ChatMessage_fieldAccessorTable = new 59 | com.google.protobuf.GeneratedMessage.FieldAccessorTable( 60 | internal_static_im_ChatMessage_descriptor, 61 | new java.lang.String[] { "Boxid", "Group", "Uuid", "Sender", "Receiver", "Body", "Mtype", "Op", }); 62 | internal_static_im_ChatStatus_descriptor = 63 | getDescriptor().getMessageTypes().get(1); 64 | internal_static_im_ChatStatus_fieldAccessorTable = new 65 | com.google.protobuf.GeneratedMessage.FieldAccessorTable( 66 | internal_static_im_ChatStatus_descriptor, 67 | new java.lang.String[] { "Sender", "SyncMark", }); 68 | internal_static_im_ExchangeMessage_descriptor = 69 | getDescriptor().getMessageTypes().get(2); 70 | internal_static_im_ExchangeMessage_fieldAccessorTable = new 71 | com.google.protobuf.GeneratedMessage.FieldAccessorTable( 72 | internal_static_im_ExchangeMessage_descriptor, 73 | new java.lang.String[] { "MessageId", "Message", "ChatPath", "ChatRoomId", "MsgPath", "ChannelId", }); 74 | return null; 75 | } 76 | }; 77 | com.google.protobuf.Descriptors.FileDescriptor 78 | .internalBuildGeneratedFileFrom(descriptorData, 79 | new com.google.protobuf.Descriptors.FileDescriptor[] { 80 | }, assigner); 81 | } 82 | 83 | // @@protoc_insertion_point(outer_class_scope) 84 | } 85 | -------------------------------------------------------------------------------- /newim-pb/src/main/java/com/whosbean/newim/entity/ExchangeMessageOrBuilder.java: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: entity_schema.proto 3 | 4 | package com.whosbean.newim.entity; 5 | 6 | public interface ExchangeMessageOrBuilder 7 | extends com.google.protobuf.MessageOrBuilder { 8 | 9 | // required string messageId = 1; 10 | /** 11 | * required string messageId = 1; 12 | */ 13 | boolean hasMessageId(); 14 | /** 15 | * required string messageId = 1; 16 | */ 17 | java.lang.String getMessageId(); 18 | /** 19 | * required string messageId = 1; 20 | */ 21 | com.google.protobuf.ByteString 22 | getMessageIdBytes(); 23 | 24 | // optional string message = 2; 25 | /** 26 | * optional string message = 2; 27 | */ 28 | boolean hasMessage(); 29 | /** 30 | * optional string message = 2; 31 | */ 32 | java.lang.String getMessage(); 33 | /** 34 | * optional string message = 2; 35 | */ 36 | com.google.protobuf.ByteString 37 | getMessageBytes(); 38 | 39 | // optional string chatPath = 3; 40 | /** 41 | * optional string chatPath = 3; 42 | */ 43 | boolean hasChatPath(); 44 | /** 45 | * optional string chatPath = 3; 46 | */ 47 | java.lang.String getChatPath(); 48 | /** 49 | * optional string chatPath = 3; 50 | */ 51 | com.google.protobuf.ByteString 52 | getChatPathBytes(); 53 | 54 | // optional string chatRoomId = 4; 55 | /** 56 | * optional string chatRoomId = 4; 57 | */ 58 | boolean hasChatRoomId(); 59 | /** 60 | * optional string chatRoomId = 4; 61 | */ 62 | java.lang.String getChatRoomId(); 63 | /** 64 | * optional string chatRoomId = 4; 65 | */ 66 | com.google.protobuf.ByteString 67 | getChatRoomIdBytes(); 68 | 69 | // optional string msgPath = 5; 70 | /** 71 | * optional string msgPath = 5; 72 | */ 73 | boolean hasMsgPath(); 74 | /** 75 | * optional string msgPath = 5; 76 | */ 77 | java.lang.String getMsgPath(); 78 | /** 79 | * optional string msgPath = 5; 80 | */ 81 | com.google.protobuf.ByteString 82 | getMsgPathBytes(); 83 | 84 | // repeated int32 channelId = 6; 85 | /** 86 | * repeated int32 channelId = 6; 87 | */ 88 | java.util.List getChannelIdList(); 89 | /** 90 | * repeated int32 channelId = 6; 91 | */ 92 | int getChannelIdCount(); 93 | /** 94 | * repeated int32 channelId = 6; 95 | */ 96 | int getChannelId(int index); 97 | } 98 | -------------------------------------------------------------------------------- /newim-pb/src/main/pb/entity_schema.proto: -------------------------------------------------------------------------------- 1 | package im; 2 | option java_package = "com.whosbean.newim.entity"; 3 | option java_multiple_files = true; 4 | 5 | message ChatMessage { 6 | required string boxid = 1; 7 | required int32 group = 2 [default = 0]; 8 | optional string uuid = 3; 9 | optional string sender = 4; 10 | optional string receiver = 5; 11 | optional string body = 6; 12 | enum MessageType { 13 | SmallText = 0; 14 | LongText = 1; 15 | AUDIO = 2; 16 | IMAGE = 3; 17 | VIDEO = 4; 18 | FILE = 5; 19 | LINK = 6; 20 | SYNC = 7; 21 | } 22 | required MessageType mtype = 7 [default = SYNC]; 23 | 24 | enum ChatOp { 25 | JOIN = 0; 26 | QUIT = 1; 27 | CHAT = 2; 28 | ACK = 3; 29 | } 30 | required ChatOp op = 8 [default = ACK]; 31 | } 32 | 33 | message ChatStatus { 34 | required string sender = 1; 35 | required int32 syncMark = 2; 36 | } 37 | 38 | message ExchangeMessage { 39 | required string messageId = 1; 40 | optional string message = 2; 41 | optional string chatPath = 3; 42 | optional string chatRoomId = 4; 43 | optional string msgPath = 5; 44 | repeated int32 channelId = 6; 45 | } 46 | -------------------------------------------------------------------------------- /newim-pb/src/main/resources/redis.yaml: -------------------------------------------------------------------------------- 1 | host: 127.0.0.1 2 | port: 6379 3 | maxActive: 50 4 | maxIdle: 10 5 | maxWait: 5000 -------------------------------------------------------------------------------- /newim-pb/src/main/resources/zookeeper.yaml: -------------------------------------------------------------------------------- 1 | servers: 127.0.0.1:2181,127.0.0.1:2182,127.0.0.l:2183 2 | retry: 3 | interval: 1000 4 | limit: 3 -------------------------------------------------------------------------------- /newim-pb/src/test/java/com/whosbean/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.whosbean; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.whosbean 6 | newim 7 | 1.0-SNAPSHOT 8 | 9 | newim-gateway 10 | newim-chat 11 | newim-common 12 | newim-pb 13 | demo 14 | 15 | pom 16 | 17 | newim 18 | http://maven.apache.org 19 | 20 | 21 | UTF-8 22 | 4.0.23.Final 23 | 24 | 25 | 26 | 27 | junit 28 | junit 29 | 3.8.1 30 | test 31 | 32 | 33 | io.netty 34 | netty-all 35 | ${netty.version} 36 | 37 | 38 | org.springframework 39 | spring-context 40 | 4.0.0.RELEASE 41 | 42 | 43 | org.springframework 44 | spring-core 45 | 4.0.0.RELEASE 46 | 47 | 48 | org.apache.zookeeper 49 | zookeeper 50 | 3.4.6 51 | 52 | 53 | org.slf4j 54 | slf4j-log4j12 55 | 56 | 57 | log4j 58 | log4j 59 | 60 | 61 | io.netty 62 | netty 63 | 64 | 65 | 66 | 67 | com.google.protobuf 68 | protobuf-java 69 | 2.5.0 70 | 71 | 72 | com.google.guava 73 | guava 74 | 15.0 75 | 76 | 77 | org.slf4j 78 | slf4j-api 79 | 1.7.5 80 | 81 | 82 | org.slf4j 83 | log4j-over-slf4j 84 | 85 | 86 | 87 | 88 | org.slf4j 89 | jul-to-slf4j 90 | 1.5.11 91 | 92 | 93 | ch.qos.logback 94 | logback-classic 95 | 1.0.13 96 | 97 | 98 | org.apache.curator 99 | curator-recipes 100 | 2.6.0 101 | 102 | 103 | org.yaml 104 | snakeyaml 105 | 1.13 106 | 107 | 108 | org.apache.commons 109 | commons-lang3 110 | 3.2.1 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/main/java/com/whosbean/App.java: -------------------------------------------------------------------------------- 1 | package com.whosbean; 2 | 3 | /** 4 | * Hello world! 5 | * 6 | */ 7 | public class App 8 | { 9 | public static void main( String[] args ) 10 | { 11 | System.out.println( "Hello World!" ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/whosbean/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.whosbean; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | --------------------------------------------------------------------------------