WebSocket区分不同客户端两种方法(HttpSession和@PathParam)

介绍

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

开发环境和工具

    MyEclipse,Tomcat8.0
    WebSocket

获取HttpSession值

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

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

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

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

1

    新建一个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理解错误那就另当别论了。(应该没理解错吧)如果理解有误还请大神指正。
    所以,感觉上来讲还是第二种方法靠谱一点。

@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}";

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

区分不同客户端

    能够获取不同用户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);
        }
        //其它部分代码就不放了

    大概就是这样子了,这是我查找了很多资料自己也做过试验的结果,希望能够帮助到大家。
    好好学习,天天向上,加油!

代码

    现在回头看一看,发现博客讲的不是很清楚,实在是抱歉了,此处把我项目的源码放上来,希望对各位有所帮助。
    Github:https://github.com/IcedSoul/WithMe
    项目本身比较大,包含1.0和2.0版本,它们使用的WebScoket版本不一样,1.0使用的是JavaEE7.0带的WebSocket(JSR356),只支持Web端聊天,2.0使用的是Github上的一个开源项目Java-WebScoket,同时支持Web端和Android端。查看代码的时候注意留意一下你所使用的WebSocket版本,然后去查看对应的代码。

  • 12
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
要在Python中实现WebSocket服务端和客户端,可以使用Python websockets库。该库基于asyncio异步IO构建,提供了基于协程的API。 首先,你需要安装websockets库,可以使用pip命令进行安装:pip3 install websockets。 对于服务端的实现,你可以创建一个Python文件,并引入websockets和asyncio模块。使用async关键字定义一个async函数,例如叫做handle_client,这个函数将处理客户端连接并处理接收到的消息。在函数内部,使用async with语句创建一个WebSocket服务器对象,传入服务器的地址和端口。然后,使用一个无限循环来等待并处理客户端连接。在循环中,使用await关键字接收客户端发送的消息,并根据需要进行处理。最后,使用await关键字向客户端发送消息。下面是一个简单的示例代码: ```python import asyncio import websockets async def handle_client(websocket, path): async for message in websocket: # 处理接收到的消息 # ... # 发送消息给客户端 response = "Hello, client!" await websocket.send(response) start_server = websockets.serve(handle_client, 'localhost', 8765) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever() ``` 对于客户端的实现,你可以创建一个Python文件,并引入websockets和asyncio模块。使用async关键字定义一个async函数,例如叫做connect_to_server,这个函数将连接到服务器并发送消息。在函数内部,使用async with语句创建一个WebSocket客户端对象,传入服务器的地址和端口。然后,使用await关键字向服务器发送消息,并等待服务器的响应。最后,打印接收到的消息。下面是一个简单的示例代码: ```python import asyncio import websockets async def connect_to_server(): async with websockets.connect('ws://localhost:8765') as websocket: message = "Hello, server!" await websocket.send(message) response = await websocket.recv() print(response) asyncio.get_event_loop().run_until_complete(connect_to_server()) ``` 需要注意的是,以上示例只是一个简单的示例,你可以根据自己的需求进行修改和扩展。另外,确保你的Python版本是大于等于3.6的版本,因为websockets库对于较低版本的Python可能会有兼容性问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值