写过web的同学们应该对Session这个东西很熟悉。浏览器第一次与服务器建立连接的时候,服务器就会自动为之分配一个Session。Session可以用来判断用户是否经过登录验证,也可以保存用户的各种信息。

其实,Session是很常用的技术。不管是WEB,还是游戏服务,还是联网的桌面程序,都有session的身影。有了Session,我们可以向里面保存各种个人参数,还可以利用session来向客户端发送消息。极大方便了程序对客户端的管理。

Mina IO框架默认有IoSession这个对象,Netty可就没有了。所以我们可以自己创建一个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<String, Object> 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是通讯的载体,为了方便对channel的各种操作,加了一个channel的工具类(ChannelUtils.java)

public final class ChannelUtils {

	public static AttributeKey<IoSession> SESSION_KEY = AttributeKey.valueOf("session");

	/**
	 * 添加新的会话
	 * @param channel
	 * @param session
	 * @return
	 */
	public static boolean addChannelSession(Channel channel, IoSession session) {
		Attribute<IoSession> sessionAttr = channel.attr(SESSION_KEY);
		return sessionAttr.compareAndSet(null, session);
	}

	public static IoSession getSessionBy(Channel channel) {
		Attribute<IoSession> 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<IoSession, Long> session2UserIds  = new ConcurrentHashMap<>();

	/** 缓存用户id与对应的会话 */
	private ConcurrentMap<Long, IoSession> 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聊天室客户端

Netty网络聊天室之会话管理的更多相关文章

  1. Netty网络聊天(一) 聊天室实战

    首发地址; Netty网络聊天(一) 聊天室实战 之前做过一个IM的项目,里面涉及了基本的聊天功能,所以注意这系列的文章不是练习,不含基础和逐步学习的部分,直接开始实战和思想引导,基础部分需要额外的去 ...

  2. php websocket-网页实时聊天之PHP实现websocket(ajax长轮询和websocket都可以时间网络聊天室)

    php websocket-网页实时聊天之PHP实现websocket(ajax长轮询和websocket都可以时间网络聊天室) 一.总结 1.ajax长轮询和websocket都可以时间网络聊天室 ...

  3. Qt NetWork即时通讯网络聊天室(基于TCP)

    本文使用QT的网络模块来创建一个网络聊天室程序,主要包括以下功能: 1.基于TCP的可靠连接(QTcpServer.QTcpSocket) 2.一个服务器,多个客户端 3.服务器接收到某个客户端的请求 ...

  4. 基于Linux的TCP网络聊天室

    1.实验项目名称:基于Linux的TCP网络聊天室 2.实验目的:通过TCP完成多用户群聊和私聊功能. 3.实验过程: 通过socket建立用户连接并传送用户输入的信息,分别来写客户端和服务器端,利用 ...

  5. Python3 网络通信 网络聊天室 文件传输

    Python3 网络通信 网络聊天室 文件传输 功能描述 该项目将实现一个文字和文件传输的客户端和服务器程序通信应用程序.它将传输和接收视频文件. 文本消息必须通过TCP与服务器通信,而客户端自己用U ...

  6. Java NIO示例:多人网络聊天室

    一个多客户端聊天室,支持多客户端聊天,有如下功能: 功能1: 客户端通过Java NIO连接到服务端,支持多客户端的连接 功能2:客户端初次连接时,服务端提示输入昵称,如果昵称已经有人使用,提示重新输 ...

  7. 使用socket搭建一个网络聊天室

    #服务器端import socket import threading #创建一个TCP端 sock = socket.socket(socket.AF_INET, socket.SOCK_STREA ...

  8. C#实例之简单聊天室(状态管理)

    前言        状态管理是在同一页或不同页的多个请求发生时,维护状态和页信息的过程.因为Web应用程序的通信协议使用了无状态的HTTP协议,所以当客户端请求页面时,ASP.NET服务器端都会重新生 ...

  9. Java WebSocket实现网络聊天室(群聊+私聊)

    1.简单说明 在网上看到一份比较nice的基于webSocket网页聊天项目,准备看看学习学习,如是有了这篇文章!原博主博客:http://blog.csdn.net/Amayadream/artic ...

随机推荐

  1. shell 算术运算符

    算术运算符 下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 20: 运算符 说明 举例 + 加法 `expr $a + $b` 结果为 30. - 减法 `expr $a - $b` ...

  2. python2 安装scrapy出现错误提示解决办法~

    首先:set STATICBUILD=true && pip install lxml 安装环境: windows7操作系统,已经正确安装python,pip. 使用pip功能安装Sc ...

  3. java数组声明和变式--record1

    ​ java声明数组方式: String[] namelist; int numlist[];//此声明为动态声明,不能指定长度,numlist[10] 静态声明的方式: int a[]={1,2,3 ...

  4. cmd 常用操作

    2017-08-24 16:05:28 cd : change directory(目录) 功能:显示当前目录 类型:内部命令 格式:CD[盘符:][路径名][子目录名] 说明:CD命令不能改变当前所 ...

  5. Java8 Lambda代码备份

    简单研究了一下,贴出来,相当于笔记 import java.lang.reflect.*; import java.util.ArrayList; import java.util.List; imp ...

  6. 在linux环境下编译C++ 程序

    单个源文件生成可执行程序 下面是一个保存在文件 helloworld.cpp 中一个简单的 C++ 程序的代码: 单个源文件生成可执行程序 /* helloworld.cpp */ #include ...

  7. 问题✅:render json的格式支持。to_json被改成as_json,功能一样

    class StudentsController < ApplicationController def show @student = Student.find(params[:id]) re ...

  8. 微信小程序之更新上一页数据(十二)

    小程序开发过程中经常有这种需求,需要把当前页面数据传递给上一个页面,但是wx.navigateBack()无法传递数据. 一般的办法是把当前页面数据放入本地缓存,上一个页面再从缓存中取出. 除此之外还 ...

  9. 最小生成树(模板 Kruskal)

    Description 某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离.省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达 ...

  10. ORACLE workflow审批界面显示附件信息和附件的下载链接(转)

    原文: ORACLE workflow审批界面显示附件信息和附件的下载链接 EBS获取附件URL 代码如下: CREATE OR REPLACE PACKAGE cux_attachment_util ...