关于WebSocket网上已经有不少的介绍了,这里简单复制一下。
websocket采用全双工通信,使服务端也能主动向客户端发送数据。流程为:客户端向服务器发出建立websocket连接的请求,在websocket连接建立之后,客户端和服务端就可以通过TCP连接传输数据。
这里采用spring4.0的框架实现一个,有聊天用户列表(ip地址),显示发送人和和接收人,可以发送图片的例子。
废话少说,先上代码:代码资源下载地址
Java后端首先采用配置的形式实现端口:
WebSoketConfig .java
/**
* @author sgchen项目启动时配置路径
*
*/
@Configuration //定义配置文件
@EnableWebSocket //申明一个服务开启WebSocket
public class WebSoketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry handler) {
// 支持的链接
handler.addHandler(new WebSocketHandler(), "/sgchen").addInterceptors(new WebSocketHandInterceptor())
.setAllowedOrigins("*");
//SocketJs的链接
handler.addHandler(new WebSocketHandler(), "/zh/sgchen").addInterceptors(new WebSocketHandInterceptor())
.withSockJS();
}
}
WebSocketHandInterceptor.java
/**
* @author Administrator
*WebSocket拦截器
*/
public class WebSocketHandInterceptor implements HandshakeInterceptor {
/**
* 初次握手之后
*/
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler, Exception e) {
}
/**
* 初次握手前
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler,
Map<String, Object> map) throws Exception {
if(request instanceof ServletServerHttpRequest) {
HttpServletRequest rq=((ServletServerHttpRequest) request).getServletRequest();
//rq.setCharacterEncoding("UTF-8");
if(ServletFileUpload.isMultipartContent(rq)) {
int len=rq.getContentLength();
ServletInputStream in =rq.getInputStream();
byte[] buffer=new byte[len];
in.read(buffer, 0, len);
//rq.getSession().setAttribute("IMAGE_BLOB", buffer);
map.put("IMAGE_BLOB",buffer);
}
//使用userName区分WebSocketHandler,以便定向发送消息
String host = (String) rq.getRemoteHost();
//存入数据,方便在hander中获取,这里只是在方便在webSocket中存储了数据,
//并不是在正常的httpSession中存储,想要在平时使用的session中获得这里的数据,需要使用session 来存储一下
map.put("REMOTE_HOST",host);
//rq.getSession().setAttribute("WEBSOCKET_USERNAME", host);
}
return true;
}
}
WebSocketHandler .java 类
package com.fiberhome.godway.zdrcommon.WebSocket;
/**
* @author sgchen
*WebSocket消息处理中心
*/
public class WebSocketHandler extends AbstractWebSocketHandler{
public static final Logger logger = Logger.getLogger(WebSocketHandler.class);
public static Collection<WebSocketSession> servers=Collections.synchronizedCollection(new ArrayList<WebSocketSession>());
FileOutputStream os;
//连接关闭之后
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
// TODO Auto-generated method stub
String host=(String) session.getAttributes().get("REMOTE_HOST");
String message = "游客[" + host + "]退出聊天室!";
logger.debug("连接关闭..."+closeStatus.toString());
WebSocketUtil.removeSession(host);
WebSocketUtil.notifyToAll(host,message);
}
//初次连接执行
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// TODO Auto-generated method stub
logger.debug("连接成功...");
String host=(String) session.getAttributes().get("REMOTE_HOST");
WebSocketUtil.addSession(host, session);
String message = "有新人[" + host + "]加入聊天室!";
WebSocketUtil.notifyToAll(host,message);
}
//连接错误
@Override
public void handleTransportError(WebSocketSession session, Throwable throwable) throws Exception {
// TODO Auto-generated method stub
if (session.isOpen()) {
session.close();
}
logger.error("连接错误关闭连接", throwable);
WebSocketUtil.removeSession(session.getAttributes().get("REMOTE_HOST").toString());
}
@Override
public boolean supportsPartialMessages() {
// TODO Auto-generated method stub
return true;
}
//二进制信息
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
// TODO Auto-generated method stub
ByteBuffer buffer=message.getPayload();
os.write(buffer.array());
//os.flush();
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
//判断传输的消息类型
String host=(String) session.getAttributes().get("REMOTE_HOST");
String payLoad=(String) message.getPayload();
if(payLoad.endsWith(":fileStart")) {
os=new FileOutputStream(payLoad.split(":")[0]);
}else if(payLoad.endsWith(":fileNoText")){
os.close();
String fileName=payLoad.split(":")[0];
WebSocketUtil.sendImgToAll(host, fileName);
}else {
WebSocketUtil.sendMessageToAll(host,message.getPayload());
}
}
}
工具类:WebSocketUtil.java
public class WebSocketUtil {
private static final Map<String, WebSocketSession> ONLINE_SESSION=new ConcurrentHashMap<>();
private static final int STATUS=200;
private static final int BINARY_TWO=2;
public static final Logger logger = Logger.getLogger(WebSocketUtil.class);
public static void addSession(String keyName,WebSocketSession session) {
ONLINE_SESSION.put(keyName, session);
}
public static void removeSession(String keyName) {
ONLINE_SESSION.remove(keyName);
}
public static void sendMessage(WebSocketSession session,String message) {
if(session==null) {
return;
}
try {
session.sendMessage(new TextMessage(message));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void sendMessageToAll(String host, String message) {
if(ONLINE_SESSION.isEmpty()) {
return;
}
for (Entry<String, WebSocketSession> entry : ONLINE_SESSION.entrySet()) {
if(entry.getValue().isOpen()) {
String info = "用户[" + host + "]:" + message;
String sendTime=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
String massageType="receiver";
if(host.equals(entry.getKey())) {
massageType="sender";
}
JSONObject infoMap=new JSONObject();
infoMap.put("status", STATUS);
infoMap.put("remark", info);
infoMap.put("host", host);
infoMap.put("total", getTotal());
infoMap.put("message", message);
infoMap.put("messageType", massageType);
infoMap.put("sendTime", sendTime);
sendMessage(entry.getValue(), infoMap.toString());
}
}
}
public static void notifyToAll(String host,String Message) {
if(ONLINE_SESSION.isEmpty()) {
return;
}
for (Entry<String, WebSocketSession> entry : ONLINE_SESSION.entrySet()) {
if(entry.getValue().isOpen()) {
JSONObject infoMap=new JSONObject();
infoMap.put("status", STATUS);
infoMap.put("remark", ONLINE_SESSION.keySet());
infoMap.put("host", host);
infoMap.put("total", WebSocketUtil.getTotal());
infoMap.put("message", Message);
infoMap.put("messageType", "notify");
infoMap.put("sendTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
sendMessage(entry.getValue(), infoMap.toString());
}
}
}
public static void sendImgToAll(String host,String fileName) {
if(ONLINE_SESSION.isEmpty()) {
return;
}
BinaryMessage byteMsg = null;
File file=new File(fileName);
InputStream in = null;
int len=0;
if(file.exists()) {
try {
in=new FileInputStream(fileName);
byte[] bytes=new byte[(int) file.length()];
len=in.read(bytes);
logger.debug("the file size is "+len);
byteMsg=new BinaryMessage(bytes);
in.close();
file.delete();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(in!=null) {
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
if(byteMsg==null) {
return;
}
for (Entry<String, WebSocketSession> entry : ONLINE_SESSION.entrySet()) {
if(entry.getValue().isOpen()) {
String info = "image";
String sendTime=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
String massageType="receiver";
if(host.equals(entry.getKey())) {
massageType="sender";
}
//ByteArrayInputStream blob=new ByteArrayInputStream(byteMsg.getPayload().array());
JSONObject infoMap=new JSONObject();
infoMap.put("status", STATUS);
infoMap.put("remark", info);
infoMap.put("host", host);
infoMap.put("total", getTotal());
infoMap.put("message", byteMsg.getPayload().array());
infoMap.put("messageType", massageType);
infoMap.put("sendTime", sendTime);
sendMessage(entry.getValue(), infoMap.toString());
}
}
}
public static int getTotal() {
return ONLINE_SESSION.size();
}
}
前端boostrap+requireJs+templateJs实现界面渲染
webSocket.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>星空聊天室</title>
<link rel="stylesheet" href="assets/css/bootstrap/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/webSocket.css">
</head>
<body>
<div class="content">
<div class="page-header" id="tou">
<span class="headerTop"> 聊天室</span>
<div class="chartCount">当前在线人数<span class="num">0</span>人</div>
</div>
<div class="well" id="msg">
<div class="chat-detail-bd">
</div>
<div class="nodata">
暂无内容
</div>
</div>
<div class="chat-list">
<ul class="chats">
</ul>
<div class="nodata">
暂无在线人员
</div>
</div>
<div class="col-lg">
<div class="input-group">
<input type="text" class="form-control" placeholder="发送信息..." id="message">
<span class="input-group-btn">
<button class="btn btn-default" type="button" id="send">发送</button>
<a class="img-upload" type="button">
<!-- <img src="../assets/images/rec.png" alt="上传图片">-->图片
</a>
</span>
</div>
<div class="img-display" style="margin-top: 10px;display: none">
<a href="#" class="thumbnail">
<img src="" style="max-height: 90px;">
</a>
</div>
<input type="file" style="display: none" class="img-file"/>
</div>
</div>
<script src="assets/js/lib/jquery/1.8.3/jquery.js"></script>
<script src="assets/js/lib/bootstrap/3.2.0/bootstrap.js"></script>
<script src="assets/js/lib/template/template.js"></script>
<script src="assets/js/lib/require.js"></script>
<script src="assets/js/Websocket.js"></script>
<script>
template.helper("startWith",function(v1,v2){
return v1.startsWith(v2);
})
</script>
<script id="userList" type="text/html">
{{if data.messageType=='notify'}}
{{each data.remark as value i}}
<li class="each-chat">
<span class="badge" style="font-size: 16px;">{{value}}</span>
</li>
{{/each}}
{{/if}}
</script>
<script id="chartContext" type="text/html">
{{if data.status=='200'}}
{{if data.messageType=='receiver'}}
<div class="detail-item-wrap clearfix">
<div class="detail-item other">
<i class="portrait"></i>
<div class="item" type="text">
<div class="item-hd clearfix">
<span class="name">{{data.host}}</span>
<span class="time">{{data.sendTime}}</span>
<!-- <span class="detail">查看明细</span>-->
</div>
<div class="item-bd text">
{{if data.remark=='image'}}
<image style="width: auto;height: 200px;" src="{{data.message}}"></image>
{{else}}
<div class="text-line">{{data.message}}</div>
{{/if}}
</div>
</div>
</div>
</div>
{{/if}}
{{if data.messageType=='sender'}}
<div class="detail-item-wrap clearfix">
<div class="detail-item me">
<i class="portrait"></i>
<div class="item" type="text">
<div class="item-hd clearfix">
<span class="name">{{data.host}}</span>
<span class="time">{{data.sendTime}}</span>
<!-- <span class="detail">查看明细</span>-->
</div>
<div class="item-bd text">
{{if data.remark=='image'}}
<image style="width: auto;height: 200px;" src="{{data.message}}"></image>
{{else}}
<div class="text-line">{{data.message}}</div>
{{/if}}
</div>
</div>
</div>
</div>
{{/if}}
{{if data.messageType=='notify'}}
<div class="detail-item-wrap clearfix">
<div class="detail-item notify">
<div class="text-line"><span style="color:#A63868;margin-right: 10px;">{{data.host}}</span>进入聊天室</div>
</div>
</div>
{{/if}}
{{/if}}
</script>
</body>
</html>
主要的js为:webSocket.js
define([], function () {
var chartCount = 0;//内容计数器
var totalCount = 100;//边界
var websocket = null;
var url = "10.0.18.25:8082/godway";
var app = {
init: function () {
if ('WebSocket' in window) {
websocket = new WebSocket("ws://10.0.18.25:8082/godway/sgchen");
} else if ('MozWebSocket' in window) {
websocket = new MozWebSocket("ws://sgchen");
} else {
websocket = new SockJS("http://" + url + "/ch/sgchen");
}
websocket.onopen = function () {
$("#tou .headerTop").html("连接服务器成功!");
$(".well .nodata").hide();
$(".chat-list .nodata").hide();
};
websocket.onmessage = function (event) {
var rspData = JSON.parse(event.data);
if (rspData && rspData.remark=="image") {
//返回字节时
var url=URL.createObjectURL(app.base64ToBlob(rspData.message));
rspData.message=url;
}
app.setChartPersonTotal(rspData);
app.fillChartMessage(rspData);
app.fillOnlineMemu(rspData);
$(".chat-detail-bd").animate({scrollTop:$(".chat-detail-bd").prop("scrollHeight")},400);
};
websocket.onerror = function (evnt) {
console.log(evnt)
};
websocket.onclose = function (evnt) {
$("#tou .headerTop").html("与服务器断开了连接!");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
//window.onbeforeunload = app.closeWebSocket();
$('#send').bind('click', function () {
app.sendTextMessage();
});
$("#message").keydown(function (event) {
if (event.keyCode == 13) {
$('#send').click();
}
});
//动态生成元素后的点击事件,jquery只有一次有效,所以调用live方法
$(".img-file").live('change',function (event) {
if(!app.imgIsValidate($(this)[0].files[0].name)){
alert("请选择图片类型");
return;
}
$(".img-display").show();
var objectUrl=URL.createObjectURL($(this)[0].files[0]);
$(".img-display .thumbnail img").attr("src",objectUrl);
$(".img-display .thumbnail img").onload=function (ev) {
URL.revokeObjectURL(objectUrl);
}
});
$(".img-upload").on('click', function (event) {
$(".img-file").click();
});
},
disconnect: function () {
if (websocket != null) {
websocket.close();
websocket = null;
}
},
reconnect: function () {
if (websocket != null) {
websocket.close();
websocket = null;
}
if ('WebSocket' in window) {
websocket = new WebSocket("ws://" + url + "/chat.sc");
} else {
websocket = new SockJS("http://" + url + "/sockjs/chat.sc");
}
/* websocket.onopen = app.onOpen();
websocket.onmessage = app.onMesssage();
websocket.onerror = app.onError();
websocket.onclose = app.onClose();*/
},
//发送数据
sendTextMessage: function () {
if (websocket.readyState == 1) { //0-CONNECTING;1-OPEN;2-CLOSING;3-CLOSED
var message = document.getElementById('message').value;
if (message) {
websocket.send(message);
}
document.getElementById('message').value = "";
app.sendFile();
} else {
alert('未与服务器链接.');
}
},
sendFile: function () {
//如果图片存在先发送图片
if ($(".img-display .thumbnail img").attr("src")) {
var file=$(".img-file")[0].files[0];
if(!file){
return;
}
if(!app.imgIsValidate(file.name)){
alert("你发送的不是图片");
return;
}
websocket.send(file.name+":fileStart");
var reader=new FileReader();
//以二进制形式读取文件
reader.readAsArrayBuffer(file);
//文件读取完毕后该函数响应
reader.onload = function loaded(evt) {
var blob = evt.target.result;
//发送二进制表示的文件
websocket.send(blob);
websocket.send(file.name+":fileNoText");
//清空<input type="file">的值
var clearFile=$(".img-file")[0];
clearFile.outerHTML=clearFile.outerHTML;
clearFile.value="";
}
//发送完后
$(".img-display").hide();
//$(".img-display .thumbnail img").attr("src", "");
}
},
//将消息显示在网页上
fillChartMessage: function (rspData) {
if(!rspData){
return;
}
if (chartCount > totalCount) {
$(".chat-detail-bd .detail-item-wrap:lt(41)").remove();
chartCount = totalCount - 40;
}
var html = template('chartContext', {data: rspData});
$("#msg .chat-detail-bd").append(html);
chartCount++;
},
//设置人员总数
setChartPersonTotal: function (rspData) {
$(".page-header .chartCount .num").html(rspData.total);
},
//关闭WebSocket连接
closeWebSocket: function () {
websocket.close();
},
fillOnlineMemu: function (rspData) {
if (rspData.messageType == "notify") {
var html = template('userList', {data: rspData});
$(".chat-list .chats").html(html);
}
},
base64ToBlob:function(urlData){
var binary=atob(urlData);
var array=[];
$.each(binary,function(i,val){
array.push(binary.charCodeAt(i));
})
return new Blob([new Uint8Array(array),{type:"image/jpeg"}])
},
imgIsValidate:function(fileName){
var fileType=fileName.substr(fileName.lastIndexOf(".")).toLowerCase();
if(fileType!=".jpg" && fileType!=".jpeg" && fileType!=".png" && fileType!=".gif"){
return false;
}
return true;
}
}
return app.init()
})
服务器启动后项目界面的效果图为: