基于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()
);
}
}

浙公网安备 33010602011771号