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































































 

posted @ 2018-03-29 18:34  chenhui7373  阅读(1688)  评论(0)    收藏  举报