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模板网!

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

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

相关文章

  • 【go】gorilla/websocket如何判断客户端强制断开连接

    当客户端因为某些问题异常关闭连接时,可以判断关闭连接的异常类型 通过调用websocket.IsCloseError或websocket.IsUnexpectedCloseError即可 其中github源码如下 异常类型如下

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

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

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

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

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

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

    2024年02月16日
    浏览(35)
  • 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日
    浏览(48)
  • Java实现websocket客户端

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

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

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

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

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

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

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

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

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

    2024年02月12日
    浏览(30)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包