GithubHelp home page GithubHelp logo

yuanrw / im Goto Github PK

View Code? Open in Web Editor NEW
675.0 23.0 314.0 634 KB

IM server based on netty. Provides a client jar. Integrate with your own login system.基于netty实现的IM服务端,提供客户端jar包,可集成自己的登录系统

Java 68.67% Groovy 21.47% Dockerfile 1.82% Shell 8.03%
netty webflux instant-messaging spring-boot spring java mircoservices im proto protobuf

im's Introduction

IM

Build Status codecov codebeat badge release last commit

IM is a lightweight instant messaging server. It also provides a client jar,allows you to develop your own client.For example,with spring boot. It's able to login with your own login system or with ldap.

中文

Features

  • One to one text/file message
  • Sent/Delivered/Read message
  • Ldap Authentication
  • Authenticate with individual login system
  • Horizontal expansion
  • Provide client jar

Quick Start

Prepare

We use docker to quick start IM.

# detect if the docker environment is avaliable.
docker -v
# clone the repository
git clone [email protected]:yuanrw/IM.git

Start

cd IM/docker
docker-compose up

There is a simple sample in the container,it starts serveral clients and send messages to their friends randomly,printing logs which are similar with followed:

......
2019-08-11 17:29:13.451 client-samples - [Olive] get a msg: 357980857883037697 has been read
2019-08-11 17:29:13.452 client-samples - [yuanrw] get a msg: 357980857887232002 has been read
2019-08-11 17:29:13.452 client-samples - [xianyy] get a msg: 357980857887232001 has been read
2019-08-11 17:29:13.452 client-samples - [Adela] get a msg: 357980857874649089 has been read
2019-08-11 17:29:13.452 client-samples - [Bella] get a msg: 357980857874649090 has been read
2019-08-11 17:29:13.452 client-samples - [Tom] get a msg: 357980857887232000 has been read



sentMsg: 51, readMsg: 51, hasSentAck: 51, hasDeliveredAck: 51, hasReadAck: 51, hasException: 0



2019-08-11 17:29:15.114 client-samples - [Bella]get a msg: 357980866275840002 has been sent
2019-08-11 17:29:15.114 client-samples - [Adela]get a msg: 357980866275840000 has been sent
2019-08-11 17:29:15.114 client-samples - [Cynthia]get a msg: 357980866275840003 has been sent
......

Distributed Deploy

mvn clean package -DskipTests

get $SERVICE_NAME-$VERSION-bin.zip under dir /target

Environment Requirement

  • java 8+
  • mysql 5.7+
  • rabbitmq
  • redis

Start

Start services with the following order: rest-web --> transfer -->connector

Here are the steps for start rest-web,transfer and connector are similar with it.

rest-web

  1. Unzip
unzip rest-web-$VERSION-bin.zip
cd rest-web-$VERSION
  1. Update the config file
server.port=8082

# your log path
log.path=

# your jdbc config
spring.datasource.url=
......

# your redis config
spring.redis.host=
......

# your rabbitmq config
spring.rabbitmq.host=
......
  1. run the sql in the file rest.sql

  2. start server

java -jar rest-web-$VERSION.jar --spring.config.location=application.properties

transfer

java -jar -Dconfig=transfer.properties transfer-$VERSION.jar

connector

java -jar -Dconfig=connector.properties connector-$VERSION.jar

Nginx Config

All services are available to expand horizontally,connections need to be kept alive between each client and connector server. A sample nginx config:

stream {
	upstream backend {
        # connector services port
        server 127.0.0.1:9081         max_fails=3 fail_timeout=30s;
        server 127.0.0.1:19081			max_fails=3 fail_timeout=30s;
	}

    server {
        # to keep a persistent connection
        listen 9999 so_keepalive=on;
        proxy_timeout 1d;
        proxy_pass backend;
    }
}

Login

There is a simple usable login system in IM. IM also support the following two ways to authenticate.

ldap

We use open ldap as an example. update application.properties

spi.user.impl.class=com.yrw.im.rest.web.spi.impl.LdapUserSpiImpl

# the following config should be replace with your own config
spring.ldap.base=dc=example,dc=org
# admin
spring.ldap.username=cn=admin,dc=example,dc=org
spring.ldap.password=admin
spring.ldap.urls=ldap://127.0.0.1:389
# user filter,use the filter to search user when login in
spring.ldap.searchFilter=
# search base eg. ou=dev
ldap.searchBase=
# user objectClass
ldap.mapping.objectClass=inetOrgPerson
ldap.mapping.loginId=uid
ldap.mapping.userDisplayName=gecos
ldap.mapping.email=mail
java -jar rest-web-$VERSION.jar --spring.config.location=application.properties

individual login system

  1. Implement the spi in com.yrw.im.rest.spi.UserSpi
public interface UserSpi<T extends UserBase> {

    /**
     * get user by username and password, return user(id can not be null)
     * if username and password are right, else return null.
     * <p>
     * be sure that your password has been properly encrypted
     *
     * @param username
     * @param pwd
     * @return
     */
    T getUser(String username, String pwd);

    /**
     * get user by id, if id not exist then return null.
     *
     * @param id
     * @return
     */
    T getById(String id);
}
  1. Update application.properties
# your implement class full name
spi.user.impl.class=
  1. Build
mvn clean package -DskipTests

Use client jar

A client demo:

MyClient.java

im's People

Contributors

dependabot[bot] avatar sage417 avatar yuanrw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

im's Issues

im 聊天

学习下im聊天编程,谢谢分享

windows 启动报错 是什么原因呢

rest-web | wait-for-it.sh: im-mysql:3306 is available after 10 seconds
rest-web | wait-for-it.sh: waiting for im-redis:6379 without a timeout
rest-web | wait-for-it.sh: im-redis:6379 is available after 0 seconds
rest-web | wait-for-it.sh: waiting for im-rabbit:5672 without a timeout
rest-web | wait-for-it.sh: im-rabbit:5672 is available after 0 seconds
rest-web | JAVA_HOME: /opt/java/openjdk
rest-web | OpenJDK 64-Bit Server VM warning: Ignoring option PermSize; support was removed in 8.0
rest-web | OpenJDK 64-Bit Server VM warning: Ignoring option MaxPermSize; support was removed in 8.0
rest-web | 2020-08-23 22:55:10.399 rest [main] ERROR o.s.boot.SpringApplication - Application run failed
rest-web | java.lang.IllegalStateException: Logback configuration error detected:
rest-web | ERROR in ch.qos.logback.core.rolling.RollingFileAppender[file] - openFile(/tmp/IM_logs/rest.log,true) call failed. java.io.FileNotFoundException: /tmp/IM_logs/rest.
log (Is a directory)
rest-web | at org.springframework.boot.logging.logback.LogbackLoggingSystem.loadConfiguration(LogbackLoggingSystem.java:169) ~[spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]

rest-web | at org.springframework.boot.logging.AbstractLoggingSystem.initializeWithConventions(AbstractLoggingSystem.java:82) ~[spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE
]
rest-web | at org.springframework.boot.logging.AbstractLoggingSystem.initialize(AbstractLoggingSystem.java:60) ~[spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
rest-web | at org.springframework.boot.logging.logback.LogbackLoggingSystem.initialize(LogbackLoggingSystem.java:117) ~[spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
rest-web | at org.springframework.boot.context.logging.LoggingApplicationListener.initializeSystem(LoggingApplicationListener.java:293) ~[spring-boot-2.1.2.RELEASE.jar:2.1
.2.RELEASE]
rest-web | at org.springframework.boot.context.logging.LoggingApplicationListener.initialize(LoggingApplicationListener.java:266) ~[spring-boot-2.1.2.RELEASE.jar:2.1.2.REL
EASE]
rest-web | at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEnvironmentPreparedEvent(LoggingApplicationListener.java:229) ~[spring-boot-
2.1.2.RELEASE.jar:2.1.2.RELEASE]
rest-web | at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:202) ~[spring-boot-2.1.2.RELEASE.jar:2
.1.2.RELEASE]
rest-web | at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172) ~[spring-context-5.1.4.RELEA
SE.jar:5.1.4.RELEASE]
rest-web | at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165) ~[spring-context-5.1.4.RELEASE
.jar:5.1.4.RELEASE]
rest-web | at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139) ~[spring-context-5.1.4.RELEASE
.jar:5.1.4.RELEASE]
rest-web | at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127) ~[spring-context-5.1.4.RELEASE
.jar:5.1.4.RELEASE]
rest-web | at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:75) ~[spring-boot-2.1.2.RELEASE.jar:2.1
.2.RELEASE]
rest-web | at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:54) ~[spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEA
SE]
rest-web | at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:347) ~[spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
rest-web | at org.springframework.boot.SpringApplication.run(SpringApplication.java:306) ~[spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
rest-web | at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) ~[spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
rest-web | at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) ~[spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
rest-web | at com.github.yuanrw.im.rest.web.RestStarter.main(RestStarter.java:19) ~[rest-web-1.0.0.jar:na]
rest-web | Exception in thread "main" java.lang.IllegalStateException: Logback configuration error detected:
rest-web | ERROR in ch.qos.logback.core.rolling.RollingFileAppender[file] - openFile(/tmp/IM_logs/rest.log,true) call failed. java.io.FileNotFoundException: /tmp/IM_logs/rest.
log (Is a directory)
rest-web | at org.springframework.boot.logging.logback.LogbackLoggingSystem.loadConfiguration(LogbackLoggingSystem.java:169)
rest-web | at org.springframework.boot.logging.AbstractLoggingSystem.initializeWithConventions(AbstractLoggingSystem.java:82)
rest-web | at org.springframework.boot.logging.AbstractLoggingSystem.initialize(AbstractLoggingSystem.java:60)
rest-web | at org.springframework.boot.logging.logback.LogbackLoggingSystem.initialize(LogbackLoggingSystem.java:117)
rest-web | at org.springframework.boot.context.logging.LoggingApplicationListener.initializeSystem(LoggingApplicationListener.java:293)
rest-web | at org.springframework.boot.context.logging.LoggingApplicationListener.initialize(LoggingApplicationListener.java:266)
rest-web | at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEnvironmentPreparedEvent(LoggingApplicationListener.java:229)
rest-web | at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:202)
rest-web | at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
rest-web | at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
rest-web | at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
rest-web | at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127)
rest-web | at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:75)
rest-web | at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:54)
rest-web | at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:347)
rest-web | at org.springframework.boot.SpringApplication.run(SpringApplication.java:306)
rest-web | at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260)
rest-web | at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248)
rest-web | at com.github.yuanrw.im.rest.web.RestStarter.main(RestStarter.java:19)
rest-web | SERVICE_NAME started....
rest-web exited with code 0
transfer | nc: bad address 'rest-web'
transfer | nc: bad address 'rest-web'
transfer | nc: bad address '

connector转发的Ack.AckMsg MsgType为READ消息无法被ack,connector会一直发送消息

connector发送的Ack.AckMsg 或者Chat.ChatMsg无法被客户端Ack,connector后面会一直发送

com.github.yuanrw.im.connector.service.ConnectorToClientService

public void doChatToClientOrTransferAndFlush(Chat.ChatMsg chat) {
        boolean onTheMachine = sendMsg(chat.getDestId(), chat.getId(),
            cid -> Chat.ChatMsg.newBuilder().mergeFrom(chat).setId(IdWorker.nextId(cid)).build());

        //send ack to from id
        if (onTheMachine) {
            ClientConn conn = clientConnContext.getConnByUserId(chat.getFromId());
            if (conn == null) {
                ChannelHandlerContext ctx = ConnectorTransferHandler.getOneOfTransferCtx(System.currentTimeMillis());
                ctx.writeAndFlush(getDelivered(ctx.channel().attr(Conn.NET_ID).get(), chat));
            } else {
                //need wait for ack
                Ack.AckMsg delivered = getDelivered(conn.getNetId(), chat);
                ServerAckWindow.offer(conn.getUserId(), delivered.getId(), delivered, m -> conn.getCtx().writeAndFlush(m));
            }
        }
    }

private boolean sendMsg(String destId, Long msgId, Function<Serializable, Message> generateMsg) {
        Conn conn = clientConnContext.getConnByUserId(destId);
        if (conn == null) {
            ChannelHandlerContext ctx = ConnectorTransferHandler.getOneOfTransferCtx(System.currentTimeMillis());
            ctx.writeAndFlush(generateMsg.apply(ctx.channel().attr(Conn.NET_ID).get()));
            return false;
        } else {
            //the user is connected to this machine
            //won 't save chat histories
            Message message = generateMsg.apply(conn.getNetId());
            ServerAckWindow.offer(conn.getNetId(), msgId, message, m -> conn.getCtx().writeAndFlush(m));
            return true;
        }
    }

doChatToClientOrTransferAndFlush()发送delivered ack时,ServerAckWindow.offer的第一个参数是conn.getNetId()
sendMsg()的ServerAckWindow.offer的msgId参数应该是message的新id吗?

com.github.yuanrw.im.client.handler.ClientConnectorHandler

public void channelActive(ChannelHandlerContext ctx) throws Exception {
        this.ctx = ctx;
        serverAckWindow = new ServerAckWindow(IdWorker.uuid(), 10, Duration.ofSeconds(5));
        clientAckWindow = new ClientAckWindow(5);
        clientMsgListener.online();
    }

这里生成了connectId,com.github.yuanrw.im.client.ImClient也生成了connnectId,两个不一样,后面ChatApi发送消息时拿不到ServerAckWindow实例

关于系统的消息ID的生成策略与使用ClientAckWindow判断消息顺序疑问?

com.github.yuanrw.im.common.util.IdWorker

    public static Long nextId(Serializable connectorId) {
        return sessionMap.computeIfAbsent(connectorId,
            key -> new AtomicLong(0)).incrementAndGet();
    }

dalao,
这个ID生成的策略是不是还没完成呀?

这个根据ID判断消息是否顺序的,是不是要根据自己ID的生成策略,然后才能继续编写?

com.github.yuanrw.im.common.domain.ack.ClientAckWindow

    private boolean isContinuous(Long msgId) {
        //如果是本次会话的第一条消息
        if (first.compareAndSet(true, false)) {
            return true;
        } else {
            //不是第一条消息,则按照公式算(如果同时有好几条第一条消息,除了真正的第一条,其他会返回false)
            return msgId - lastId.get() == 1;
        }
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.