1. 程式人生 > >完美解決WebSocket 伺服器 The WebSocket session [0] has been closed and no method...異常資訊

完美解決WebSocket 伺服器 The WebSocket session [0] has been closed and no method...異常資訊

最近專案需要web客戶端與伺服器保持長連結的場景並需要伺服器向所有連結的客戶端推送訊息,於是自然使用了WebSocket技術,自然要考慮到伺服器於多個客戶端執行緒安全的問題。於是乎,想當然的在WebSocket伺服器端通過一個執行緒安全的佇列來保持所有客戶端的Session.

private volatile static List<Session> sessions = Collections.synchronizedList(new ArrayList());
private  Session session;

    /*
     * 客戶端連結成功後講其儲存線上程安全的集合中
     */
@OnOpen public void onOpen(Session session) throws IOException { this.session = session; sessions.add(this); } /* * 客戶端斷開連結後將其從執行緒安全的集合中移除 */ @OnClose public void onClose() { sessions.remove(this); } //給所有客戶端傳送訊息 public static void sendMessage(String clientInfoJson) { try
{ if (sessions.size() != 0) { for (session s : sessions) { if (s != null) { s.getBasicRemote().sendText(clientInfoJson); } } } } catch (IOException e) { // TODO Auto-generated catch block
e.printStackTrace(); } }

上述程式碼感覺上好像沒問題。Session資訊是儲存線上程安全的集合,又通過volatile變數來修飾保證了記憶體可見性,但實際執行時卻發現並沒有想象的那麼好。當客戶端斷開連結,時伺服器需要傳送訊息給客戶端時.服務端丟擲異常:

IllegalStateException: The WebSocket session [0] has been closed and no method (apart from close()) may be called on a closed session

不難看出,是服務端在關閉Session即將Session從執行緒安全的佇列移除時,在傳送訊息的方法裡應該被移除的Session訊息卻進入了傳送訊息的環節,在執行getBasicRemote().sendText(clientInfoJson);操作時發生了異常。

解決方法:

Google了大量資料後發現如果要解決這種執行緒安全的問題,不能通過執行緒安全的集合來儲存Session解決。而應該儲存整個類,並通過CopyOnWriteArraySet容器來操作。

@ServerEndpoint("/getLocation")
@Component
public class TransmissionLocationWebSocket {

    @Autowired
    public TerminalService terminalServiceInWebSocket;

    private static CopyOnWriteArraySet<TransmissionLocationWebSocket> sessions = new CopyOnWriteArraySet<TransmissionLocationWebSocket>();
    /*
     * 執行緒不安全
     */
    //private volatile static List<Session> sessions = Collections.synchronizedList(new ArrayList());
    private  Session session;

    /*
     * 連結成功後的回掉
     */
    @OnOpen
    public void onOpen(Session session) throws IOException {
        System.out.println("連結成功");
        this.session = session;
        sessions.add(this);
    }

    public static void sendUserLocal(String clientInfoJson,) {
        try {
            if (sessions.size() != 0) {
                for (TransmissionLocationWebSocket s : sessions) {
                    if (s != null) {
                        // 判斷是否為終端資訊。如果是終端資訊則查詢資料庫獲取detail
                            s.session.getBasicRemote().sendText(clientInfoJson);
                     }
            }
        }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @OnClose
    public void onClose() {
        System.out.println("設定離線");
        sessions.remove(this);
    }

}

完美解決
備註:雖然我上面貼出來的程式碼是在COW中儲存了整個類,但我測試的時候發生,儲存Session也是可以的。

Copy-On-Write簡稱COW,是一種用於程式設計中的優化策略。其基本思路是,從一開始大家都在共享同一個內容,當某個人想要修改這個內容的時候,才會真正把內容Copy出去形成一個新的內容然後再改,這是一種延時懶惰策略。從JDK1.5開始Java併發包裡提供了兩個使用CopyOnWrite機制實現的併發容器,它們是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的併發場景中使用到。

CopyOnWrite容器即寫時複製的容器。通俗的理解是當我們往一個容器新增元素的時候,不直接往當前容器新增,而是先將當前容器進行Copy,複製出一個新的容器,然後新的容器裡新增元素,新增完元素之後,再將原容器的引用指向新的容器。這樣做的好處是我們可以對CopyOnWrite容器進行併發的讀,而不需要加鎖,因為當前容器不會新增任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。