WebSocket 实现长连接及通过WebSocket获取客户端IP

这篇具有很好参考价值的文章主要介绍了WebSocket 实现长连接及通过WebSocket获取客户端IP。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

WebSocket 实现长连接及通过WebSocket获取客户端IP

WebSocket 是一种支持双向通讯的网络通信协议。

实现过程:

1 添加ServerEndpointExporter配置bean

@Configuration
public class WebSocketConfig {

    // 自动注册使用了@ServerEndpoint**注解声明的Websocket endpoint
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

2 实现过程

需求是通过WebSocket,建立长连接,并获取当前在线的人数。通过Websocket 不断发送消息,建立长连接,给Session续命。我是通过MAC地址,区分不同的设备,因为我的需求中需要一个账号能够登录多台机器。所以我通过MAC地址用于标识不同的设备信息。(若是一个账号只能登陆一次,采用用户ID)

1 . 添加配置

@ServerEndpoint(value = "/websocket/onlineAme/{Mac}")

2. 主要方法

  • @OnOpen

    • 首次建立连接时,运行该注解下的方法。
    • 在此方法中可以获取websocket的session
    • 并将该用户的Mac 以及 Session存放到
    private static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
    
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "Mac") String mac) throws IOException {
        log.info("【Ame websocket 链接成功】,Ame mac:"+ mac);
        session.setMaxIdleTimeout(sessionTimeout);
        // 获取客户端的Ip
        if(StringUtils.isBlank(mac)||ObjectUtil.isNull(mac)){
            log.error("并未上传设备信息");
        }
        setMap(session,mac);
    }
    
    private void setMap(Session session,String mac){
        sessionMap.put(mac,session);
        log.warn("Ame MAC address:{},当前在线人数为:{}",mac,sessionMap.size());
    }
    
  • @OnMessage

    • 该方法是客户端与服务端进行通讯。

    • 每次客户端与服务端建立通讯时,会给Session续命,延长Session的时常

      @OnMessage
      public void onMessage(Session session, String msg)  {
              session.setMaxIdleTimeout(sessionTimeout);
              if(StringUtils.isBlank(msg)){
                  return;
              }
              // 判断 MAC 地址 是否 是正在上线
              String mac = getMACBySession(session);
              if(StringUtils.isBlank(mac)){
                  return;
              }
              // 将上传的msg转化为 AmeServicePack
      		handleAmeMsg(mac,ame);
      }
      private String getMACBySession(Session session){
          String mac = getUserIdBySession(session);
          if(ObjectUtil.isNull(mac)){
              return null;
          }
          return mac;
      }
      private String getUserIdBySession(Session session){
          for (String mac : sessionMap.keySet()) {
              /*session 本身是有一个id的,通过userid 找到Session 然后再通过 其对应 id,与 传入的Session 中的 session对比  */
              if(sessionMap.get(mac).getId().equals(session.getId())){
                  return mac;
              }
          }
          return null;
      }
      
  • @OnClose

    @OnClose
    public void onClose(Session session,@PathParam(value = "Mac") String mac) {
        removeMap(session);
        log.info("【websocket退出成功】该设备退出:"+mac);
    }
    
    private void removeMap(Session session){
        String mac = getUserIdBySession(session);
        if(ObjectUtil.isNull(mac)){
            return;
        }
        sessionMap.remove(mac);
        //userMap.remove(userId);
        removeAme(mac);
    }
    
    private void removeAme(String mac){
        ameHashMap.remove(mac);
        sendInfo("Ame:"+ameInfo.getAmeip()+"下线成功",mac);
    }
    
  • @OnError

    @OnError
    public void onError(Session session,Throwable throwable) {
        log.error("websocket: 发生了错误");
        removeMap(session);
        throwable.printStackTrace();
    }
    
  • 向某一个用户发送消息

     /*
       发送自定义消息
         向某一个用户发送,消息*/
    public static void sendInfo(String message,String toMac){
        log.info("发送消息:{},内容是:{}",message,toMac);
        if(ObjectUtil.isNull(toMac) || StringUtils.isBlank(message)){
            log.error("消息不完整");
            return;
        }
        // 包含就发送
        //System.out.println(sessionMap.containsKey(toUserId));
        if(sessionMap.containsKey(toMac)){
            try {
                sendMessage(sessionMap.get(toMac),message);
            }catch (Exception e){
                log.error("发送给用户{}的消息出错",toMac);
            }
        }
        // 用户不在线
        else {
            log.error("设备{}不在线",toMac);
        }
    
    }
    
    public static void sendMessage(Session session,String message) throws IOException {
        session.getBasicRemote().sendText(message);
    }
    

3 通过WebSocket获取 请求Ip地址

Websocket 中的request中并没有header 中并没有客户端的Ip地址,但是在SpringCloud中,是通过网关,路由转发。在网关中的请求的request中存在Ip地址,可以通过拦截器,获取网关的ip然后将request放到websocket的request中。文章来源地址https://www.toymoban.com/news/detail-700094.html

3.1 拦截器
package com.mam.gateway.filter;

import jdk.nashorn.internal.runtime.regexp.joni.Config;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.net.InetSocketAddress;
import java.util.Objects;

/**
 * 获取 WebSocket 上传Session的 Ip信息
 */
@Component
public class SessionFilter extends AbstractGatewayFilterFactory<SessionFilter.Config> {
    public SessionFilter()
    {
        super(SessionFilter.Config.class);
    }

    @Override
    public String name()
    {
        return "SessionFilter";
    }

    @Override
    public GatewayFilter apply(SessionFilter.Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                ServerWebExchange mutatedServerWebExchange = exchange.mutate().request(exchange.getRequest()).build();
                return chain.filter(mutatedServerWebExchange);
            }
        };
    }
    static class Config
    {
        private Integer order;
        public Integer getOrder()
        {
            return order;
        }
        public void setOrder(Integer order)
        {
            this.order = order;
        }
    }
}

3.2 ServerEndpointConfig 的配置
public class WebSocketConfigurator extends ServerEndpointConfig.Configurator{
    private static final Logger log = LoggerFactory.getLogger(WebSocketConfigurator.class);

    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        Map<String, Object> attributes = sec.getUserProperties();
        try{
            String clientIp = IpUtils.getIpAddrByHandshakeRequest(request.getHeaders());
            attributes.put("clientIp",clientIp);
            log.info("websocker拦截器X-Real_IP{}header{}",request.getHeaders().get("X-Real_IP"),request.getHeaders().toString());
        }catch (Exception e){
            e.printStackTrace();
        }
        super.modifyHandshake(sec,request,response);
    }
}
public static String getIpAddrByHandshakeRequest(Map<String, List<String>> map)
{
    if (map == null)
    {
        return null;
    }

    String ip = null;

    // X-Forwarded-For:Squid 服务代理
    String ipAddresses = Convert.toStr(map.get("X-Forwarded-For"));
    if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses))
    {
        // Proxy-Client-IP:apache 服务代理
        ipAddresses = Convert.toStr(map.get("Proxy-Client-IP"));
    }else {
        ipAddresses = ipAddresses.substring(1,ipAddresses.length()-1);
    }
    if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses))
    {
        // WL-Proxy-Client-IP:weblogic 服务代理
        ipAddresses = Convert.toStr(map.get("WL-Proxy-Client-IP"));
    }
    if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses))
    {
        // HTTP_CLIENT_IP:有些代理服务器
        ipAddresses = Convert.toStr(map.get("HTTP_CLIENT_IP"));
    }
    if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses))
    {
        // X-Real-IP:nginx服务代理
        ipAddresses = Convert.toStr(map.get("X-Real-IP"));
    }

    // 有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
    if (ipAddresses != null && ipAddresses.length() != 0)
    {
        ip = ipAddresses.split(",")[0];
    }
    return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
}

3.3 WebSocket 获取Ip
AmeServicePack ame = JSONObject.toJavaObject(JSONObject.parseObject(msg), AmeServicePack.class);
Map<String, Object> userProperties = session.getUserProperties();
String clientip = (String) userProperties.get("clientIp");

4 完整代码

@Component
@ServerEndpoint(value = "/websocket/onlineAme/{Mac}",configurator = WebSocketConfigurator.class)
public class AmeLoginWebSocket {

    static Logger log = LoggerFactory.getLogger(AmeLoginWebSocket.class);

    /* 存储session */
    private static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
    /* 存储 在线 ame服务 信息*/
    private  ConcurrentHashMap<String,AmeServicePack> ameHashMap = new ConcurrentHashMap<>();
    /* 存储 Ip  */

    private static final long sessionTimeout = 600000;


    /** 链接成功后发送消息**/
    @OnMessage
    public void onMessage(Session session, String msg)  {
        session.setMaxIdleTimeout(sessionTimeout);
        log.info("【websocket 接收成功】内容为"+msg);
        if(StringUtils.isBlank(msg)){
            return;
        }
        // 判断 MAC 地址 是否 是正在上线
        String mac = getMACBySession(session);
        if(StringUtils.isBlank(mac)){
            return;
        }
        AmeServicePack ame = JSONObject.toJavaObject(JSONObject.parseObject(msg), AmeServicePack.class);
        Map<String, Object> userProperties = session.getUserProperties();
        String clientip = (String) userProperties.get("clientIp");
        // 将上传的msg转化为 AmeServicePack
        handleAmeMsg(mac,ame);
        
    }

    private void handleAmeMsg(String mac, AmeServicePack ameInfo) {

        log.info("Ame:MAC{}Ip{}:",mac,ameInfo.getAmeip());
        if(ameHashMap.containsKey(mac)){
            log.info("该设备{}Ip{}已上线",mac,ameInfo.getAmeip());
            sendInfo("该用户Ip"+ameInfo.getAmeip()+"已存在",mac);
        }else {
            ameHashMap.put(mac,ameInfo);
        }
    }

    private boolean updateOnline(AmeServicePack ameInfo){
        AjaxResult isonline = SpringUtils.getBean(IAmePackService.class).update(ameInfo,SecurityConstants.INNER);
        if((Integer)isonline.get("code")== 200){
            return true;
        }else {
            return false;
        }
    }

    /**
     * ameIp 为上线 但 任务表中仍有 正在运行的任务,并将其修改为 -2
     * @param ameIp
     */
    private void updateErrorTaskStatus(String ameIp){
        SpringUtils.getBean(IAmePackService.class).updateErrorAMEStatus(ameIp,SecurityConstants.INNER);
    }



    private void handlePCMsg(LoginUser loginUser, String msg) {
        log.info("系统用户:{},消息{}:",loginUser.getUsername(),msg);
    }
    private String getMACBySession(Session session){
        String mac = getUserIdBySession(session);
        if(ObjectUtil.isNull(mac)){
            return null;
        }
        return mac;
    }
    /*
     *成功建立连接后调用
     * @param [session, username]
     * @return void
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "Mac") String mac) throws IOException {
        log.info("【Ame websocket 链接成功】,Ame mac:"+ mac);
        session.setMaxIdleTimeout(sessionTimeout);
        // 获取客户端的Ip


        if(StringUtils.isBlank(mac)||ObjectUtil.isNull(mac)){
            log.error("并未上传设备信息");
        }
        setMap(session,mac);
    }
    private void setMap(Session session,String mac){
        sessionMap.put(mac,session);
        log.warn("Ame MAC address:{},当前在线人数为:{}",mac,sessionMap.size());
    }


    /*
     *关闭连接时调用
     * @param [userId]
     * @return void
     */
    @OnClose
    public void onClose(Session session,@PathParam(value = "Mac") String mac) {
        removeMap(session);
        log.info("【websocket退出成功】该设备退出:"+mac);
    }

    private void removeMap(Session session){
        String mac = getUserIdBySession(session);
        if(ObjectUtil.isNull(mac)){
            return;
        }
        sessionMap.remove(mac);
        //userMap.remove(userId);
        removeAme(mac);
    }
    private String getUserIdBySession(Session session){
        for (String mac : sessionMap.keySet()) {
            /*session 本身是有一个id的,通过userid 找到Session 然后再通过 其对应 id,与 传入的Session 中的 session对比  */
            if(sessionMap.get(mac).getId().equals(session.getId())){
                return mac;
            }
        }
        return null;
    }
    private void removeAme(String mac){
        ameHashMap.remove(mac);
        log.info("{}:下线成功",ameInfo.getAmeip());
        sendInfo("Ame:"+ameInfo.getAmeip()+"下线成功",mac);
    }
    /*
     *发生错误时调用
     * @param [session, throwable]
     * @return void
     */
    @OnError
    public void onError(Session session,Throwable throwable) {
        log.error("websocket: 发生了错误");
        removeMap(session);
        throwable.printStackTrace();
    }

    /*
     发送自定义消息
     向某一个用户发送,消息
    */
    public static void sendInfo(String message,String toMac){
        log.info("发送消息:{},内容是:{}",message,toMac);
        if(ObjectUtil.isNull(toMac) || StringUtils.isBlank(message)){
            log.error("消息不完整");
            return;
        }
        // 包含就发送
        //System.out.println(sessionMap.containsKey(toUserId));
        if(sessionMap.containsKey(toMac)){
            try {
                sendMessage(sessionMap.get(toMac),message);
            }catch (Exception e){
                log.error("发送给用户{}的消息出错",toMac);
            }
        }
        // 用户不在线
        else {
            log.error("设备{}不在线",toMac);
        }

    }

    public static void sendMessage(Session session,String message) throws IOException {
        session.getBasicRemote().sendText(message);
    }
}

到了这里,关于WebSocket 实现长连接及通过WebSocket获取客户端IP的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • c++: websocket 客户端与服务端之间的连接交互

    目录 socket 头文件 延迟时间 通信协议地址 TCP/IP 服务端 客户端 编程步骤 服务端 客户端 编程步骤 1. 初始化 WSAStartup 2. 创建 socket 2.1 协议族 2.2 socket 类型 2.3 协议 3. 绑定 bind (服务端) 4. 监听 listen(服务端) 5. 请求连接 connect(客户端) 6. 接收请求 accept(服务端) 7. 发送

    2024年02月14日
    浏览(41)
  • Springboot 集成WebSocket作为客户端,含重连接功能,开箱即用

    使用演示 只需要init后调用sendMessage方法即可,做到开箱即用。内部封装了失败重连接、断线重连接等功能。 基于Springboot工程 引入websocket依赖 开箱即用的工具类

    2024年02月04日
    浏览(60)
  • Java 构建websocket客户端,构建wss客户端,使用wss连接,并发送数据到服务器端,接收服务器端消息

    Java 构建websocket客户端,构建wss客户端,使用wss连接,并发送数据到服务器端,接收服务器端消息 回调函数处理

    2024年02月13日
    浏览(61)
  • 通过Milo实现的OPC UA客户端连接并订阅Prosys OPC UA Simulation Server模拟服务器

    前面我们搭建了一个本地的 PLC 仿真环境,并通过 KEPServerEX6 读取 PLC 上的数据,最后还使用 UAExpert 作为 OPC 客户端完成从 KEPServerEX6 这个OPC服务器的数据读取与订阅功能:SpringBoot集成Milo库实现OPC UA客户端:连接、遍历节点、读取、写入、订阅与批量订阅。 注意,如果实际工

    2024年02月16日
    浏览(51)
  • Java编程技巧:获取ip地址、通过ip获取地理位置、获取客户端操作系统、获取客户端浏览器、获取主机名、获取操作系统、获取系统架构

    说明: 大家直接去对应项目位置找到代码,然后看着复制就行了 1.1、若依(自己写的代码) 项目:https://gitee.com/y_project/RuoYi 子模块:ruoyi-common 所在类:com.ruoyi.common.utils.IpUtils 所在方法:getIpAddr 详细位置:整个方法 1.2、Snowy(借助hutool工具包) 项目:https://gitee.com/xiaonuo

    2024年02月04日
    浏览(128)
  • Java实现websocket客户端

    常规情况下,大多数时候Java后台作为websocket服务端,实现方式也比较简单,网上很多案例代码。但是很多时候项目中服务与服务之间也需要使用websocket通信,此时项目就需要实现客户端功能。 步骤一:导入依赖: 步骤二:实现WebSocketClient抽象类: 该类中和websocket服务端接口

    2024年02月16日
    浏览(60)
  • websocket客户端实现(java)

    其中,headers 参数是一个键值对,表示需要设置的请求头。在构造函数中,我们首先创建了一个 ClientEndpointConfig.Configurator 对象,重写了其中的 beforeRequest() 方法,用于在请求之前设置请求头。然后,我们使用 ClientEndpointConfig.Builder.create() 方法创建一个 ClientEndpointConfig 对象,并

    2024年02月15日
    浏览(49)
  • 通过 EPOLL 解决客户端同时连接多服务器的问题

    项目需求是  程序上 同时配置了多个服务端 设备 每隔一段时间需要 比如1分钟 连一下服务器看下是否连通   并将结果上报给平台  原来是用线程池来做的   具体大概就是 定时器到了之后  遍历设备列表  找到设备之后  通过 socket连接 发送一个指令 等待服务器返回 用来

    2024年02月13日
    浏览(39)
  • SpringBoot+WebSocket实现服务端、客户端

    小编最近一直在使用springboot框架开发项目,毕竟现在很多公司都在采用此框架,之后小编也会陆续写关于springboot开发常用功能的文章。 什么场景下会要使用到websocket的呢? websocket主要功能就是实现网络通讯,比如说最经典的客服聊天窗口、您有新的消息通知,或者是项目与

    2024年02月13日
    浏览(50)
  • 实现c++轻量级别websocket协议客户端

    因以前发过这个代码,但是一直没有整理,这次整理了一下,持续修改,主要是要使用在arm的linux上,发送接收的数据压缩成图片发送出去。 要达到轻量websocket 使用,必须要达到几个方面才能足够简单, 1、不用加入其他的库 2、只需要使用头文件包含就可以 3、跨平台 如果

    2024年02月12日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包