1. 程式人生 > >Spring+Websocket實現伺服器與Andoird端通訊

Spring+Websocket實現伺服器與Andoird端通訊

本部落格伺服器端內容參考於部落格:http://www.cnblogs.com/3dianpomian/p/5902084.html
寫這篇部落格的原因是在網上查閱了很多資料,關於websocket的介紹和程式碼很多,但是很少有統一給出伺服器端和Andorid端的具體實現的,在這裡給出我的解決方案,希望可以幫助大家。如有疑問,歡迎留言。

伺服器端

第一步:使用Maven(不會自行百度,這個很實用)自動更新新增依賴包

   <dependency>
         <groupId>org.springframework</groupId>
         <artifactId
>
spring-websocket</artifactId> <version>${srping.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>${srping.version}</version
>
</dependency>

另外還有配置Sping的相關包,不在此全部列出。

第二步:配置WebSocket的入口,編寫WebSocketConfig類實現WebSocketConfigurer 介面

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketHandler(),"/websocket/socketServer.do"
).addInterceptors(new SpringWebSocketHandlerInterceptor()); registry.addHandler(webSocketHandler(), "/sockjs/socketServer.do").addInterceptors(new SpringWebSocketHandlerInterceptor()).withSockJS(); } @Bean public TextWebSocketHandler webSocketHandler(){ return new SpringWebSocketHandler(); } }

第三步:定義一個SpringWebSocketHandler類繼承TextWebSocketHandler,這個類是用來處理Websocket連線建立、斷開,訊息傳送的邏輯的,這個是訊息處理的核心程式碼

public class SpringWebSocketHandler extends TextWebSocketHandler {
    private static final ArrayList<WebSocketSession> users;//這個會出現效能問題,最好用Map來儲存,key用userid
    private static Logger logger = Logger.getLogger(SpringWebSocketHandler.class);
    static {
        users = new ArrayList<WebSocketSession>();
    }

    public SpringWebSocketHandler() {
        // TODO Auto-generated constructor stub
    }

    /**
     * 連線成功時候,會觸發頁面上onopen方法
     */
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // TODO Auto-generated method stub
        System.out.println("connect to the websocket success......當前數量:"+users.size());
        users.add(session);
        //這塊會實現自己業務,比如,當用戶登入後,會把離線訊息推送給使用者
//        TextMessage returnMessage = new TextMessage("你將收到的離線");
//        session.sendMessage(returnMessage);
    }

    /**
     * 關閉連線時觸發
     */
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        logger.debug("websocket connection closed......");
        String username= (String) session.getAttributes().get("WEBSOCKET_USERNAME");
        System.out.println("使用者"+username+"已退出!");
        users.remove(session);
        System.out.println("剩餘線上使用者"+users.size());
    }

    /**
     * js呼叫websocket.send時候,會呼叫該方法
     */
    @Override    
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        logger.debug("message:"+message.getPayload().toString());
        TextMessage returnMessage = new TextMessage(message.getPayload().toString());
        //session.sendMessage(returnMessage);
        //sendMessageToUser("123",returnMessage);
        sendMessageToUsers(returnMessage);
    }

    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        if(session.isOpen()){session.close();}
        logger.debug("websocket connection closed......");
        users.remove(session);
    }

    public boolean supportsPartialMessages() {
        return false;
    }

    /**
     * 給某個使用者傳送訊息
     *
     * @param userName
     * @param message
     */
    public void sendMessageToUser(String userName, TextMessage message) {
        for (WebSocketSession user : users) {
            if (user.getAttributes().get("WEBSOCKET_USERNAME").equals(userName)) {
                try {
                    if (user.isOpen()) {
                        user.sendMessage(message);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
    }

    /**
     * 給所有線上使用者傳送訊息
     *
     * @param message
     */
    public void sendMessageToUsers(TextMessage message) {
        for (WebSocketSession user : users) {
            try {
                if (user.isOpen()) {
                    user.sendMessage(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }   
}

第四步:攔截器配置
從WebSocketConfig中可以看到在註冊WebSocket通道時,不僅設定了入口地址,還配置了攔截器,攔截器可以實現握手之前和之後的邏輯操作,這裡配置的攔截器主要用於儲存使用者名稱以便於在Handler中定向傳送訊息。

public class SpringWebSocketHandlerInterceptor extends HttpSessionHandshakeInterceptor {

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
            Map<String, Object> attributes) throws Exception {
        // TODO Auto-generated method stub
        System.out.println("Before Handshake");
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            HttpSession session = servletRequest.getServletRequest().getSession(false);
            if (session != null) {
                //使用userName區分WebSocketHandler,以便定向傳送訊息
                String userName = (String) session.getAttribute("SESSION_USERNAME");
                if (userName==null) {
                    userName="default-system";
                }
                attributes.put("WEBSOCKET_USERNAME",userName);
            }
        }
        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);
    }
}

第五步:配置Websocket連線前臺頁面
網頁端連線Websocket和推送訊息的介面就是這裡

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
<body>
<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 msgDiv = document.getElementById("#msgDiv");
    var websocket = null;
    if ('WebSocket' in window) {
        websocket = new WebSocket("ws://localhost:8080/bank_pm/websocket/socketServer.do");
    }
    else if ('MozWebSocket' in window) {
        websocket = new MozWebSocket("ws://localhost:8080/bank_pm/websocket/socketServer.do");
    } 
    else {
        websocket = new SockJS("http://localhost:8080/bank_pm/sockjs/socketServer.do");
    }
    websocket.onopen = onOpen;      
    websocket.onmessage = onMessage;
    websocket.onerror = onError;
    websocket.onclose = onClose;

    function onOpen(openEvt) {
        //alert(openEvt.Data+"onOpen");
    }

    function onMessage(evt) {
        alert(evt.data+"onMessage");
    }
    function onError() {
        alert("出錯"+"onError");
    }
    function onClose() {
        alert("關閉"+"onClose");
    }

    function doSend() {
        if (websocket.readyState == websocket.OPEN) {          
            var msg = document.getElementById("inputMsg").value;  
            websocket.send(msg); //呼叫後臺handleTextMessage方法
            alert("傳送成功!");
        } else {  
            alert("連線失敗!");  
        }  
    }
    window.close=function(){
        websocket.onclose();
    }
</script>
<body align="center">
    <h3>訊息推送</h3>
    請輸入:<textarea rows="8" cols="50" id="inputMsg" name="inputMsg"></textarea>
    <button onclick="doSend();">傳送</button>
    <hr/>
    <textarea rows="10" cols="70" id="msgDiv"></textarea>
</body>
</html>

第六步:登入介面與登入的Controller配置
因為我們傳送訊息是需要傳送給登入使用者的,所以在這裡補充登入的相關程式碼:
(1)登入頁面,地址可自己換成自己的:

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>

<body align="center">
    <h2>銀行績效管理系統</h2>
    <form action="${pageContext.request.contextPath}/login.action" method="post">
        登入名:<input type="text" name="username"/><br/>
        密碼 : <input type="text" name="password"/><br/>
        <input type="submit" id="login_btn_id" value="登入"/>
    </form>    
</body>
</html>

(2)登入的Controller編寫:主要是在登入是用HttpSession儲存了使用者名稱,為攔截器獲取使用者名稱做了準備。

@ResponseBody
    @RequestMapping(value="/login.action",method={RequestMethod.POST,RequestMethod.GET})
    public User checkUserAndPassword(
    @RequestParam(value="username",required=true) String username,
    @RequestParam(value="password",required=true) String password,User user,HttpServletRequest request) throws Exception{
        User u = new User();
        //登入成功
        if((u = userService.checkUsernameAndPassword(user)) != null){
            HttpSession session = request.getSession(true);
            session.setAttribute("SESSION_USERNAME", user.getUsername());
            return u;
        };
        //登入失敗,返回Null
        return null;
    }

另:web.xml中的servlet和filter中新增非同步支援

<async-supported>true</async-supported>

Android端

現在,伺服器端通過前臺介面websocket.jsp可以傳送和接收訊息了,那麼Android端是如何接收和傳送訊息的呢?同樣,Android端的處理方法也是用到了WebSocket。
第一步:Android專案中匯入JavaWebSocket_fat.jar包,放到專案的libs目錄下,記得新增檔案依賴哦。

編寫ManActivity測試程式碼
實現思路:在導好包後,在MainActivity中使用WebSocketClient這個類來進行Websocket網路通訊,詳細解釋見程式碼。點選andorid介面上的TextView實現網路連線,在網頁上推送訊息,介面上就會以Toast形式彈出傳送過來的訊息。
MainActivity:

public class MainActivity extends AppCompatActivity {

    private WebSocketClient mWebSocketClient;
    //注意更改成為你的伺服器地址,格式:"ws://ip:port/專案名字/websocket入口地址
    private String address = "ws://192.168.1.115:8080/bank_pm/websocket/socketServer.do";
    private TextView tv;
    private Handler handler = new Handler(){
        public void handleMessage(Message msg){
            if(msg.what == 0x111){
                String news = "";
                news = msg.getData().getString("news");
                Toast.makeText(MainActivity.this, "收到訊息:"+news, Toast.LENGTH_SHORT).show();
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.tv);
        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    initSocketClient();
                    mWebSocketClient.connect();
                } catch (URISyntaxException e) {
                    e.printStackTrace();
                }
            }
        });

    }

    private void initSocketClient() throws URISyntaxException {
        if(mWebSocketClient == null) {
            mWebSocketClient = new WebSocketClient(new URI(address)) {
                @Override
                public void onOpen(ServerHandshake serverHandshake) {
                    //連線成功
                    Log.i("LOG","opened connection");
                }
‘
                @Override
                public void onMessage(String s) {
                    //服務端訊息來了
                    Log.i("LOG","received:" + s);
                    Message msg = Message.obtain();
                    msg.what = 0x111;
                    Bundle bundle = new Bundle();
                    bundle.putString("news",s);
                    msg.setData(bundle);
                    handler.sendMessage(msg);
                }

                @Override
                public void onClose(int i, String s, boolean remote) {
                    //連線斷開,remote判定是客戶端斷開還是服務端斷開
                    Log.i("LOG","Connection closed by " + ( remote ? "remote peer" : "us" ) + ", info=" + s);
                    //
                    closeConnect();
                }

                @Override
                public void onError(Exception e) {
                    Log.i("LOG","error:" + e);
                }
            };
        }
    }

    //斷開連線
    private void closeConnect() {
        try {
            mWebSocketClient.close();
        }
        catch(Exception e) {
            e.printStackTrace();
        }
        finally {
            mWebSocketClient = null;
        }
    }
    //向伺服器傳送訊息的方法
    private void sendMsg(String msg) {
        mWebSocketClient.send(msg);
    }
}

注意:websocket的網路連線自己已經是用了執行緒的,所以不用我們再去寫執行緒操作了。android專案記得新增網路許可權。

PS:OK,終於初步實現了這個東西,以後還是要多擠出時間寫部落格,向大牛邁進!