Netty 인터넷 채팅방의 세션 관리

웹을 쓴 학생들은 세션이라는 것에 익숙할 것이다.브라우저가 처음으로 서버와 연결을 맺을 때 서버는 자동으로 세션을 분배합니다.Session은 사용자가 로그인 인증을 받았는지 판단하거나 사용자의 각종 정보를 저장할 수 있습니다.
사실 Session은 자주 사용하는 기술입니다.웹이든 게임 서비스든 인터넷 데스크톱 프로그램이든 모두 세션의 그림자가 있다.Session이 있으면 다양한 개인 파라미터를 저장할 수 있고, session을 이용하여 클라이언트에게 메시지를 보낼 수 있습니다.프로그램이 클라이언트에 대한 관리를 매우 편리하게 했다.
미나 IO 프레임워크는 기본적으로 IoSession이라는 대상이 있는데 넷티는 없어요.그래서 우리는 스스로 Session 추상을 만들 수 있다.
Session에 관해서 우리는 그것이 이런 작용을 하기를 바란다.
1. 클라이언트 링크가 처음 활성화되었을 때 서버는 이를 위해Session을 생성합니다.
2. Session을 사용하여 사용자에게 메시지를 보낼 수 있습니다.
3. 중요하고 지속적인 사용자 정보를 저장할 수 있다.
4. 서비스 쪽에서만 생명주기의 소멸을 제어할 수 있다.
public class IoSession {
	
	private static final Logger logger = LoggerFactory.getLogger(IoSession.class);

	/**  channel */
	private Channel channel;

	private User user;

	/** ip  */
	private String ipAddr;

	private boolean reconnected;
	
	/**  ,   */
	private Map attrs = new HashMap<>();

	public IoSession() {

	}

	public IoSession(Channel channel) {
		this.channel = channel;
		this.ipAddr = ChannelUtils.getIp(channel);
	}
	
	public void setUser(User user) {
		this.user = user;
	}
	
	/**
	 *  
	 * @param packet
	 */
	public void sendPacket(Packet packet) {
		if (packet == null) {
			return;
		}
		if (channel != null) {
			channel.writeAndFlush(packet);
		}
	}

	public String getIpAddr() {
		return ipAddr;
	}

	public void setIpAddr(String ipAddr) {
		this.ipAddr = ipAddr;
	}

	public boolean isReconnected() {
		return reconnected;
	}

	public void setReconnected(boolean reconnected) {
		this.reconnected = reconnected;
	}

	public User getUser() {
		return user;
	}
	
	public boolean isClose() {
		if (channel == null) {
			return true;
		}
		return !channel.isActive() ||
			   !channel.isOpen();
	}
	
	/**
	 *  session 
	 * @param reason {@link SessionCloseReason}
	 */
	public void close(SessionCloseReason reason) {
		try{
			if (this.channel == null) {
				return;
			}
			if (channel.isOpen()) {
				channel.close();
				logger.info("close session[{}], reason is {}", getUser().getUserId(), reason);
			}else{
				logger.info("session[{}] already close, reason is {}", getUser().getUserId(), reason);
			}
		}catch(Exception e){
		}
	}

}

Session이 닫히는 데는 일련의 원인이 있을 수 있기 때문에 우리는 마지막으로 여러 가지 원인을 하나하나 저장한다.
package com.kingston.net;

public enum SessionCloseReason {
	
	/**   */
	NORMAL,
	
	/**   */
	OVER_TIME,
	

}

Netty에서 채널은 통신의 캐리어로 채널의 각종 조작을 편리하게 하기 위해 채널의 도구 클래스를 추가했습니다(Channel Utils.java)
public final class ChannelUtils {
	
	public static AttributeKey SESSION_KEY = AttributeKey.valueOf("session");
	
	/**
	 *  
	 * @param channel
	 * @param session
	 * @return
	 */
	public static boolean addChannelSession(Channel channel, IoSession session) {
		Attribute sessionAttr = channel.attr(SESSION_KEY);
		return sessionAttr.compareAndSet(null, session);
	}
	
	public static IoSession getSessionBy(Channel channel) {
		Attribute sessionAttr = channel.attr(SESSION_KEY);
		return sessionAttr.get() ;
	}
	
	public static String getIp(Channel channel) {
		return ((InetSocketAddress)channel.remoteAddress()).getAddress().toString().substring(1);
	}

}

이전에 사용자 통신을 관리하는 데 사용했던 IoSession 도구 클래스도 이에 따라 변경되었습니다.
public enum ServerManager {

	INSTANCE;

	private Logger logger = LoggerFactory.getLogger(ServerManager.class);

	/**  ( ) */ 
	private Map session2UserIds  = new ConcurrentHashMap<>();

	/**  id  */
	private ConcurrentMap userId2Sessions = new ConcurrentHashMap<>();


	public void sendPacketTo(Packet pact,Long userId){
		if(pact == null || userId <= 0) return;

		IoSession session = userId2Sessions.get(userId);
		if (session != null) {
			session.sendPacket(pact);
		}
	}

	/**
	 *   
	 */
	public void sendPacketToAllUsers(Packet pact){
		if(pact == null ) return;

		userId2Sessions.values().forEach( (session) -> session.sendPacket(pact));
	}

	/**
	 *   
	 */
	public void sendPacketTo(Packet pact,ChannelHandlerContext targetContext ){
		if(pact == null || targetContext == null) return;
		targetContext.writeAndFlush(pact);
	}


	public IoSession getSessionBy(long userId) {
		return this.userId2Sessions.get(userId);
	}

	public boolean registerSession(User user, IoSession session) {

		session.setUser(user);
		userId2Sessions.put(user.getUserId(), session);

		logger.info("[{}] registered...", user.getUserId());

		return true;
	}

	/**
	 *    
	 */
	public void ungisterUserContext(Channel context ){
		if(context  == null){

			return;
		}
		IoSession session = ChannelUtils.getSessionBy(context);
		Long userId = session2UserIds.remove(session);
		userId2Sessions.remove(userId);
		if (session != null) {
			session.close(SessionCloseReason.OVER_TIME);
		}
	}

}

IoSession에 가입한 후 이전의 업무는 약간의 수정이 필요합니다. 예를 들어 클라이언트 체인이 구축된 후에 새로운session 대상을 만들어야 합니다.
MessageTransportHandler 클래스에서 추가 방법
@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		if (!ChannelUtils.addChannelSession(ctx.channel(), new IoSession(ctx.channel()))) {
			ctx.channel().close();
			logger.error("Duplicate session,IP=[{}]",ChannelUtils.getIp(ctx.channel()));
		}
	}

모든 코드는github에서 관리됨
서버 코드 이동 --> netty 채팅방 서버
클라이언트 코드 이동 --> netty 채팅방 클라이언트

좋은 웹페이지 즐겨찾기