Mapping active users and disconnecting if they exceed 3 concurrent users in java

2

I would like to know how I could map the users that connect based on their unique id's and id session so that when there are more than 3 sessions for this id, the users who connected first are removed from the HashMap and so on.

Example:

UserID:1 Sessão:1989448
UserID:1 Sessão:2848484
UserID:1 Sessão:84848

When a new connection was made, if there were 3 connections, the older one would be disconnected / removed from the hashmap by getting as below.

UserID:1 Sessão:2848484
UserID:1 Sessão:84848
UserID:1 Sessão:4848880

Java code:

public void onHTTPCupertinoStreamingSessionCreate(HTTPStreamerSessionCupertino httpSession) {
    String User_Session = httpSession.getSessionId();
    String Client_ID = httpSession.getProperties().getPropertyStr("sql_client_id");
    //adiciona ao hashmap o Client_ID e o User_Session.
}

public void onHTTPCupertinoStreamingSessionDestroy(IHTTPStreamerSession httpSession) {
    String User_Session = httpSession.getSessionId();
    //remove do hashmap o Client_ID baseado na sessão User_Session
}

public void KillSession(int SessionId){
    IApplicationInstance Instance = applicationInstance;
    IHTTPStreamerSession sessions = Instance.getHTTPStreamerSessions().get(SessionId);
    sessions.rejectSession();
}

//Checa e remove do hash map se houver mais de 3 conexões para o mesmo Client_ID invocando o KillSession

The Client_ID is the id of the user in the database, the User_Session is the unique session in the wowza generated for each connection, this session does not have equal values, that is, if a same Client_ID connects more than once, this value will be different for each of your sessions.

The Client_ID in hashmap serves to count how many connections the user in question has, and to do what is discussed here.

That is, basically my difficulty is to mount the hashmap, how could I do that?

    
asked by anonymous 06.01.2017 / 17:00

1 answer

2

First, let's assume you have a IdUsuario class that represents (as the name says), the id of some user. This class must be immutable and must implement the equals and hashCode methods properly. If you prefer, you can use Long or String in place, but I'm going to assume that the id will be something more complicated. I will assume that there is a class IdSession in the same way that it encapsulates session id of wowza.

A very simple example of these classes would be this:

public final class IdUsuario {
    private final String id;

    public IdUsuario(String id) {
        if (id == null) throw new IllegalArgumentException();
        this.id = id;
    }

    @Override
    public int hashCode() {
        return id.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return (obj instanceof IdUsuario) && id.equals(((IdUsuario) obj).id);
    }
}
public final class IdSession {
    private final String id;

    public IdSession(String id) {
        if (id == null) throw new IllegalArgumentException();
        this.id = id;
    }

    @Override
    public int hashCode() {
        return id.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return (obj instanceof IdSession) && id.equals(((IdSession) obj).id);
    }
}

However, if you want to enrich these classes with more data that you consider relevant to identify users and sessions, feel free to.

In the same way, I will assume that the session data is stored in a SessaoUsuario interface that has these methods:

void notificarDesconexao();
void notificarConexao();

So you use the singleton pattern to have a session repository:

public class RepositorioSessoes {

    private static final int SESSOES_POR_USUARIO = 3;
    private static final RepositorioSessoes REPOSITORIO = new RepositorioSessoes();

    public static RepositorioSessoes instance() {
        return REPOSITORIO;
    }

    private final Map<IdUsuario, GrupoSessaoUsuario> grupos;

    private RepositorioSessoes() {
        sessoes = new ConcurrentHashMap<>();
    }

    public void conectar(
           IdUsuario idUsuario,
           IdSession idSession,
           BiFunction<IdUsuario, IdSession, SessaoUsuario> criaSessoes)
    {
       GrupoSessaoUsuario grupo = sessoes.computeIfAbsent(idUsuario, k -> new GrupoSessaoUsuario(k, SESSOES_POR_USUARIO));
       grupo.conectar(idSession, criaSessoes);
    }

    public void desconectarTodos(IdUsuario id) {
        GrupoSessaoUsuario grupo = sessoes.get(id);
        if (grupo == null) return;
        grupo.limpar();
        sessoes.remove(id);
    }

    private static class GrupoSessaoUsuario {
        private final IdUsuario idUsuario;
        private final int limite;
        private final Map<IdSession, SessaoUsuario> sessoes;

        public GrupoSessaoUsuario(IdUsuario idUsuario, int limite) {
            this.idUsuario = idUsuario;
            this.sessoes = new LinkedHashMap<>(limite);
            this.limite = limite;
        }

        public synchronized void conectar(
            IdSession idSession,
            BiFunction<IdUsuario, IdSession, SessaoUsuario> criaSessoes)
        {
            SessaoUsuario novaSessao = null;
            if (sessoes.containsKey(idSession)) {
                novaSessao = sessoes.remove(idSession);
            } else if (sessoes.size() >= limite) {
                Iterator<SessaoUsuario> it = sessoes.values().iterator();
                it.next().notificarDesconexao();
                it.remove();
            }
            if (novaSessao != null) novaSessao = criaSessoes.apply(idUsuario, idSession);
            sessoes.put(idSession, novaSessao);
            novaSessao.notificarConexao();
        }

        public synchronized void limpar() {
           for (SessaoUsuario s : sessoes.values()) {
               s.notificarDesconexao();
           }
        }
    }
}

Whenever the user connects, you call the conectar(IdUsuario, IdSession, BiFunction<IdUsuario, IdSession, SessaoUsuario>) method. When you want to give kill , call desconectarTodos(IdUsuario) . The approach used here is not to create a thread to control this, but to create an object to control it.

The conectar method is a bit tricky to use because of this BiFunction , but it's not very difficult to do it. Let's suppose you have somewhere a function to create a SessaoUsuario like this:

public SessaoUsuario criarSessao(IdUsuario idUsuario, IdSession idSession) {
    ...
}

So, you would call it like this:

IdUsuario idUsuario = ...;
IdSession idSession = ...;
RepositorioSessoes.instance().conectar(idUsuario, idSession, this::criarSessao);

Or, you can use a constructor of SessaoUsuario :

public SessaoUsuario(IdUsuario idUsuario, IdSession idSession) {
    ...
}

So, you would call it like this:

IdUsuario idUsuario = ...;
IdSession idSession = ...;
RepositorioSessoes.instance().conectar(idUsuario, idSession, SessaoUsuario::new);

The class RepositorioSessoes is in charge to call when pertinent and necessary, the notificarConexao and notificarDesconexao methods.

The inner class GrupoSessaoUsuario (which is not public) manages all three user sessions. When it connects to an existing session, it goes to the end of the queue (in this case, this becomes the newest one). If there are already three sessions, it will remove the oldest.

The methods of the GrupoSessaoUsuario class are synchronized to ensure that two concurrent threads on different threads do not end up cluttering the GrupoSessaoUsuario internal state. This synchronization occurs on the GrupoSessaoUsuario object, and since each user must have one and only one instance of this object, then different threads from different users will not compete for this object, only threads from the same user will do so. The only place this object is created is in the method computeIfAbsent of ConcurrentHashMap " which has the guarantee of being atomic, and therefore there will be only one instance of this for each IdUsuario and therefore one for each user.

This class should work both in case you never reuse session ids and in case you always reuse them.

The above implementation can be simplified somewhat if your IdSession is implemented in a way that you can reach IdUsuario directly (for example, if IdSession has a field with IdUsuario ).

    
06.01.2017 / 20:32