Java搭建WebSocket的兩種方式


下面分別介紹搭建方法:
一、直接使用Java EE的api進行搭建。
一共3個步驟:
1、添加依賴
<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>7.0</version>
    <scope>provided</scope>
</dependency>
2、使用注解@ServerEndpoint
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

/**
* WebSocket連接 sessionKey是url中的參數
*/
@ServerEndpoint("/websocket/{sessionKey}")
public class WebSocket {

    private static final Logger log = Logger.getLogger(WebSocket.class.getName());

    //靜態變量,用來記錄當前在線連接數。應該把它設計成線程安全的。
    private static int onlineCount = 0;

    //concurrent包的線程安全,用來存放每個客戶端對應的MyWebSocket對象。若要實現服務端與單一客戶端通信的話,可以使用Map來存放,其中Key可以為用戶標識
    private static Map<String, WebSocket> webSockets = new ConcurrentHashMap<>();

    //與某個客戶端的連接會話,需要通過它來給客戶端發送數據
    private Session session;

    /**
     * 連接建立成功調用的方法
     *
     * @param session    可選的參數。session為與某個客戶端的連接會話,需要通過它來給客戶端發送數據
     * @param sessionKey url地址參數
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sessionKey") String sessionKey) {

        if (!webSockets.containsKey(sessionKey)) {
            this.session = session;
            webSockets.put(sessionKey, this);
            addOnlineCount();
            log.info("當前websocket連接數:" + onlineCount);
        }
    }

    /**
     * 連接關閉調用的方法
     *
     * @param sessionKey url地址參數
     */
    @OnClose
    public void onClose(@PathParam("sessionKey") String sessionKey) {

        if (webSockets.containsKey(sessionKey)) {
            webSockets.remove(sessionKey);
            subOnlineCount();
            log.info("當前websocket連接數:" + onlineCount);
        }
    }

    /**
     * 收到客戶端消息后調用的方法
     *
     * @param message 客戶端發送過來的消息
     * @param session 可選的參數
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("來自客戶端的消息:" + message);
    }

    /**
     * 發生錯誤時調用
     *
     * @param session 可選的參數
     * @param error   錯誤消息
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.info("websocket發生錯誤:" + error);
    }

    /**
     * 該方法沒有用注解,是根據自己需要添加的方法。在自己的業務中調用,發送消息給前端。
     *
     * @param sessionKey
     * @param message    返回的結果
     * @throws IOException
     */
    public static void sendMessage(String sessionKey, String message) throws IOException {

        WebSocket webSocket = webSockets.get(sessionKey);

        if (null != webSocket) {

            log.info("websocket發送消息:" + message);

            //同步發送 發送第二條時,必須等第一條發送完成
            webSocket.session.getBasicRemote().sendText(message);

            //異步發送
            //webSocket.session.getAsyncRemote().sendText(message);
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        onlineCount--;
    }

}
3、測試
在線WebSocket測試:http://coolaf.com/tool/chattest
輸入地址進行測試即可,舉例:ws://localhost:8080/websocket/sessionKey
補充:也可以使用java搭建客戶端程序測試,測試的例程放到后面去了~~
二、和springboot整合的WebSocket服務(功能更加強大)
看下效果:<ignore_js_op>
<ignore_js_op>

http://localhost:8080/websocket/sendToUser?username=river&info=你找我干嘛?

 

maven依賴如下:

maven依賴如下:

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-websocket</artifactId>

    <version>2.0.4.RELEASE</version>

</dependency>

1、控制類

package com.boot.river.websocket;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RequestParam;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.socket.TextMessage;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpSession;

 

@Controller

@RequestMapping(value = "/websocket", method = {RequestMethod.POST, RequestMethod.GET})/*GET請求開放用於測試,最好只允許POST請求*/

public class WebSocketController {

 

    @Autowired

    SpringWebSocketHandler springWebSocketHandler;

 

    /**

     * 登錄將username放入session中,然后在攔截器HandshakeInterceptor中取出

     */

    @ResponseBody

    @RequestMapping("/login")

    public String login(HttpServletRequest request, @RequestParam(value = "username") String username, @RequestParam(value = "password") String password) {

        System.out.println("登錄:" + username + ":" + password);

        HttpSession session = request.getSession();

        if (null != session) {

            session.setAttribute("SESSION_USERNAME", username);

            return "success";

        } else {

            return "fail";

        }

    }

 

    /**

     * 指定發送

     */

    @ResponseBody

    @RequestMapping("/sendToUser")

    public String send(@RequestParam(value = "username") String username, @RequestParam(value = "info") String info) {

        springWebSocketHandler.sendMessageToUser(username, new TextMessage(info));

        System.out.println("發送至:" + username);

        return "success";

    }

 

    /**

     * 廣播

     */

    @ResponseBody

    @RequestMapping("/broadcast")

    public String broadcast(@RequestParam(value = "info") String info) {

        springWebSocketHandler.sendMessageToUsers(new TextMessage("廣播消息:" + info));

        System.out.println("廣播成功");

        return "success";

    }

}

2、配置類(實現WebSocketConfigurer接口 )

 

@Configuration

@EnableWebSocket

public class SpringWebSocketConfig implements WebSocketConfigurer {

 

    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {

        registry.addHandler(getSpringWebSocketHandler(), "/websocket/server")

                .addInterceptors(getInterceptor()).setAllowedOrigins("*");

 

        registry.addHandler(getSpringWebSocketHandler(), "/sockjs/server").setAllowedOrigins("*")

                .addInterceptors(getInterceptor()).withSockJS();

    }

 

    @Bean

    public SpringWebSocketHandler getSpringWebSocketHandler() {

        return new SpringWebSocketHandler();

    }

 

    @Bean

    public SpringWebSocketHandlerInterceptor getInterceptor() {

        return new SpringWebSocketHandlerInterceptor();

    }

}

3、處理類(實現了WebSocketHandler接口)

package com.boot.river.websocket;

import org.springframework.web.socket.CloseStatus;

import org.springframework.web.socket.TextMessage;

import org.springframework.web.socket.WebSocketSession;

import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;

import java.util.HashMap;

import java.util.Map;

public class SpringWebSocketHandler extends TextWebSocketHandler {

    /**

     * 存儲用戶id和其對應的session

     */

    private static final Map<String, WebSocketSession> users = new HashMap<>();

 

    /**

     * 用戶名key值

     */

    private static final String USER_ID = "WEBSOCKET_USERID";

 

    /**

     * 連接建立后觸發

     */

    @Override

    public void afterConnectionEstablished(WebSocketSession session) {

        System.out.println("成功建立websocket連接!");

        String userId = (String) session.getAttributes().get(USER_ID);//取出在攔截器中存儲的username

        users.put(userId, session);

        System.out.println("當前線上用戶數量:" + users.size());

    }

 

    /**

     * 關閉連接時觸發

     */

    @Override

    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {

        String userId = (String) session.getAttributes().get(USER_ID);

        System.out.println("用戶" + userId + "已退出!");

        users.remove(userId);

        System.out.println("剩余在線用戶" + users.size());

    }

 

    /**

     * 接收消息

     */

    @Override

    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

        super.handleTextMessage(session, message);

        System.out.println("收到消息:" + message);

        if (message.getPayload().contains("在嗎")) {

           session.sendMessage(new TextMessage("對方不在線!"));

        }

    }

    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {

        if (session.isOpen()) {

            session.close();

        }

        System.out.println("傳輸出現異常,關閉websocket連接... ");

        String userId = (String) session.getAttributes().get(USER_ID);

        users.remove(userId);

    }

    public boolean supportsPartialMessages() {

        return false;

    }

    /**

     * 給某個用戶發送消息

     */

    public void sendMessageToUser(String userId, TextMessage message) {

        for (String id : users.keySet()) {

            if (id.equals(userId)) {

                try {

                    if (users.get(id).isOpen()) {

                        users.get(id).sendMessage(message);

                    }

                } catch (IOException e) {

                    e.printStackTrace();

                }

                break;

            }

        }

    }

 

    /**

     * 給所有在線用戶發送消息

     */

    public void sendMessageToUsers(TextMessage message) {

        for (String userId : users.keySet()) {

            try {

                if (users.get(userId).isOpen()) {

                    users.get(userId).sendMessage(message);

                }

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

    }

}

4、攔截器(實現HandshakeInterceptor接口)

package com.boot.river.websocket;

import org.springframework.http.server.ServerHttpRequest;

import org.springframework.http.server.ServerHttpResponse;

import org.springframework.http.server.ServletServerHttpRequest;

import org.springframework.web.socket.WebSocketHandler;

import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

 

import javax.servlet.http.HttpSession;

import java.util.Map;

 

public class SpringWebSocketHandlerInterceptor extends HttpSessionHandshakeInterceptor {

    @Override

    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,

                                   Map<String, Object> attributes) throws Exception {

        System.out.println("Before Handshake");

        if (request instanceof ServletServerHttpRequest) {

            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;

            HttpSession session = servletRequest.getServletRequest().getSession(false);//獲取session時,如果沒有則返回null

            if (session != null) {

                String userName = (String) session.getAttribute("SESSION_USERNAME");//在登錄時保存的用戶名

                if (userName != null) {

                    attributes.put("WEBSOCKET_USERID", userName);//放入attributes中,可以在處理器的WebSocketSession中取出

                }

            }

        }

        return super.beforeHandshake(request, response, wsHandler, attributes);

    }

 

    @Override

    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,

                               Exception ex) {

        super.afterHandshake(request, response, wsHandler, ex);

        System.out.println("after Handshake");

    }

}

參考:

https://my.oschina.net/u/3445245/blog/3003208

https://blog.csdn.net/runbat/article/details/80985944

https://docs.spring.io/spring/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/websocket.html

下面分別介紹websocket的java客戶端請求和js客戶端請求

1、java客戶端

添加依賴:

<dependency>

      <groupId>org.java-websocket</groupId>

      <artifactId>Java-WebSocket</artifactId>

      <version>1.4.0</version>

</dependency>

package com.river.websocket;

import org.java_websocket.enums.ReadyState;

import java.net.URISyntaxException;

/**

* @author river

* @date 2019-12-6

*/

public class Client {

    public static void main(String[] args) throws URISyntaxException, InterruptedException {

        MyWebSocketClient client = new MyWebSocketClient("ws://localhost:8080/websocket/server");

        client.connect();

        while (client.getReadyState() != ReadyState.OPEN) {

            System.out.println("連接狀態:" + client.getReadyState());

            Thread.sleep(100);

        }

        client.send("測試數據!");

        client.close();

    }

}

繼承WebsocketClient

package com.river.websocket;

import org.java_websocket.client.WebSocketClient;

import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;

import java.net.URISyntaxException;

public class MyWebSocketClient extends WebSocketClient {

    MyWebSocketClient(String url) throws URISyntaxException {

        super(new URI(url));

    }

    @Override

    public void onOpen(ServerHandshake shake) {

        System.out.println(shake.getHttpStatusMessage());

    }

    @Override

    public void onMessage(String paramString) {

        System.out.println(paramString);

    }

 

    @Override

    public void onClose(int paramInt, String paramString, boolean paramBoolean) {

        System.out.println("關閉");

    }

 

    @Override

    public void onError(Exception e) {

        System.out.println("發生錯誤");

    }

}

2、js客戶端

 

<!DOCTYPE html>

<html>

<head>

    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>

    <title>websocket</title>

    <script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.0/jquery.min.js"></script>

    <script type="text/javascript" src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>

    <script type="text/javascript">

        var websocket = null;

        if ('WebSocket' in window) {

            websocket = new WebSocket("ws://localhost:8080/websocket/server");

        } else if ('MozWebSocket' in window) {

            websocket = new MozWebSocket("ws://localhost:8080/websocket/server");

        } else {

            websocket = new SockJS("http://localhost:8080/sockjs/server");

        }

        websocket.onopen = onOpen;

        websocket.onmessage = onMessage;

        websocket.onerror = onError;

        websocket.onclose = onClose;

 

        function onOpen(event) {

            alert(event.type);

        }

 

        function onMessage(messageEvent) {

            alert(messageEvent.data);

        }

 

        function onError(event) {

        }

 

        function onClose(closeEvent) {

            alert(closeEvent.reason);

        }

 

        function doSendUser() {

            if (websocket.readyState === websocket.OPEN) {

                var msg = document.getElementById("inputMsg").value;

                websocket.send(msg);//發送消息

                alert("發送成功!");

            } else {

                alert("連接失敗!");

            }

        }

        window.close = function () {

            websocket.onclose();

        };

        function websocketClose() {

            websocket.close();

            alert("連接關閉");

        }

    </script>

</head>

<body>

請輸入:<input id="inputMsg" name="inputMsg"/>

<button οnclick="doSendUser();">發送</button>

<button οnclick="websocketClose();">關閉連接</button>

</body>

</html>

補充:登錄方式除了在Controller中處理,還可以在Websocket中接收到的消息進行登錄處理。

更多java學習資料可關注:itheimaGZ獲取


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM