最近在新观css3与html5相关的内容时,发现一个不错的好东西WebSocket,以前只是用过Oracle内建包的UTL_TCP,对于直接用socket与第三方服务进行通讯,确实是一件让人比较爽的事情,一些新思路顿时开阔起来。WebSocket允许浏览器里直接调用socket与服务器进行通讯,同时服务器还能主动给浏览器端进行消息推送,确实进一步丰富了BS结构,至少在浏览器里的游戏、新业务的实现,都提供了一个不错的技术选择,但是新技术毕竟还只是起步阶段,还有很多需要不断完善。
下面通过一个聊天室的例子,来慢慢了解WebSocket吧,详细的注释说明参考代码。
运行环境:
1 tomcat 8 用户web服务器及websocket服务器(其实是一个服务器,主要是WebSocket的TCP握手连接协议依赖于http协义,详细的文章参考http://tomcat.apache.org/tomcat-8.0-doc/web-socket-howto.html)
2 Myeclips 10.7,主要是用于类的编写(回头tomcat 8对应的webapps直接改到路径WebRoot对应的路径,即<Context path="/html5" docBase="D:\forDBCl\Html5\WebRoot" reloadable="true"></Context>),该版本的myeclips不支持集成tomcat 8
以上环境最大的不方便是没有办法进行调试。
强烈推荐:Myclips2015,上面可以直接开搞。
2 相关的脚本代码
2.1 WebSocket服务端参考代码,消息处理核心类
package com.cn.ljb.html5;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import com.cn.ljb.util.CommonUtils;
//后面的字符串决定了请建立websocket连接地址内容的一部分
@ServerEndpoint(value = "/websocket/chat")
public class WSChatServlet{
private static final String GUEST_PREFIX = "Guest";
private static final AtomicInteger connectionIds = new AtomicInteger(0);
private static final Set<WSChatServlet> connections =
new CopyOnWriteArraySet<WSChatServlet>();
private Session lsession;
private String connectFlag;
private String nickname = "defaultNick";
public WSChatServlet() {
connectFlag = GUEST_PREFIX + connectionIds.getAndIncrement();
System.out.println("-------WSChatServlet run construct function"+connectFlag);
}
@OnOpen
public void start(Session session) {
if(connections.contains(this))
System.out.println("session has exist in the Set,and Setsize="+connections.size());
else{
lsession = session;
connections.add(this);
}
System.out.println("-----------onOpen CurrentConnections="+connections.size()+",Flag="+connectFlag);
//String message = String.format("* %s %s", connectFlag, ".....加入聊天室......");
//broadcast(message);
}
@OnClose
public void end() {
if(connections.remove(this)){
System.out.println("-----------affter OnClose CurrentConnections="+connections.size());
broadcast("....我离开了聊天室...",this.nickname);
}
}
@OnMessage
public void incoming(String message) {
// Never trust the client
String filteredMessage = CommonUtils.filter(message.toString());
System.out.println("----sendMessage="+filteredMessage);
String data[] = filteredMessage.split("[|]");
if(data!=null && data.length>0){
this.nickname = data[0];
broadcast(data[1],this.nickname);
}
}
@OnError
public void onError(Throwable t) throws Throwable {
System.out.println("--------------Chat Error: " + t.toString());
}
/**
* @param type 1 连接消息 2 正式消息 3 离开消息
* @param msg 消息主体
* @param userName 消息发送人
*/
private static void broadcast(String msg,String userName) {
String jsonData = String.format("[{\"username\":\"%s\",\"content\":\"%s\"}]", userName+" "+CommonUtils.getLongDateNow(1),msg);
for (WSChatServlet client : connections) {
try {
synchronized (client) {
client.lsession.getBasicRemote().sendText(jsonData);
}
} catch (IOException e) {
System.out.println("Chat Error: Failed to send message to client");
connections.remove(client);
try {
client.lsession.close();
} catch (IOException e1) {
// Ignore
}
broadcast("......我离开了......",userName);
}
}
}
}
2.2 相关的工具辅助类,没有什么好讲的
package com.cn.ljb.util;
import java.util.Calendar;
import java.util.Date;
public class CommonUtils {
public static String filter(String message) {
if (message == null)
return (null);
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuilder result = new StringBuilder(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}
public static String getLongDateNow(int dataType) {
Calendar now = Calendar.getInstance();
String dateString = null;
now.setTime(new Date());
switch(dataType){
case 1:
dateString = String.format("%04d-%02d-%02d %02d:%02d:%02d.%03d",
now.get(Calendar.YEAR),
now.get(Calendar.MONTH)+1,
now.get(Calendar.DAY_OF_MONTH),
now.get(Calendar.HOUR_OF_DAY),
now.get(Calendar.MINUTE),
now.get(Calendar.SECOND),
now.get(Calendar.MILLISECOND));
break;
case 2:
dateString = String.format("%04d-%02d-%02d",
now.get(Calendar.YEAR),
now.get(Calendar.MONTH)+1,
now.get(Calendar.DAY_OF_MONTH));
break;
case 3:
now.add(Calendar.DAY_OF_MONTH, -1);
dateString = String.format("%04d-%02d-%02d",
now.get(Calendar.YEAR),
now.get(Calendar.MONTH)+1,
now.get(Calendar.DAY_OF_MONTH));
break;
default:
break;
}
return dateString;
}
}
3 前端页面代码及js代码(合到一起了)
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>测试聊天程序基于WebSocket</title>
<style type="text/css">
#console {
border-radius:5px;
border: 1px solid #CCCCCC;
border-right-color: #999999;
border-bottom-color: #999999;
height: 300px;
overflow-y: scroll;
padding: 5px;
width: 100%;
}
#console p {padding: 0;margin: 0;}
</style>
</head>
<body>
<div id="mainContent" style="margin:0 auto;height:300px;width:50%;text-align:left;">
<div id="console-container" style="margin:0 auto;height:300px;width:100%;padding-bottom:10px;">
<div id="console"></div>
</div>
<p><nav>
<input type="button" value="加入群聊" οnclick="joinChat()">
</nav></p>
<p>
<span>昵称:</span><input id="nickname" type="text" style="width:20%;" placeholder="输入昵称">
<input type="text" style="width:80%;" placeholder="输入你的聊天内容,回车发送" id="chat" /> <input type="button" value="发送消息" οnclick="sendMessage()">
</p>
</div>
</body>
</html>
<script type="text/javascript">
var ynickname=null;
var Chat = {};
Chat.socket = null;
Chat.connect = (function(host) {
if ('WebSocket' in window) {
Chat.socket = new WebSocket(host);
} else if ('MozWebSocket' in window) {
Chat.socket = new MozWebSocket(host);
} else {
Console.log("<h2 style='color: #ff0000'>该浏览器不支持websocket技术,请使用chrome或firefox等其他浏览器!</h2>");
return;
}
Chat.socket.onopen = function () {
document.getElementById('chat').onkeydown = function(event) {
if (event.keyCode == 13) {
sendMessage();
}
};
Chat.socket.send(ynickname+"|我加入了聊天哦");
//Console.log("消息:"+document.getElementById("nickname").value+" connect server success");
};
Chat.socket.onclose = function () {
document.getElementById('chat').onkeydown = null;
//Console.log("消息:"+document.getElementById("nickname").value+" close connection from server");
};
Chat.socket.onmessage = function (message) {
Console.log(message.data);
};
});
Chat.initialize = function() {
if(ynickname==null){
var nkname = document.getElementById('nickname').value;
if(nkname==''){
alert("请先给自己取一个昵称,然后开始聊天呗!");
return;
} else {
ynickname = nkname;
}
} else {
document.getElementById('nickname').value = ynickname;
}
if(Chat.socket==null)
if (window.location.protocol == 'http:') {
Chat.connect('ws://'+window.location.host+'/html5<strong>/websocket/chat</strong>');
} else {
Chat.connect('wss://'+window.location.host+'/html5<strong>/websocket/chat</strong>');
}
};
Chat.sendConnMessage = (function(inputMsg) {
if(inputMsg!='' || inputMsg!=null){
var sendResult = Chat.socket.send(inputMsg);
}
});
Chat.disConnect = (function() {
Chat.socket.onclose();
});
var Console = {};
Console.log = (function(message) {
var jsonData = eval(message);
if(jsonData.length>0){
var console = document.getElementById('console');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.innerHTML = "<font color='blue'>"+jsonData[0].username+":</font><br> "+jsonData[0].content;
console.appendChild(p);
while (console.childNodes.length > 25) {
console.removeChild(console.firstChild);
}
console.scrollTop = console.scrollHeight;
}
});
function joinChat(){
Chat.initialize();
}
function sendMessage(){
if(ynickname==null){
var nkname = document.getElementById('nickname').value;
if(nkname==''){
alert("请先给自己取一个昵称,然后开始聊天呗!");
return;
} else {
ynickname = nkname;
}
} else {
document.getElementById('nickname').value = ynickname;
}
var message = document.getElementById('chat').value;
if (message != '') {
Chat.sendConnMessage(ynickname+"|"+message);
document.getElementById('chat').value = '';
}
}
function leaveChat(){
Chat.disConnect();
}
</script>
最终的基本代码是全部在上面了!我们再来看一个效果截图,拿个拔号路由器配置一条转发,就能轻松跟好友们搭建临时聊天室了。