Mapping active and disconnecting users to exceed 3 users

Asked

Viewed 104 times

2

I wonder how I could map the users who connect based on their unique id’s and id session so that when there are more than 3 sessions for that 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 and left as below for example.

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 user id in the database, the User_session is the single session in the generated Wowza for each connection, this session does not have equal values, that is, if the same Client_id connects more than once, this value will be different for each of your sessions.

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

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

  • Where do you get this session id?

  • Session id is the unique id of the current client session in Wowza, roughly speaking it’s like your identity, and it serves many things, because it’s unique to every active connection. It is obtained through the command: String Session_ID = httpSession.getSessionId(); I didn’t put the IHTTPStreamerSession httpSession porque não era muito relevante, ou seja, são duas Strings a do Client_ID e a do Session_ID que devem ficar no hashmap, o Client_ID é obtido do SQL e o Session_ID do wowza, quando uma nova conexão for feita pelo mesmo Client_ID, os valores mais antigos devem ser removidos do hashmap.

1 answer

2

First, let’s assume you have a class IdUsuario representing (as the name says), the id of some user. This class must be immutable and must implement the methods equals and hashCode properly. If you prefer, you can use Long or String instead, but I will assume that the id will end up being something more complicated. I will assume that there is a class IdSession in the same molds that encapsulates the session id 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 pertinent to identifying users and sessions, feel free to.

Similarly, I will assume that session data is stored in a class or interface SessaoUsuario who has these methods:

void notificarDesconexao();
void notificarConexao();

Therefore, you use the Singleton standard 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 method conectar(IdUsuario, IdSession, BiFunction<IdUsuario, IdSession, SessaoUsuario>). When you want to give the Kill, call the desconectarTodos(IdUsuario). The approach used here is not to create a thread to control it, but to create an object to control it.

The method conectar is a little tricky to use because of this BiFunction, but it’s not too hard no. Let’s assume that you have somewhere a function to create a SessaoUsuario thus:

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

So you’d call him that:

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

Or you can use a SessaoUsuario:

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

So you’d call him that:

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

The class RepositorioSessoes if it is necessary to call where appropriate and necessary, the methods notificarConexao and notificarDesconexao.

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

The methods of the class GrupoSessaoUsuario are synchronized to ensure that two simultaneous connections run on different threads do not end up messing up the internal state GrupoSessaoUsuario. This synchronization occurs in the object GrupoSessaoUsuario, 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 where this object is created is in the call to method computeIfAbsent of ConcurrentHashMap which is guaranteed to be atomic, and therefore there will only be a single 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 a little in case your IdSession be implemented in such a way that it is possible to achieve IdUsuario directly (for example, if IdSession has a field with reference to IdUsuario).

  • There are no identical sessions, each new session is unique, even for the same Client_id. What can be repeated is Client_id.

  • @Florida I edited the answer.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.