Spring+Websocket实现服务器与Andoird端通信

本博客服务器端内容参考于博客:http://www.cnblogs.com/3dianpomian/p/5902084.html。
写这篇博客的原因是在网上查阅了很多资料,关于websocket的介绍和代码很多,但是很少有统一给出服务器端和Andorid端的具体实现的,在这里给出我的解决方案,希望可以帮助大家。如有疑问,欢迎留言。

服务器端

第一步:使用Maven(不会自行百度,这个很实用)自动更新添加依赖包

   <dependency>
         <groupId>org.springframeworkgroupId>
         <artifactId>spring-websocketartifactId>
         <version>${srping.version}version>
   dependency>
   <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-messagingartifactId>
        <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 users;//这个会出现性能问题,最好用Map来存储,key用userid
    private static Logger logger = Logger.getLogger(SpringWebSocketHandler.class);
    static {
        users = new ArrayList();
    }

    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 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"%>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title heretitle>
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"%>

<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>trueasync-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,终于初步实现了这个东西,以后还是要多挤出时间写博客,向大牛迈进!

你可能感兴趣的:(Web开发)