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
).
Where do you get this
session id
?– Victor Stafusa
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 theIHTTPStreamerSession 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.– Florida