Jetsever开源项目学习(四)Session学习
接口Session.java
package org.menacheri.jetserver.app; import java.util.List; import org.menacheri.jetserver.communication.MessageSender; import org.menacheri.jetserver.communication.MessageSender.Fast; import org.menacheri.jetserver.communication.MessageSender.Reliable; import org.menacheri.jetserver.event.Events; import org.menacheri.jetserver.event.Event; import org.menacheri.jetserver.event.EventDispatcher; import org.menacheri.jetserver.event.EventHandler; public interface Session { /** * session status types */ enum Status { NOT_CONNECTED, CONNECTING, CONNECTED, CLOSED } Object getId(); void setId(Object id); void setAttribute(String key, Object value); Object getAttribute(String key); void removeAttribute(String key); void onEvent(Event event); EventDispatcher getEventDispatcher(); boolean isWriteable(); void setWriteable(boolean writeable); /** * A session would not have UDP capability when created. Depending on the * network abilities of the client, it can request UDP communication to be * enabled with the LOGIN_UDP and CONNECT_UDP events of the {@link Events} * class. Once UDP is enabled this flag will be set to true on the session. * * @return Returns true if the a UDP {@link MessageSender} instance is * attached to this session, else false. */ boolean isUDPEnabled(); /** * A session would not have UDP capability when created. Depending on the * network abilities of the client, it can request UDP communication to be * enabled with the LOGIN_UDP and CONNECT_UDP events of the {@link Events} * class. Once UDP {@link MessageSender} instance is attached to the * session, this method should be called with flag to true to signal that * the session is now UDP enabled. * * @param isEnabled * Should be true in most use cases. It is used to signal that * the UDP {@link MessageSender} has been attached to session. */ void setUDPEnabled(boolean isEnabled); boolean isShuttingDown(); long getCreationTime(); long getLastReadWriteTime(); void setStatus(Status status); Status getStatus(); boolean isConnected(); void addHandler(EventHandler eventHandler); void removeHandler(EventHandler eventHandler); List<EventHandler> getEventHandlers(int eventType); void close(); public abstract void setUdpSender(Fast udpSender); public abstract Fast getUdpSender(); public abstract void setTcpSender(Reliable tcpSender); public abstract Reliable getTcpSender(); }
DefaultSession.java
package org.menacheri.jetserver.app.impl; import java.util.HashMap; import java.util.List; import java.util.Map; import org.menacheri.jetserver.app.Session; import org.menacheri.jetserver.communication.MessageSender.Fast; import org.menacheri.jetserver.communication.MessageSender.Reliable; import org.menacheri.jetserver.event.Event; import org.menacheri.jetserver.event.EventDispatcher; import org.menacheri.jetserver.event.EventHandler; import org.menacheri.jetserver.event.impl.EventDispatchers; import org.menacheri.jetserver.service.UniqueIDGeneratorService; import org.menacheri.jetserver.service.impl.SimpleUniqueIdGenerator; /** * The default implementation of the session class. This class is responsible * for receiving and sending events. For receiving it uses the * {@link #onEvent(Event)} method and for sending it uses the * {@link EventDispatcher} fireEvent method. The Method {@link #setId(Object)} * will throw {@link IllegalArgumentException} in this implementation class. * * @author Abraham Menacherry * */ public class DefaultSession implements Session { /** * session id */ protected final Object id; /** * event dispatcher */ protected EventDispatcher eventDispatcher; /** * session parameters */ protected final Map<String, Object> sessionAttributes; protected final long creationTime; protected long lastReadWriteTime; protected Status status; protected boolean isWriteable; /** * Life cycle variable to check if the session is shutting down. If it is, then no * more incoming events will be accepted. */ protected volatile boolean isShuttingDown; protected boolean isUDPEnabled; protected Reliable tcpSender = null; protected Fast udpSender = null; protected DefaultSession(SessionBuilder sessionBuilder) { // validate variables and provide default values if necessary. Normally // done in the builder.build() method, but done here since this class is // meant to be overriden and this could be easier. sessionBuilder.validateAndSetValues(); this.id = sessionBuilder.id; this.eventDispatcher = sessionBuilder.eventDispatcher; this.sessionAttributes = sessionBuilder.sessionAttributes; this.creationTime = sessionBuilder.creationTime; this.status = sessionBuilder.status; this.lastReadWriteTime = sessionBuilder.lastReadWriteTime; this.isWriteable = sessionBuilder.isWriteable; this.isShuttingDown = sessionBuilder.isShuttingDown; this.isUDPEnabled = sessionBuilder.isUDPEnabled; } /** * This class is roughly based on Joshua Bloch's Builder pattern. Since * Session class will be extended by child classes, the * {@link #validateAndSetValues()} method on this builder is actually called * by the {@link DefaultSession} constructor for ease of use. May not be good * design though. * * @author Abraham, Menacherry * */ public static class SessionBuilder { /** * Used to set a unique id on the incoming sessions to this room. */ protected static final UniqueIDGeneratorService ID_GENERATOR_SERVICE = new SimpleUniqueIdGenerator(); protected Object id = null; protected EventDispatcher eventDispatcher = null; protected Map<String, Object> sessionAttributes = null; protected long creationTime = 0L; protected long lastReadWriteTime = 0L; protected Status status = Status.NOT_CONNECTED; protected boolean isWriteable = true; protected volatile boolean isShuttingDown = false; protected boolean isUDPEnabled = false;// By default UDP is not enabled. public Session build() { return new DefaultSession(this); } /** * This method is used to validate and set the variables to default * values if they are not already set before calling build. This method * is invoked by the constructor of SessionBuilder. <b>Important!</b> * Builder child classes which override this method need to call * super.validateAndSetValues(), otherwise you could get runtime NPE's. */ protected void validateAndSetValues(){ if (null == id) { id = String.valueOf(ID_GENERATOR_SERVICE.generateFor(DefaultSession.class)); } if (null == eventDispatcher) { eventDispatcher = EventDispatchers.newJetlangEventDispatcher(null,null); } if(null == sessionAttributes) { sessionAttributes = new HashMap<String, Object>(); } creationTime = System.currentTimeMillis(); } public Object getId() { return id; } public SessionBuilder id(final String id) { this.id = id; return this; } public SessionBuilder eventDispatcher(final EventDispatcher eventDispatcher) { this.eventDispatcher = eventDispatcher; return this; } public SessionBuilder sessionAttributes(final Map<String, Object> sessionAttributes) { this.sessionAttributes = sessionAttributes; return this; } public SessionBuilder creationTime(long creationTime) { this.creationTime = creationTime; return this; } public SessionBuilder lastReadWriteTime(long lastReadWriteTime) { this.lastReadWriteTime = lastReadWriteTime; return this; } public SessionBuilder status(Status status) { this.status = status; return this; } public SessionBuilder isWriteable(boolean isWriteable) { this.isWriteable = isWriteable; return this; } public SessionBuilder isShuttingDown(boolean isShuttingDown) { this.isShuttingDown = isShuttingDown; return this; } public SessionBuilder isUDPEnabled(boolean isUDPEnabled) { this.isUDPEnabled = isUDPEnabled; return this; } } @Override public void onEvent(Event event) { if(!isShuttingDown){ eventDispatcher.fireEvent(event); } } @Override public Object getId() { return id; } @Override public void setId(Object id) { throw new IllegalArgumentException("id cannot be set in this implementation, since it is final"); } @Override public EventDispatcher getEventDispatcher() { return eventDispatcher; } @Override public void addHandler(EventHandler eventHandler) { eventDispatcher.addHandler(eventHandler); } @Override public void removeHandler(EventHandler eventHandler) { eventDispatcher.removeHandler(eventHandler); } @Override public List<EventHandler> getEventHandlers(int eventType) { return eventDispatcher.getHandlers(eventType); } @Override public Object getAttribute(String key) { return sessionAttributes.get(key); } @Override public void removeAttribute(String key) { sessionAttributes.remove(key); } @Override public void setAttribute(String key, Object value) { sessionAttributes.put(key, value); } @Override public long getCreationTime() { return creationTime; } @Override public long getLastReadWriteTime() { return lastReadWriteTime; } public void setLastReadWriteTime(long lastReadWriteTime) { this.lastReadWriteTime = lastReadWriteTime; } @Override public Status getStatus() { return status; } @Override public void setStatus(Status status) { this.status = status; } @Override public boolean isConnected() { return this.status == Status.CONNECTED; } @Override public boolean isWriteable() { return isWriteable; } @Override public void setWriteable(boolean isWriteable) { this.isWriteable = isWriteable; } /** * Not synchronized because default implementation does not care whether a * duplicated message sender is created. * * @see org.menacheri.jetserver.app.Session#isUDPEnabled() */ @Override public boolean isUDPEnabled() { return isUDPEnabled; } @Override public void setUDPEnabled(boolean isEnabled) { this.isUDPEnabled = isEnabled; } @Override public synchronized void close() { isShuttingDown = true; eventDispatcher.close(); if(null != tcpSender){ tcpSender.close(); tcpSender = null; } if(null != udpSender){ udpSender.close(); udpSender = null; } this.status = Status.CLOSED; } @Override public boolean isShuttingDown() { return isShuttingDown; } public Map<String, Object> getSessionAttributes() { return sessionAttributes; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; DefaultSession other = (DefaultSession) obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; return true; } @Override public Reliable getTcpSender() { return tcpSender; } @Override public void setTcpSender(Reliable tcpSender) { this.tcpSender = tcpSender; } @Override public Fast getUdpSender() { return udpSender; } @Override public void setUdpSender(Fast udpSender) { this.udpSender = udpSender; } }
PlayerSession.java
这个接口是玩家和服务器间的通信模型,他声明了一些方法,设置和获取用户,游戏房间以及session所使用的通信协议
package org.menacheri.jetserver.app; import org.menacheri.jetserver.event.Event; import org.menacheri.jetserver.protocols.Protocol; /** * This interface model's a human player's session to jetserver. It declares * methods to get and set the {@link Player}, the {@link GameRoom} to which * this session will connect and the network {@link Protocol} that will be used * for communication. * * @author Abraham Menacherry * */ public interface PlayerSession extends Session { /** * Each session is associated with a {@link Player}. This is the actual * human or machine using this session. * * @return Returns that associated Player object or null if it is not * associated yet. */ public abstract Player getPlayer(); /** * Each user session is attached to a game room. This method is used to retrieve that * game room object. * * @return Returns the associated game room object or null if none is * associated. */ public abstract GameRoom getGameRoom(); /** * Method used to set the game room for a particular session. * * @param gameRoom * The gameRoom object to set. */ public abstract void setGameRoom(GameRoom gameRoom); /** * Get the {@link Protocol} associated with this session. * * @return Returns the associated protocol instance. */ public Protocol getProtocol(); /** * Set the network protocol on the user session. * * @param protocol * The {@link Protocol} to set. */ public void setProtocol(Protocol protocol); /** * The event to be send to the {@link GameRoom} to which the PlayerSession * belongs. Behavior is unspecified if message is sent when a room change is * taking place. * * @param event The event to send to the {@link GameRoom} */ public void sendToGameRoom(Event event); }
DefaultPlayerSession.java
package org.menacheri.jetserver.app.impl; import org.jetlang.channels.MemoryChannel; import org.menacheri.jetserver.app.GameRoom; import org.menacheri.jetserver.app.Player; import org.menacheri.jetserver.app.PlayerSession; import org.menacheri.jetserver.concurrent.LaneStrategy.LaneStrategies; import org.menacheri.jetserver.event.Event; import org.menacheri.jetserver.event.EventDispatcher; import org.menacheri.jetserver.event.impl.EventDispatchers; import org.menacheri.jetserver.protocols.Protocol; /** * This implementation of the {@link PlayerSession} interface is used to both * receive and send messages to a particular player using the * {@link #onEvent(org.menacheri.jetserver.event.Event)}. Broadcasts from the * {@link GameRoom} are directly patched to the {@link EventDispatcher} which * listens on the room's {@link MemoryChannel} for events and in turn publishes * them to the listeners. * * @author Abraham Menacherry * */ public class DefaultPlayerSession extends DefaultSession implements PlayerSession { /** * Each session belongs to a Player. This variable holds the reference. */ final protected Player player; /** * Each incoming connection is made to a game room. This reference holds the * association to the game room. */ protected GameRoom parentGameRoom; /** * This variable holds information about the type of binary communication * protocol to be used with this session. */ protected Protocol protocol; protected DefaultPlayerSession(PlayerSessionBuilder playerSessionBuilder) { super(playerSessionBuilder); this.player = playerSessionBuilder.player; this.parentGameRoom = playerSessionBuilder.parentGameRoom; this.protocol = playerSessionBuilder.protocol; } public static class PlayerSessionBuilder extends SessionBuilder { protected Player player = null; protected GameRoom parentGameRoom; protected Protocol protocol; public PlayerSession build() { return new DefaultPlayerSession(this); } public PlayerSessionBuilder player(Player player) { this.player = player; return this; } public PlayerSessionBuilder parentGameRoom(GameRoom parentGameRoom) { if (null == parentGameRoom) { throw new IllegalArgumentException( "GameRoom instance is null, session will not be constructed"); } this.parentGameRoom = parentGameRoom; return this; } @Override protected void validateAndSetValues() { if (null == eventDispatcher) { eventDispatcher = EventDispatchers.newJetlangEventDispatcher( parentGameRoom, LaneStrategies.GROUP_BY_ROOM); } super.validateAndSetValues(); } public PlayerSessionBuilder protocol(Protocol protocol) { this.protocol = protocol; return this; } } @Override public Player getPlayer() { return player; } public GameRoom getGameRoom() { return parentGameRoom; } public void setGameRoom(GameRoom gameRoom) { this.parentGameRoom = gameRoom; } @Override public Protocol getProtocol() { return protocol; } @Override public void setProtocol(Protocol protocol) { this.protocol = protocol; } @Override public synchronized void close() { if (!isShuttingDown) { super.close(); parentGameRoom.disconnectSession(this); } } @Override public void sendToGameRoom(Event event) { parentGameRoom.send(event); } @Override public String toString() { return "PlayerSession [id=" + id + "player=" + player + ", parentGameRoom=" + parentGameRoom + ", protocol=" + protocol + ", isShuttingDown=" + isShuttingDown + "]"; } }
GameRoomSession.java
package org.menacheri.jetserver.app.impl; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ExecutorService; import org.menacheri.jetserver.app.Game; import org.menacheri.jetserver.app.GameRoom; import org.menacheri.jetserver.app.Player; import org.menacheri.jetserver.app.PlayerSession; import org.menacheri.jetserver.app.Session; import org.menacheri.jetserver.concurrent.LaneStrategy; import org.menacheri.jetserver.concurrent.LaneStrategy.LaneStrategies; import org.menacheri.jetserver.event.Event; import org.menacheri.jetserver.event.EventHandler; import org.menacheri.jetserver.event.NetworkEvent; import org.menacheri.jetserver.event.impl.EventDispatchers; import org.menacheri.jetserver.event.impl.NetworkEventListener; import org.menacheri.jetserver.protocols.Protocol; import org.menacheri.jetserver.service.GameStateManagerService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class GameRoomSession extends DefaultSession implements GameRoom { private static final Logger LOG = LoggerFactory.getLogger(GameRoomSession.class); /** * The name of the game room, preferably unique across multiple games. */ protected String gameRoomName; /** * The parent {@link SimpleGame} reference of this game room. */ protected Game parentGame; /** * Each game room has separate state manager instances. This variable will * manage the state for all the {@link DefaultPlayer}s connected to this game room. */ protected GameStateManagerService stateManager; /** * The set of sessions in this object. */ protected Set<PlayerSession> sessions; /** * Each game room has its own protocol for communication with client. */ protected Protocol protocol; protected GameRoomSession(GameRoomSessionBuilder gameRoomSessionBuilder) { super(gameRoomSessionBuilder); this.sessions = gameRoomSessionBuilder.sessions; this.parentGame = gameRoomSessionBuilder.parentGame; this.gameRoomName = gameRoomSessionBuilder.gameRoomName; this.protocol = gameRoomSessionBuilder.protocol; if(null == gameRoomSessionBuilder.eventDispatcher) { this.eventDispatcher = EventDispatchers.newJetlangEventDispatcher( this, gameRoomSessionBuilder.laneStrategy); } } public static class GameRoomSessionBuilder extends SessionBuilder { protected Set<PlayerSession> sessions; protected Game parentGame; protected String gameRoomName; protected Protocol protocol; protected LaneStrategy<String, ExecutorService, GameRoom> laneStrategy; @Override protected void validateAndSetValues() { if (null == id) { id = String.valueOf(ID_GENERATOR_SERVICE.generateFor(GameRoomSession.class)); } if(null == sessionAttributes) { sessionAttributes = new HashMap<String, Object>(); } if (null == sessions) { sessions = new HashSet<PlayerSession>(); } if (null == laneStrategy) { laneStrategy = LaneStrategies.GROUP_BY_ROOM; } creationTime = System.currentTimeMillis(); } public GameRoomSessionBuilder sessions(Set<PlayerSession> sessions) { this.sessions = sessions; return this; } public GameRoomSessionBuilder parentGame(Game parentGame) { this.parentGame = parentGame; return this; } public GameRoomSessionBuilder gameRoomName(String gameRoomName) { this.gameRoomName = gameRoomName; return this; } public GameRoomSessionBuilder protocol(Protocol protocol) { this.protocol = protocol; return this; } public GameRoomSessionBuilder laneStrategy( LaneStrategy<String, ExecutorService, GameRoom> laneStrategy) { this.laneStrategy = laneStrategy; return this; } } @Override public PlayerSession createPlayerSession(Player player) { PlayerSession playerSession = getSessionInstance(player); return playerSession; } @Override public abstract void onLogin(PlayerSession playerSession); @Override public synchronized boolean connectSession(PlayerSession playerSession) { if (!isShuttingDown) { playerSession.setStatus(Session.Status.CONNECTING); sessions.add(playerSession); playerSession.setGameRoom(this); LOG.trace("Protocol to be applied is: {}",protocol.getClass().getName()); protocol.applyProtocol(playerSession,true); createAndAddEventHandlers(playerSession); playerSession.setStatus(Session.Status.CONNECTED); afterSessionConnect(playerSession); return true; // TODO send event to all other sessions? } else { LOG.warn("Game Room is shutting down, playerSession {} {}", playerSession,"will not be connected!"); return false; } } @Override public void afterSessionConnect(PlayerSession playerSession) { } public synchronized boolean disconnectSession(PlayerSession playerSession) { final boolean removeHandlers = this.eventDispatcher.removeHandlersForSession(playerSession); //playerSession.getEventDispatcher().clear(); // remove network handlers of the session. return (removeHandlers && sessions.remove(playerSession)); } @Override public void send(Event event) { onEvent(event); } @Override public void sendBroadcast(NetworkEvent networkEvent) { onEvent(networkEvent); } @Override public synchronized void close() { isShuttingDown = true; for(PlayerSession session: sessions) { session.close(); } } public PlayerSession getSessionInstance(Player player) { PlayerSession playerSession = Sessions.newPlayerSession(this,player); return playerSession; } @Override public Set<PlayerSession> getSessions() { return sessions; } @Override public void setSessions(Set<PlayerSession> sessions) { this.sessions = sessions; } @Override public String getGameRoomName() { return gameRoomName; } @Override public void setGameRoomName(String gameRoomName) { this.gameRoomName = gameRoomName; } @Override public Game getParentGame() { return parentGame; } @Override public void setParentGame(Game parentGame) { this.parentGame = parentGame; } @Override public void setStateManager(GameStateManagerService stateManager) { this.stateManager = stateManager; } @Override public GameStateManagerService getStateManager() { return stateManager; } @Override public Protocol getProtocol() { return protocol; } @Override public void setProtocol(Protocol protocol) { this.protocol = protocol; } @Override public boolean isShuttingDown() { return isShuttingDown; } public void setShuttingDown(boolean isShuttingDown) { this.isShuttingDown = isShuttingDown; } /** * Method which will create and add event handlers of the player session to * the Game Room's EventDispatcher. * * @param playerSession * The session for which the event handlers are created. */ protected void createAndAddEventHandlers(PlayerSession playerSession) { // Create a network event listener for the player session. EventHandler networkEventHandler = new NetworkEventListener(playerSession); // Add the handler to the game room's EventDispatcher so that it will // pass game room network events to player session session. this.eventDispatcher.addHandler(networkEventHandler); LOG.trace("Added Network handler to " + "EventDispatcher of GameRoom {}, for session: {}", this, playerSession); } }
1.做了一张UML图纸,梳理一下Session实现的架构。
2.梳理一下Builder实现方式的要点:
- Session接口中的方法都是包级保护的(不带任何修饰符)
- PlayerSession接口中的方法都是Public的
- DefaultSession中的属性都是protected的
- DefaultSession的构造器是protected的
- DefaultSession的内部类(SessionBuilder)是public static的。
- DefaultSession内部类(SessionBuilder)中的属性是protected的,validateAndSetValues()方法是protected的,提供给子类调用(注意子类需调用这个 validateAndSetValues方法,否则会出现NPE。因为这个函数是用来初始化三个空引用,如果子类中只是初始化了其中的一个或几个,就需要调用父类的这个方法)
- DefaultSession内部类(SessionBuilder)中其他设置属性的方法都是public的
- DefaultPlayerSession和GameRoomSession是DefaultSession的子类,域和方法包括内部类的访问权限和DefaultSession设置的差不多。
3.对isShuttingDown变量进行了学习,这个voliate变量是在DefaultSession中定义的
Life cycle variable to check if the session is shutting down. If it is, then no more incoming events will be accepted.
GameRoomSession中isShuttingDown是GameRoomSession的生命周期标志位,主要是在更新PlayerSession结合的时候需要访问到。如果对话终止则不能接受新的PlayerSession
4.GameRoomSession同步块的学习
GameRoomSession中主要有三个同步块方法,主要是对PlayerSession集合(sessions对象)的访问进行了同步