Netty网络聊天室之会话管理
写过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网络聊天室之会话管理的更多相关文章
- Netty网络聊天(一) 聊天室实战
首发地址; Netty网络聊天(一) 聊天室实战 之前做过一个IM的项目,里面涉及了基本的聊天功能,所以注意这系列的文章不是练习,不含基础和逐步学习的部分,直接开始实战和思想引导,基础部分需要额外的去 ...
- php websocket-网页实时聊天之PHP实现websocket(ajax长轮询和websocket都可以时间网络聊天室)
php websocket-网页实时聊天之PHP实现websocket(ajax长轮询和websocket都可以时间网络聊天室) 一.总结 1.ajax长轮询和websocket都可以时间网络聊天室 ...
- Qt NetWork即时通讯网络聊天室(基于TCP)
本文使用QT的网络模块来创建一个网络聊天室程序,主要包括以下功能: 1.基于TCP的可靠连接(QTcpServer.QTcpSocket) 2.一个服务器,多个客户端 3.服务器接收到某个客户端的请求 ...
- 基于Linux的TCP网络聊天室
1.实验项目名称:基于Linux的TCP网络聊天室 2.实验目的:通过TCP完成多用户群聊和私聊功能. 3.实验过程: 通过socket建立用户连接并传送用户输入的信息,分别来写客户端和服务器端,利用 ...
- Python3 网络通信 网络聊天室 文件传输
Python3 网络通信 网络聊天室 文件传输 功能描述 该项目将实现一个文字和文件传输的客户端和服务器程序通信应用程序.它将传输和接收视频文件. 文本消息必须通过TCP与服务器通信,而客户端自己用U ...
- Java NIO示例:多人网络聊天室
一个多客户端聊天室,支持多客户端聊天,有如下功能: 功能1: 客户端通过Java NIO连接到服务端,支持多客户端的连接 功能2:客户端初次连接时,服务端提示输入昵称,如果昵称已经有人使用,提示重新输 ...
- 使用socket搭建一个网络聊天室
#服务器端import socket import threading #创建一个TCP端 sock = socket.socket(socket.AF_INET, socket.SOCK_STREA ...
- C#实例之简单聊天室(状态管理)
前言 状态管理是在同一页或不同页的多个请求发生时,维护状态和页信息的过程.因为Web应用程序的通信协议使用了无状态的HTTP协议,所以当客户端请求页面时,ASP.NET服务器端都会重新生 ...
- Java WebSocket实现网络聊天室(群聊+私聊)
1.简单说明 在网上看到一份比较nice的基于webSocket网页聊天项目,准备看看学习学习,如是有了这篇文章!原博主博客:http://blog.csdn.net/Amayadream/artic ...
随机推荐
- 查找SQL Server 自增ID值不连续记录
在很多的时候,我们会在数据库的表中设置一个字段:ID,这个ID是一个IDENTITY,也就是说这是一个自增ID.当并发量很大并且这个字段不是主键的时候,就有可能会让这个值重复:或者在某些情况(例如插入 ...
- Linq 常用方法解释
/// <summary> /// linq /// </summary> public class Linq { /// <summary> /// 测试 /// ...
- Codeforces 545D - Queue
545D - Queue 思路:忍耐时间短的排在前面,从小到大排序,贪心模拟,记录当前等待时间,如过等待时间大于当前的这个人得忍耐时间,那么就把这个人扔到最后面,不要管他了(哼╭(╯^╰)╮,谁叫你那 ...
- js事件轮询机制
console.log(1) setTimeout(function(){ console.log(2) },0); console.log(3) 毫无疑问:运行结果是1 3 2 也就是说:setTi ...
- 构造函数用return 会出显什么情况
首先我们都知道js中构造函数一般应该是这样的 function Super (a) { this.a = a; } Super.prototype.sayHello = function() { al ...
- ASCII 和 Unicode 编码的由来
大话数据结构上的说明: 网络博文的说明:
- Unity搭建简单的图片服务器
具体要实现的目标是:将图片手动拷贝到服务器,然后在Unity中点击按钮将服务器中的图片加载到Unity中. 首先简答解释下 WAMP(Windows + Apache + Mysql + PHP),一 ...
- VS 2013 无法启动IIS Express Web 服务器
之前程序好好的没什么问题,可是今天一大早来到公司后打开VS2013运行Web程序,然后就弹出提示信息:无法启动IIS Express Web 服务器,思来想去昨天都好好的,今天怎么会出现如此问题呢? ...
- English trip V1 - 2.Don't Do That Teacher:Patrick Key: 祈使句(imperatives)
什么是祈使句? What's imperatives? 求或者希望别人做什么事或者不做什么事时用的句子:带有命令的语气 In this lesson you will learn how to ...
- 使用Python生成双色球号码
说来也是巧,今天和一个朋友聊天,说他运气不错应该买彩票,于是就想到了双色球的规则,就写了几行代码产生双色球号码,代码如下: import random,time def process_int(x): ...