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 ...
随机推荐
- Centos下挖XMR门罗币的详细教程
很多朋友都看过我之前写的Ubuntu下挖XMR门罗币的教程,也有很多朋友提出,为什么不写个Centos的教程出来,今天我在这里就写个Centos的教程,看这个教程前,大家先看看之前的教程,因为里面涉及 ...
- 获取公网IP地址
https://ipip.yy.com/get_ip_info.php http://pv.sohu.com/cityjson?ie=utf-8 http://www.ip168.com/json.d ...
- 【Python】【环境搭建】
[环境配置] Windows : http://blog.csdn.net/zhunianguo/article/details/53524792 [Pycharm] pyCharm最新2018激活码 ...
- 纯CSS实现一个微信logo,需要几个标签?
博客已迁移至http://lwzhang.github.io. 纯CSS实现一个微信logo并不难,难的是怎样用最少的html标签实现.我一直在想怎样用一个标签就能实现,最后还是没想出来,就只好用两个 ...
- P2865 【[USACO06NOV]路障Roadblocks】(次短路)
传送门 算法Dijkstra要求次短路 那么在不考虑重复走一条边的情况下 肯定是把最短路中的一段改成另一段 至少要换另一条边到路径里所以可以枚举所有不属于最短路的每条边(a,b) 那么dis(1,a) ...
- python 分数的数学四则运算
import fractions f1 = fractions.Fraction(, ) f2 = fractions.Fraction(, ) print('{} + {} = {}'.format ...
- Jmeter 中Cookie管理器的使用
Jmeter中有好几个地方可以管理Cookie,比如 Http Cookie Manager HTTP Header Manager jmeter.property文件中: #CookieManage ...
- Insert Delete GetRandom O(1)
2018-07-15 18:36:29 问题描述: 问题求解: private ArrayList<Integer> ls; private HashMap<Integer, Int ...
- php 邮件发送利器 PHPMailer
php 自带的邮件发送函数已经弱到不能用了. PHPMailer非常的强大. 绝对是php里必须使用的程序. 下载地址: https://github.com/Synchro/PHPMailer 只要 ...
- java通过java.net.URL发送http请求调用接口
一般在*.html,*.jsp页面中我们通过使用ajax调用接口,这个是我们通常用的.对于这些接口,大都是本公司写的接口供自己调用,所以直接用ajax就可以.但是,如果是多家公司共同开发一个东西,一个 ...