基于WebSocket 私聊、ws_session、httpsession
【解码器跟编码器】为了可以直接sendObject
解码 => 解成计算机需要的码 => 将用户输入的文本或者二进制 序列化成消息对象。 (dll 给机器吃的)
编码 => 编成用户需要的码 => 将消息对象 反序列化成 文本或者二进制。(txt 给用户吃的)
public class ChatMessageCodec
implements Encoder.BinaryStream<ChatMessage>,
Decoder.BinaryStream<ChatMessage>
{
//Jackson readValue=>解码 writeValue=>编码
private static final ObjectMapper MAPPER = new ObjectMapper(); //JSON生成器默认配置是自动关闭的,也就是true
static {
MAPPER.findAndRegisterModules();
MAPPER.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
} //对象转换成JSON 写入OutputStream
@Override
public void encode(ChatMessage chatMessage, OutputStream outputStream)
throws EncodeException, IOException
{
try
{
ChatMessageCodec.MAPPER.writeValue(outputStream, chatMessage);
}
catch(JsonGenerationException | JsonMappingException e)
{
throw new EncodeException(chatMessage, e.getMessage(), e);
}
} //从InputStream JSON 反序列化成消息
@Override
public ChatMessage decode(InputStream inputStream)
throws DecodeException, IOException
{
try
{
return ChatMessageCodec.MAPPER.readValue(
inputStream, ChatMessage.class
);
}
catch(JsonParseException | JsonMappingException e)
{
throw new DecodeException((ByteBuffer)null, e.getMessage(), e);
}
}
}
【ServerEndpoint】Session(WS_SESSION) HttpSession(用户请求) ChatSession(聊天窗口) 都放在一处 协调。
//通过配置编码器和解码器,jackson自动 序列化、反序列化。
@ServerEndpoint(value = "/chat/{sessionId}",
encoders = ChatMessageCodec.class,
decoders = ChatMessageCodec.class,
configurator = ChatEndpoint.EndpointConfigurator.class)
@WebListener
public class ChatEndpoint implements HttpSessionListener
{
private static final String HTTP_SESSION_PROPERTY = "com.wrox.ws.HTTP_SESSION";
private static final String WS_SESSION_PROPERTY = "com.wrox.http.WS_SESSION";
private static long sessionIdSequence = 1L;
//对象锁 给序列号 自增使用
private static final Object sessionIdSequenceLock = new Object();
private static final Map<Long, ChatSession> chatSessions = new Hashtable<>();
private static final Map<Session, ChatSession> sessions = new Hashtable<>();
//HttpSession 倾向于请求 关联Session跟HttpSession
private static final Map<Session, HttpSession> httpSessions =
new Hashtable<>();
//等待加入的聊天会话 列表
public static final List<ChatSession> pendingSessions = new ArrayList<>(); //onOpen 客户端连接进来
@OnOpen
public void onOpen(Session session, @PathParam("sessionId") long sessionId)
{
//modifyHandshake方法负责put 到onOpen这里首先要检查
//HttpSession(http)是否和Session(tcp)关联
HttpSession httpSession = (HttpSession)session.getUserProperties()
.get(ChatEndpoint.HTTP_SESSION_PROPERTY);
try
{
if(httpSession == null || httpSession.getAttribute("username") == null)
{
session.close(new CloseReason(
CloseReason.CloseCodes.VIOLATED_POLICY,
"You are not logged in!"
));
return;
}
String username = (String)httpSession.getAttribute("username");
session.getUserProperties().put("username", username); ChatMessage message = new ChatMessage();
message.setTimestamp(OffsetDateTime.now());
message.setUser(username);
ChatSession chatSession;
if(sessionId < 1)//会话列表从1开始,小于1就是没有
{
message.setType(ChatMessage.Type.STARTED);
message.setContent(username + " started the chat session.");
//构造聊天窗口消息列表
chatSession = new ChatSession();
synchronized(ChatEndpoint.sessionIdSequenceLock)
{
chatSession.setSessionId(ChatEndpoint.sessionIdSequence++);
}
//首次加入的会话 是客户
chatSession.setCustomer(session);
chatSession.setCustomerUsername(username);
chatSession.setCreationMessage(message);
//因为首先加入的 所以要放到等待列表
ChatEndpoint.pendingSessions.add(chatSession);
//关联 聊天窗口消息列表 保存在ServerEndpoint。
ChatEndpoint.chatSessions.put(chatSession.getSessionId(),
chatSession);
}
else
{
//客户支持代表加入
message.setType(ChatMessage.Type.JOINED);
message.setContent(username + " joined the chat session.");
//路径参数 sessionId 获得聊天窗口会话
chatSession = ChatEndpoint.chatSessions.get(sessionId);
chatSession.setRepresentative(session);
chatSession.setRepresentativeUsername(username);
ChatEndpoint.pendingSessions.remove(chatSession);//从等待会话列表移除
//向客户发送消息
session.getBasicRemote()
.sendObject(chatSession.getCreationMessage());
session.getBasicRemote().sendObject(message);
}
//关联websocket session 和 chatSession
ChatEndpoint.sessions.put(session, chatSession);
//关联
ChatEndpoint.httpSessions.put(session, httpSession);
//httpSession 也放了一份 ws_session 发给表现层
this.getSessionsFor(httpSession).add(session);
//聊天记录
chatSession.log(message);
//向客户代表发送消息
chatSession.getCustomer().getBasicRemote().sendObject(message);
}
catch(IOException | EncodeException e)
{
this.onError(session, e);
}
}
//收到消息 负责把消息发送给两个客户端。
@OnMessage
public void onMessage(Session session, ChatMessage message)
{
//从WS_SESSION获得 当前session所在 聊天窗口会话消息列表
ChatSession c = ChatEndpoint.sessions.get(session);
//和当前session 关联 另一个session 。
Session other = this.getOtherSession(c, session);
if(c != null && other != null)
{
c.log(message);
try
{
session.getBasicRemote().sendObject(message);
other.getBasicRemote().sendObject(message);
}
catch(IOException | EncodeException e)
{
this.onError(session, e);
}
}
}
@SuppressWarnings("unchecked")
private synchronized ArrayList<Session> getSessionsFor(HttpSession session)
{
try
{
//稍后发到表现层 WS_SESSION
if(session.getAttribute(WS_SESSION_PROPERTY) == null)
session.setAttribute(WS_SESSION_PROPERTY, new ArrayList<>()); return (ArrayList<Session>)session.getAttribute(WS_SESSION_PROPERTY);
}
catch(IllegalStateException e)
{
return new ArrayList<>();
}
}
private Session getOtherSession(ChatSession c, Session s)
{
return c == null ? null :
(s == c.getCustomer() ? c.getRepresentative() : c.getCustomer());
} //handshake 握手
public static class EndpointConfigurator
extends ServerEndpointConfig.Configurator
{
@Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request,
HandshakeResponse response)
{
super.modifyHandshake(config, request, response); //做了一个配置
//获得了HttpSession ,就可以保证用户已经登录 config.getUserProperties().put(
ChatEndpoint.HTTP_SESSION_PROPERTY, request.getHttpSession()
);
}
}
基于WebSocket 私聊、ws_session、httpsession的更多相关文章
- workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的)
workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的) 一.总结 1.下面链接里面还有一个来聊的php聊天室源码可以学习 2. ...
- 基于websocket实现的一个简单的聊天室
本文是基于websocket写的一个简单的聊天室的例子,可以实现简单的群聊和私聊.是基于websocket的注解方式编写的.(有一个小的缺陷,如果用户名是中文,会乱码,不知如何处理,如有人知道,请告知 ...
- 基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通讯同步(二)
我们上一篇<基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通讯同步(一)>主要讲解了如何搭建一个实时数据通讯服务器,客户端与服务端是如何通讯的,相信通过上一篇的讲解,再配 ...
- 高效简易开发基于websocket 的通讯应用
websocket的主要是为了解决在web上应用长连接进行灵活的通讯应用而产生,但websocket本身只是一个基础协议,对于消息上还不算灵活,毕竟websocket只提供文本和二进制流这种基础数据格 ...
- Socket.IO – 基于 WebSocket 构建跨浏览器的实时应用
Socket.IO 是一个功能非常强大的框架,能够帮助你构建基于 WebSocket 的跨浏览器的实时应用.支持主流浏览器,多种平台,多种传输模式,还可以集合 Exppress 框架构建各种功能复杂 ...
- python测试基于websocket协议的即时通讯接口
随着html5的广泛应用,基于websocket协议的即时通讯有了越来越多的使用场景,本文使用python中的websocket-client模块来做相关的接口测试 import webclient ...
- 基于 WebSocket 构建跨浏览器的实时应用
Socket.IO – 基于 WebSocket 构建跨浏览器的实时应用 Socket.IO 是一个功能非常强大的框架,能够帮助你构建基于 WebSocket 的跨浏览器的实时应用.支持主流浏览器,多 ...
- 分享基于 websocket 网页端聊天室
博客地址:https://ainyi.com/67 有一个月没有写博客了,也是因为年前需求多.回家过春节的原因,现在返回北京的第二天,想想,应该也要分享技术专题的博客了!! 主题 基于 websock ...
- 第一节:.Net版基于WebSocket的聊天室样例
一. 说在前面的话 该篇文章为实时通讯系列的第一节,基于WebSocket编写了一个简易版聊天样例,主要作用是为引出后面SignalR系列的用法及其强大方便之处,通过这个样例与后续的SignalR对比 ...
随机推荐
- VCC、VDD和VSS
在电子电路中,常可以看到VCC.VDD和VSS三种不同的符号,它们有什么区别呢? 一.解释 VCC:C=circuit 表示电路的意思, 即接入电路的电压: VDD:D=device 表示器件的意思 ...
- poj 3666 Making the Grade(离散化+dp)
Description A straight dirt road connects two fields on FJ's farm, but it changes elevation more tha ...
- 洛谷P3975 弦论
题意:求一个串的字典序第k小的子串/本质不同第k小的子串. 解:一开始我的想法是在后缀树上找,但是不知道后缀树上的边对应的是哪些字符... 然而可以不用fail树转移,用转移边转移即可. 先建一个后缀 ...
- C++常见函数使用
备注:总结C++中一些常见函数的使用,提高工作效率 数组的拼接: //报文头的前6B固定 DRV_UCHAR pkt_info_head[PALIVE_TO_NP_LEN] = {0x70, 0xff ...
- 安装原版Win8.1并激活
别问我为啥是win8.1,因为我不喜欢win10. 别问我为啥装系统,因为我新买了个硬盘. 别问我为啥写个教程,因为经历了很多坑. 第一步,用U启动做个U盘启动 http://www.uqdown.c ...
- Jquery Mobile表单
三个前提: 1.每个form必须设置method和action属性 2.每个form必须有页面范围内唯一的id标识 3.每个form必须有一个label标签,通过设置它的for属性来匹配元素的id & ...
- (选择不相交区间)今年暑假不AC hdu2037
今年暑假不AC Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Sub ...
- java将long数据转为int类型的方法
二.调用intValue()方法 [java] long ll = 300000; int ii= new Long(ll).intValue(); 三.先把long转换成字符串String,然后在转 ...
- 洛谷P1228 分治
https://www.luogu.org/problemnew/show/P1228 我真傻,真的,我单知道这种题目可以用dfs剪枝过,没有想到还能构造分治,当我敲了一发dfs上去的时候,只看到一个 ...
- mysql报错汇总
一.启动mysql: Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' #/var/r ...