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 ...
随机推荐
- self用法
- asp.net <asp:Repeater>下的 asp:LinkButton CommandArgument点击事件
前台 <asp:Repeater ID="rptData" runat="server" OnItemCommand="rptData_Item ...
- AngularJS监听路由变化
使用AngularJS时,当路由发生改变时,我们需要做某些处理,此时可以监听路由事件,常用的是$routeStartChange, $routeChangeSuccess.完整例子如下: <!D ...
- 一个简单可参考的API网关架构设计
网关一词较早出现在网络设备里面,比如两个相互独立的局域网段之间通过路由器或者桥接设备进行通信, 这中间的路由或者桥接设备我们称之为网关. 相应的 API 网关将各系统对外暴露的服务聚合起来,所有要调用 ...
- LeetCode--069--x的平方根
问题描述: 实现 int sqrt(int x) 函数. 计算并返回 x 的平方根,其中 x 是非负整数. 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去. 示例 1: 输入: 4 输出: ...
- Confluence 6 配置 LDAP 连接池
当 LDAP 连接池被启用后,LDAP 目录服务器将会维护一个连接池同时当必要的时候指派他们.当一个连接关闭后,这个连接将会放回到连接池中供以后进行使用.这种设置将会有效的提高系统性能. 希望配置 L ...
- 52 53django
一.需要弄清楚的几个关键点: KEY: 1 Django项目无论多大,只是一个应用程序 2 地址栏发请求默认是GET请求 form表单可以发送get请求,也可以发送post请求 3 浏览器接受的响应体 ...
- vs2015下通过opencv使用hdf5
因为使用Kinect SDK编程,又需求高速文件I/O,所以通过opencv接口使用hdf5. (opencv 3.1以上版本,在其Extra Modules中支持hdf5) 一. 环境 OS: Wi ...
- HDU-5050 Divided Land (二进制求GCD)
题目大意:将两个二进制数的GCD用二进制数表示出来. 题目分析:这道题可以用java中的大数类AC. 代码如下: import java.io*; import java.math.BigIntege ...
- Import Data from *.xlsx file to DB Table through OAF page(转)
Use Poi.jar Import Data from *.xlsx file to DB Table through OAF page Use Jxl.jar Import Data from ...