1

결론 전달되지 않습니다일부 서버 생성 된 이벤트는 WebSocket을 통해 생산 클라이언트에

일부 서버 생성 된 이벤트가 WebSocket을 통해 생산 클라이언트에 전달되지 않습니다를. 그러나 websocket 연결은 정상적으로 작동합니다.

사례 연구 :

내가 구글 크롬을 열고 우리의 서버에 연결합니다. devtools를 엽니 다. WS 탭에서 연결이 잘된 것을 볼 수 있지만, 서버에서 페이지의 내용을 업데이트해야 할 때 프레임이 수신되지 않습니다. 나는 잠시 동안, 때로는 (때로는) 나는 막대한 양의 지연과 함께 어떤 사건들을 기다린다. 그러나 예상대로 로컬로 작동합니다.

질문 :

누구나 볼 유사한 웹 소켓 동작을 가지고 있으며,이 조사를 위해 변수를 제거하는 방법에 대한 제안이있다.

인프라 :

서버 : 리눅스 톰캣

두 처리 서버 : 사용자로부터 2. 트래픽 (서버에 TCP/IP를 통해 통신) 1. 교통을 장치에서

사용자와 장치는 다 대다 관계입니다. 사용자가 연결된 장치가없는 서버에 연결되면 이 서버는 다른 서버를보고 정보 교환을 처리합니다.

서버 앞에 방화벽이 있습니다.

코드 :

https://github.com/kino6052/websockets-issue

WebSocketServerEndpoint.java

@ServerEndpoint("/actions") 
    public class WebSocketServerEndpoint { 
     static private final org.slf4j.Logger logger = LoggerFactory.getLogger(WebSocketServerEndpoint.class); 


     @OnOpen 
     public void open(Session session) { 
      WebSocketSessionHandler.addSession(session); 
     } 

     @OnClose 
     public void close(Session session) { 
      WebSocketSessionHandler.removeSession(session); 
     } 

     @OnError 
     public void onError(Throwable error) { 
      //Logger.getLogger(WebSocketServerEndpoint.class.getName()).log(Level.SEVERE, null, error); 
     } 

     @OnMessage 
     public void handleMessage(String message, Session session) { 
      try (JsonReader reader = Json.createReader(new StringReader(message))) { 
       JsonObject jsonMessage = reader.readObject(); 
       Long userId = null; 
       Long tenantId = null; 
       switch (WebSocketActions.valueOf(jsonMessage.getString("action"))){ 
        case SaveUserId: 
         userId = getUserId(jsonMessage); 
         tenantId = getTenantId(jsonMessage); 
         Long userIdKey = WebSocketSessionHandler.saveUserId(userId, session); 
         Long tenantUserKey = WebSocketSessionHandler.saveTenantUser(tenantId, userId); 
         WebSocketSessionHandler.updateUserSessionKeys(session, tenantUserKey, userIdKey); // Needed for Making Weak Maps Keep Their Keys if Session is Currently Active 
       } 
      } catch (Exception e) { 
       logger.error(e.toString()); 
      } 
     } 

     private Long getUserId(JsonObject jsonMessage) { 
      Long userId = null; 
      try { 
       userId = Long.parseLong(((Integer) jsonMessage.getInt("userId")).toString()); 
       return userId; 
      } catch (Exception e) { 
       logger.error(e.getMessage()); 
       return userId; 
      } 
     } 

     private Long getTenantId(JsonObject jsonMessage) { 
      Long tenantId = null; 
      try { 
       tenantId = Long.parseLong(((Integer) jsonMessage.getInt("tenantId")).toString()); 
       return tenantId; 
      } catch (Exception e) { 
       logger.error(e.getMessage()); 
       return tenantId; 
      } 
     } 
    } 

WebSocketService.java

@Singleton 
    public class WebSocketService { 
     private static final Logger logger = LoggerFactory.getLogger(WebSocketService.class); 

     public enum WebSocketEvents{ 
      OnConnection, 
      OnActivity, 
      OnAccesspointStatus, 
      OnClosedStatus, 
      OnConnectedStatus, 
      OnAlert, 
      OnSessionExpired 
     } 

     public enum WebSocketActions{ 
      SaveUserId 
     } 

     @WebPost("/lookupWebSocketSessions") 
     public WebResponse lookupWebSocketSessions(@JsonArrayParam("userIds") List<Integer> userIds, @WebParam("message") String message){ 
      try { 
       for (Integer userIdInt : userIds) { 
        Long userId = Long.parseLong(userIdInt.toString()); 
        if (WebSocketSessionHandler.sendToUser(userId, message) == 0) { 
        } else { 
         //logger.debug("Couldn't Send to User"); 
        } 
       } 
      } catch (ClassCastException e) { 
       //logger.error(e.getMessage()); 
       return webResponseBuilder.fail(e); 
      } catch (Exception e) { 
       //logger.error(e.getMessage()); 
       return webResponseBuilder.fail(e); 
      } 

      return webResponseBuilder.success(message); 
     } 

     @WebPost("/lookupWebSocketHistorySessions") 
     public WebResponse lookupWebSocketHistorySessions(@JsonArrayParam("userIds") List<Integer> userIds, @WebParam("message") String message){ 
      try { 
       for (Integer userIdInt : userIds) { 
        Long userId = Long.parseLong(userIdInt.toString()); 
        if (WebSocketHistorySessionHandler.sendToUser(userId, message) == 0) { 
        } else { 
         //logger.debug("Couldn't Send to User"); 
        } 
       } 
      } catch (ClassCastException e) { 
       //logger.error(e.getMessage()); 
       return webResponseBuilder.fail(e); 
      } catch (Exception e) { 
       //logger.error(e.getMessage()); 
       return webResponseBuilder.fail(e); 
      } 

      return webResponseBuilder.success(message); 
     } 

     // Kick Out a User if Their Session is no Longer Valid 
     public void sendLogout(User user) { 
      try { 
       Long userId = user.getId(); 
       List<Long> userIds = new ArrayList<>(); 
       userIds.add(userId); 
       JSONObject result = new JSONObject(); 
       result.put("userId", userId); 
       JSON message = WebSocketSessionHandler.createMessage(WebSocketEvents.OnSessionExpired, result); 
       lookOnOtherServers(userIds, message); 
      } catch (Exception e) { 
       logger.error("Couldn't Logout User"); 
      } 
     } 

     // Send History after Processing Data 
     // Returns "0" if success, "-1" otherwise 
     public int sendHistory(Activity activity) { 
      try { 
       TimezoneService.TimeZoneConfig timeZoneConfig = timezoneService.getTimezoneConfigsByAp(null, activity.getAccesspointId()); 
       JSONObject result = (JSONObject) JSONSerializer.toJSON(activity); 
       String timezoneId = timezoneService.convertTimezoneConfigToTimezoneId(timeZoneConfig); 
       result.put("timezoneString", timezoneId); 
       result.put(
         "profileId", 
         userDao.getUserProfileId(activity.getUserId()) 
       ); 
       JSON message = WebSocketHistorySessionHandler.createMessage(WebSocketEvents.OnActivity, result); 
       List<Long> userIds = getUsersSubscribedToActivity(activity.getTenantId()); 
       lookOnOtherServersHistory(userIds, message); 
       return 0; 
      } catch (Exception e) { 
       //logger.error("Couldn't Send History"); 
       return -1; 
      } 
     } 

     // SendAlertUpdate after Processing Data 
     public void sendAlertUpdate(Alert alert) { 
      try { 
       List<Long> userIds = getUsersUnderTenantByAccesspointId(alert.getAccesspointId()); 
       JSONObject result = JSONObject.fromObject(alert); 
       JSON message = WebSocketSessionHandler.createMessage(WebSocketEvents.OnAlert, result); 
       lookOnOtherServers(userIds, message); 
      } catch (Exception e) { 
       //logger.error("Couldn't Send Aleart"); 
      } 
     } 

     // Send Connected Status after Processing Data 
     public void sendConnectedStatus(Long accesspointId, Boolean isConnected) { 
      try { 
       List<Long> userIds = getUsersUnderTenantByAccesspointId(accesspointId); 
       JSONObject result = new JSONObject(); 
       result.put("accesspointId", accesspointId); 
       result.put("isConnected", isConnected); 
       JSON message = WebSocketSessionHandler.createMessage(WebSocketEvents.OnConnectedStatus, result); 
       lookOnOtherServers(userIds, message); 
      } catch (Exception e) { 
       //logger.error("Couldn't Send Connected Status"); 
      } 
     } 


     public int sendHistory(CredentialActivity activity) { 
      try { 
       TimezoneService.TimeZoneConfig timeZoneConfig = timezoneService.getTimezoneConfigsByAp(null, activity.getAccesspointId()); 
       JSONObject result = (JSONObject) JSONSerializer.toJSON(activity); 
       String timezoneId = timezoneService.convertTimezoneConfigToTimezoneId(timeZoneConfig); 
       result.put("timezoneString", timezoneId); 
       result.put(
         "profileId", 
         userDao.getUserProfileId(activity.getUserId()) 
       ); 
       JSON message = WebSocketHistorySessionHandler.createMessage(WebSocketEvents.OnActivity, result); 
       List<Long> userIds = getUsersUnderTenantByAccesspointId(activity.getAccesspointId()); 
       lookOnOtherServersHistory(userIds, message); 
       return 0; 
      } catch (Exception e) { 
       return -1; 
      } 
     } 

     public Boolean isUserSessionAvailable(Long id) { 
      return WebSocketSessionHandler.isUserSessionAvailable(id); 
     } 

     public void lookOnOtherServers(List<Long> userId, JSON data){ 
      List<String> urls = awsService.getServerURLs(); 
      for (String url : urls) { 
       postJSONDataToUrl(url, userId, data); 
      } 
     } 

     public void lookOnOtherServersHistory(List<Long> userId, JSON data){ 
      List<String> urls = awsService.getServerURLsHistory(); 
      for (String url : urls) { 
       postJSONDataToUrl(url, userId, data); 
      } 
     } 

     public int sendClosedStatus(AccesspointStatus accesspointStatus){ 
      try { 
       JSONObject accesspointStatusJSON = new JSONObject(); 
       accesspointStatusJSON.put("accesspointId", accesspointStatus.getAccesspointId()); 
       accesspointStatusJSON.put("openStatus", accesspointStatus.getOpenStatus()); 
       List<Long> userIds = getUsersUnderTenantByAccesspointId(accesspointStatus.getAccesspointId()); 
       lookOnOtherServers(userIds, accesspointStatusJSON); 
       return 0; 
      } catch (Exception e) { 
       return -1; 
      } 
     } 

     public List<Long> getUsersSubscribedToActivity(Long tenantId) { 
      List<Long> userList = WebSocketSessionHandler.getUsersForTenant(tenantId); 
      return userList; 
     } 

     private List<Long> getUsersUnderTenantByAccesspointId(Long accesspointId) { 
      List<Long> userList = new ArrayList<>(); 
      User user = userDao.getBackgroundUserByAccesspoint(accesspointId); 
      List<Record> recordList = tenantDao.getTenantsByUser(user, user.getId()); 
      for (Record record : recordList) { 
       Long tenantId = (Long) record.get("id"); 
       userList.addAll(getUsersSubscribedToActivity(tenantId)); 
      } 
      return userList; 
     } 

     public void postJSONDataToUrl(String url, List<Long> userId, JSON data) throws AppException { 
      List<NameValuePair> parameters; 
      HttpResponse httpResponse; 
      HttpClientService.SimpleHttpClient simpleHttpClient = httpClientService.createHttpClient(url); 
      try { 
       parameters = httpClientService.convertJSONObjectToNameValuePair(userId, data); 
      } catch (Exception e) { 
       throw new AppException("Couldn't Convert Input Parameters"); 
      } 
      try { 
       httpResponse = simpleHttpClient.sendHTTPPost(parameters); 
      } catch (Exception e) { 
       throw new AppException("Couldn't Get Data from the Server"); 
      } 
      if (httpResponse == null) { 
       throw new AppException("Couldn't Send to Another Server"); 
      } else { 
       //logger.error(httpResponse.getStatusLine().toString()); 
      } 
     } 
    } 

WebSocketSessionHandler.java

public class WebSocketSessionHandler { 

     // Apparently required to instantiate the dialogue, 
     // ideally it would be better to just create session map where sessions are mapped to userId, 
     // however, userId will be send only after the session is created. 
     // TODO: Investigate Instantiation of WebSocket Session Further 

     // WeakHashMap is Used for Automatic Memory Management (So That Removal of Keys That are no Longer Used Can be Automatically Performed) 
     // NOTE: However, it Requires Certain Precautions to Make Sure Their Keys Don't Expire Unexpectedly, Look for the Commented Code Below 
     private static final Map<Long, Set<Session>> sessionMap = new WeakHashMap<>(); 

     private static final Map<Long, Set<Long>> tenantUserMap = new WeakHashMap<>(); 

     public WebSocketSessionHandler() {} 

     public static List<Long> getUsersForTenant(Long tenantId) { 
      List<Long> userIds = new ArrayList<>(); 
      Set<Long> userIdsSet = tenantUserMap.get(tenantId); 
      if (userIdsSet != null) { 
       for (Long userId : userIdsSet){ 
        userIds.add(userId); 
       } 
      } 
      return userIds; 
     } 

     public static Boolean isUserSessionAvailable(Long id){ 
      Set<Session> userSessions = sessionMap.get(id); 
      if (userSessions == null || userSessions.size() == 0) { 
       return false; 
      } else { 
       return true; 
      } 
     } 

     // addSession() should add "session" to "sessions" set 
     // returns: "0" if success and "-1" otherwise 
     public static int addSession(Session session) { 
      int output; 
      try { 
       final long ONE_DAY = 86400000; 
       session.setMaxIdleTimeout(ONE_DAY); 
       sessions.put(session, new ArrayList<>()); 
       return sendToSession(session, createMessage(WebSocketEvents.OnConnection, "Successfully Connected")); 
      } catch (Exception e) { 
       logger.error("Couldn't Add Session"); 
       return -1; 
      } 
     } 

     // removeSession() should remove "session" from "sessions" set 
     // Scenarios: 
     // sessions is null? 
     // returns: "0" if success and "-1" otherwise 
     public static int removeSession(Session session) { 
      try { 
       closeSessionProperly(session); 
       if (sessions.remove(session) != null) { 
        return 0; 
       } else { 
        return -1; 
       } 
      } catch (Exception e) { 
       logger.error("Couldn't Remove Session"); 
       return -1; 
      } 
     } 

     private static void closeSessionProperly(Session session) { 
      try { 
       session.close(); 
      } catch (IOException ex) { 

      } 
     } 

     public static Long getKeyFromMap(Map map, Long key){ // Needed for Weak Maps 
      Set<Long> keySet = map.keySet(); 
      for (Long keyReference : keySet) { 
       if (keyReference == key) { 
        return keyReference; 
       } 
      } 
      return key; // If Not Found Return the Value Passed in 
     } 

     // saveUserId() should create an { userId -> session } entry in sessionMap 
     public static Long saveUserId(Long userId, Session session){ 
      // Test Scenarios: 
      // Can userId be null or wrong? 
      // Can session be null or wrong? 
      try { 
       userId = getKeyFromMap(sessionMap, userId); // Required for Weak Maps to Work Correctly 
       Set<Session> sessionsForUser = sessionMap.get(userId); 
       if (sessionsForUser == null) { 
        sessionsForUser = new HashSet<>(); 
       } 
       sessionsForUser.add(session); 
       sessionMap.put(userId, sessionsForUser); 
       return userId; 
      } catch (Exception e) { 
       logger.error("Couldn't Save User Id"); 
       return null; 
      } 
     } 

     // saveUserId() should create an { userId -> session } entry in sessionMap 
     public static Long saveTenantUser(Long tenantId, Long userId){ 
      // Test Scenarios: 
      // Can userId be null or wrong? 
      // Can session be null or wrong? 
      try { 
       tenantId = getKeyFromMap(tenantUserMap, tenantId); // Required for Weak Maps to Work Correctly 
       Set<Long> users = tenantUserMap.get(tenantId); 
       if (users == null) { 
        users = new HashSet<>(); 
       } 
       users.add(userId); 
       tenantUserMap.put(tenantId, users); 
       return tenantId; 
      } catch (Exception e) { 
       logger.error("Couldn't Save Tenant User"); 
       return null; 
      } 
     } 

     public static void updateUserSessionKeys(Session session, Long tenantId, Long userId) { 
      try { 
       List<Long> userSessionKeys = sessions.get(session); 
       userSessionKeys.add(0, tenantId); 
       userSessionKeys.add(1, userId); 
      } catch (Exception e) { 
       logger.error("Couldn't Update User Session Keys"); 
      } 
     } 

     // removeUserId() should remove an { userId -> session } entry in sessionMap 
     // returns: "0" if success and "-1" otherwise 
     public static int removeUserId(Long userId) { 
      try { 
       sessionMap.remove(userId); 
       return 0; 
      } catch (Exception e) { 
       return -1; 
      } 
     } 

     // sendAccesspointStatus() should compose JSON message and pass it to sendToUser() 
     // returns: "0" if success and "-1" otherwise 
     public static int sendClosedStatus(Long userId, JSONObject accesspointStatus) { 
      try { 
       JSONObject accesspointStatusEventMessage = (JSONObject) createMessage(WebSocketEvents.OnClosedStatus, accesspointStatus); 
       sendToUser(userId, accesspointStatusEventMessage); 
       return 0; 
      } catch (Exception e) { 
       return -1; 
      } 
     } 

     // sendToUser() sends message to session that is mapped to userId 
     // returns: "0" if success and "-1" otherwise 
     public static int sendToUser(Long userId, JSON message) { 

      if (sessionMap.containsKey(userId)) { 
       Set<Session> sessionsForUser = sessionMap.get(userId); 
       for (Session session : sessionsForUser) { 
        if (!session.isOpen()) { 
         sessions.remove(session); 
         continue; 
        } 
        sendToSession(session, message); 
       } 
       return 0; 
      } else { 
       return -1; 
      } 
     } 


     // sendToSession() sends string message to session 
     // returns: "0" if success and "-1" otherwise 
     private static int sendToSession(Session session, JSON message){ 
      try { 
       try { 
        Long tenantId = sessions.get(session).get(0); 
        ((JSONObject) message).put("tenantId", tenantId); 
       } catch (Exception e) { 
        logger.error("No tenantId Found"); 
       } 
       session.getBasicRemote().sendText(message.toString()); 
       return 0; 
      } catch (IOException e) { 
       try { 
        session.close(); 
       } catch (IOException ex) { 

       } 

       closeSessionProperly(session); 
       sessions.remove(session); 

       return -1; 
      } 
     } 

     // sendToSession() sends string message to session 
     // returns: "0" if success and "-1" otherwise 
     private static int sendToSession(Session session, String message){ 
      try { 
       JSONObject newMessage = JSONObject.fromObject(message); 
       try { 
        Long tenantId = sessions.get(session).get(0); 
        newMessage.put("tenantId", tenantId); 
       } catch (Exception e) { 
        logger.error("No tenantId Found"); 
       } 
       session.getBasicRemote().sendText(newMessage.toString()); 
       return 0; 
      } catch (IOException e) { 
       closeSessionProperly(session); 
       sessions.remove(session); 
       return -1; 
      } 
     } 
    } 
+1

하지 (내 의견에서 복사가.이 솔루션을했다 밝혀졌습니다)하지만'WebSocketSessionHandler' 클래스는 스레드로부터 안전하지 않습니다. 그것은 동기화되지 않은 내부적으로'WeakHashMap'을 사용합니다. 이러한지도에 동시에 액세스하면 예기치 않은 동작이 발생할 수 있으며 이는 사용자가보고있는 효과를 유발할 수도 있고 그렇지 않을 수도 있습니다. – defnull

+0

@defnull 답장을 보내 주셔서 감사합니다. 이 방법으로 문제가 해결되고 업데이트가 제공되는지 확인하겠습니다. –

+0

@defnull, weakhashmap을 잠재적 인 버그로 지적했습니다. 내가 upvote 수 있도록 귀하의 답변을 게시하십시오. –

답변

0

아마 없을 유일한 버그,하지만 WebSocketSessionHandler 클래스는 스레드로부터 안전하지 않습니다. 그것은 동기화되지 않은 내부적으로 WeakHashMap을 사용합니다. 이러한지도에 동시에 액세스하면 예기치 않은 동작이 발생할 수 있으며 이는 사용자가보고있는 효과를 유발할 수도 있고 그렇지 않을 수도 있습니다.

이것이 올바른 가정이었습니다. 일반적인 경험 법칙 : 예기치 않은 동작 ~ 인종 조건

0

아마도 유일한 버그는 아니지만 WebSocketSessionHandler 클래스는 스레드로부터 안전하지 않을 수 있습니다.동기화되지 않은 내부적으로 WeakHashMap을 사용합니다. 이러한지도에 동시에 액세스하면 예기치 않은 동작이 발생할 수 있으며 이는 사용자가보고있는 효과를 유발할 수도 있고 그렇지 않을 수도 있습니다.

아마 유일한 버그

관련 문제