本博客服务器端内容参考于博客: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>
现在,服务器端通过前台界面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,终于初步实现了这个东西,以后还是要多挤出时间写博客,向大牛迈进!