WebSocket区分不同客户端方法

Posted 赵广陆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WebSocket区分不同客户端方法相关的知识,希望对你有一定的参考价值。

目录


1 获取HttpSession值

在使用websocket来制作多人即时聊天工具的时候,难免会遇到一个问题,如何区分不同的客户端。想要解决这个问题就等于是要解决这样一个问题:如何把当前登录用户的userId传给服务端呢?因为不同的客户端代表着不同的用户,做到了获取不同客户端的userId那么自然就把不同的客户端区分开来了。两种可行获取客户端userId的方法一种是通过在Server取HttpSession中的值获取当前用户,一种是直接在客户端建立连接时附带上用户的值。

当我们在完成用户登录的功能时,用户登录成功,则将当前用户放入HttpSession中,这是一种很常见的做法,这一部分代码如下(框架是SpringMVC,不详细介绍,具体代码请以自己所用框架为准):

if(Objects.equals(userDetail.getUserDetailPassword(), userPassword))

//如果当前用户登录成功,则将user对象放入httpSession的currentUser
                httpSession.setAttribute("currentUser",user);
                resoult = "success";
            

那么接下来问题的关键就来了,我们怎么在Server中获取在这里放入HttpSession中的User对象呢,直接获取肯定是不行的,不卖关子,直接放代码。
注意,结构如图:

新建一个GetHttpSessionConfigurator类,内容如下:

import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import javax.websocket.server.ServerEndpointConfig.Configurator;

public class GetHttpSessionConfigurator extends Configurator
    @Override
    public void modifyHandshake(ServerEndpointConfig sec,HandshakeRequest request, HandshakeResponse response) 
        HttpSession httpSession=(HttpSession) request.getHttpSession();
        sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
    

然后在Server里面注解的地方加上一句:

@ServerEndpoint(value="/server/",configurator=GetHttpSessionConfigurator.class)

此时,我们就已经可以用

@OnOpen
        public void onOpen(Session session, EndpointConfig config)
            HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
            

来获取httpSession对象了,然后直接取出currentUser存储的用户对象就可以了。

但是,在这里我产生了一个问题:原则上来讲我在server获取的httpsession中取出来的用户对象就是现在正和服务端建立连接的对象,因为这种情况的操作肯定是先登录,然后直接建立连接,可是在实际中多用户同时登录时就不一定是这样子了。因为登录是客户端发起的操作,建立连接也是客户端发起的操作,且不说在客户端这两个操作是否是紧密相连,就算是紧密相连,从服务器验证成功(此时已经放入currentUser对象)返回登录结果给客户端到客户端向服务端发起连接这中间因为网络原因也是会消耗一定时间的。那么这时候一件尴尬的事情就发生了:此时,另一个用户也在登录,并且在之前用户两个操作期间完成了登录验证操作,那么第一个用户连接建立之后取出的use对象就不是这个用户的而是第二个用户的,这就乱套了。这种方法相当于是 ,用户A先对服务器说,记住了,我叫A,然后过了一会儿来说,我要建立连接,我是刚刚告诉你名字那个人。那如果B在A离开那会儿也告诉了服务器我叫B,那么服务器就会把A当成B了。
当前,上面我所说的我没办法去验证,如果我对HttpSession理解错误那就另当别论了。(应该没理解错吧)如果理解有误还请大神指正。
所以,感觉上来讲还是第二种方法靠谱一点。

2 @PathParam获取用户对象

这种方法是在建立连接时把userId放在建立连接的申请之中,这样的话就不会乱掉了:因为用户A登录成功之后我就把用户A的user对象传回去了,然后用户A拿着自己的userId来对客户端说我要建立连接我是A,服务端自然不会搞错。实现方法如下:
服务端注解地方如下:

@ServerEndpoint(value="/server/userId")

方法参数如下:

@OnOpen
        public void onOpen(@PathParam("userId")String userId,Session session)

服务端在建立连接请求时路径如下(cp是jsp中的:)

<c:set var="cp" value="$pageContext.request.contextPath" />

currentUser就是我们之前登录成功时放入httpSession的值,这个值即便别的用户登录也不会被刷新因为它是被保存在自己的浏览器之中的。

ws = "ws://localhost:8080" + "$cp" + "/server"+"/$currentUser.userId";

这样的话,服务端就获取到了当前建立连接的用户了。

3 区分不同客户端

能够获取不同用户userId之后,我们就可以在服务端进行如下操作来区分用户了,具体见注释。(为了突出要点,代码做了精简,仅仅用于示范区分不同的用户)

    public class Server 
     //存放每个客户端对应的Server对象,可以考虑使用Map来代替,key作为用户标识
     private static CopyOnWriteArraySet<Server> server = new CopyOnWriteArraySet<Server>();
     //表示与某个用户的连接会话,通过它给客户端发送数据
     @SuppressWarnings("unused")
     private Session session;
     //用户id
     private String userId;
     //用户id和websocket的session绑定的路由表
     @SuppressWarnings("rawtypes")
     private static Map routeTable = new HashMap<>();
     /**
         * 连接建立成功调用的方法
         * @param session  可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
         */
        @SuppressWarnings("unchecked")
        @OnOpen
        public void onOpen(@PathParam("userId")String userIds,Session session)
            this.session = session;
            //获取当前登录用户的id
            this.userId=userIds;
            //将用户id和session绑定到路由表
            //绑定之后就可以在其它地方根据id来获取session,这时两个用户私聊就可以实现了
            routeTable.put(userId, session);
        
        //其它部分代码就不放了

以上是关于WebSocket区分不同客户端方法的主要内容,如果未能解决你的问题,请参考以下文章

Node.js Websocket 区分不同的用户

Websocket 处理不同的数据 - 不同的端点?

Nginx代理WebSocket方法

关于WebSocket学习记录

如何将 websocket 放入数据库(mongoDB/mongoose)?

php 怎么使用websocket推送消息