一.介紹
- 服務器端和瀏覽器端建立通道,在http協議基礎上實現的.
- 之后的通訊都是通過這個通道進行的,無需在發起請求和響應(這一點與http協議是有區別的,http協議是請求響應模型,每一次與服務器的通訊都是一次和響應)
- 使用最新的websocket JSR356開發規范, tomcat7是支持該規范的,在tomcat7中的http://localhost:8081/examples/websocket/ 有demo展示
socket-1.jpg
在tomcat7的安裝目錄usr/local/tomcat/lib下會存在websocket的jar包
websocket-api.jar
tomcat7-websocket.jar
其中tomcat7-websocket.jar是對websocket-api.jar接口的一個實現
注意:tomcat版本必須是7以上
二.webSockAPI (ServerApplicationConfig)
- 項目啟動時會自動啟動,類似與ContextListener.
是webSocket的核心配置類。
三.ServerApplicationConfig 有兩個方法
getEndPointConfigs 獲取所有以接口方式配置的webSocket類。
getAnnotatedEndpointClasses 掃描src下所有類@ServerEndPoint注解的類。
提示: EndPoint 就指的是 一個webSocket的一個服務端程序
四.實現一個webSocket應用程序,我們要學會幾個基本操作
- 開啟連接
- 客戶端給服務器端發送數據
- 服務器端接收數據
- 服務器端給客戶端發送數據
- 客戶端接收數據
- @ServerEndPoint("/hello")
在webSocket的服務程序類上面加上 注解。
表示的連接路徑是: ws://localhost:8080/helloworld/hello - 監聽三類基本事件: onopen, onmessage,onclose
onmessage 是發送數據時的響應事件。(響應客戶端ws.send()方法發送的數據)
onopen是打開連接時的響應事件。
onclose是關閉連接時的響應事件。(客戶端關閉連接時觸發)
五.編寫demo
兩個webSocket服務:簡單輸出和聊天室
簡單輸出訪問頁面:http://localhost:8081/webSocket1/index.jsp
聊天室訪問頁面:http://localhost:8081/webSocket1/login.jsp
以下具體描述聊天室功能,簡單輸出功能請查看我的代碼庫
項目加載三個包
基礎接口包:websocket-api.jar
tomcat7實現基礎包:tomcat7-websocket.jar
谷歌公司的json和java對象轉換包:gson-2.2.4.jar-
目錄結構如下:
webSocket.jpg SocketConfig.java內容
該文件可以參考tomcat的
/usr/local/tomcat/webapps/examples/WEB-INF/classes/websocket/ExamplesConfig.java
package com.lxf.config;
/**
* ServerApplicationConfig接口的實現類
* 在web容器啟動的加載并執行,
*/
import java.util.Set;
import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
//ServerApplicationConfig接口的實現類會在web容器啟動的時候自動執行
public class SocketConfig implements ServerApplicationConfig{
/**
* @param scaned 代表是由web服務器啟動的時候,掃碼本項目中帶有@ServerEndpoint注解的類,
* 將這些類實例放到該接合中,并傳入到本方法中(也稱依賴注入,被動式編程)
*/
@Override
public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scaned) {
// TODO Auto-generated method stub
System.out.println("websocket config---------------掃描到有websocket的endPoint服務個數:"+scaned.size());
//在這里我們可以做一些過濾操作和一些自己的業務邏輯
//最后給服務器返回,服務器端就會注冊這些socket server,工前臺訪問
return scaned;
}
@Override
public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> arg0) {
// TODO Auto-generated method stub
return null;
}
}
- Message.java內容
package com.lxf.dataVo;
/**
* 用來接收用戶頁面發來的數據的結構
*/
import java.util.Date;
import java.util.List;
public class Message {
private String alert; //
//用戶名
private List<String> names;
//發送的信息
private String sendMsg;
//信息來源
private String from;
//時間
private String date;
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getSendMsg() {
return sendMsg;
}
public void setSendMsg(String sendMsg) {
this.sendMsg = sendMsg;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getAlert() {
return alert;
}
public void setAlert(String alert) {
this.alert = alert;
}
public List<String> getNames() {
return names;
}
public void setNames(List<String> names) {
this.names = names;
}
public Message() {
super();
// TODO Auto-generated constructor stub
}
}
- LoginServlet中doPost()內容
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
request.setCharacterEncoding("utf-8");
//獲取用戶名
String username= request.getParameter("username");
//將用戶名設置為session屬性
request.getSession().setAttribute("username", username);
//重定向的聊天jsp頁面
response.sendRedirect("chat.jsp");
}
以上完整代碼請查看
- 聊天室ChatSocket內容
該文件可以參考tomcat的
/usr/local/tomcat/webapps/examples/WEB-INF/classes/websocket/chat/ChatAnnotation.java
package com.lxf.socket;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import com.google.gson.Gson;
import com.lxf.dataVo.Message;
/**
* websocket 實現聊天室功能
* @author lxf
*
*/
@ServerEndpoint("/chatSocket")
public class ChatSocket {
//存儲每個管道的ChatSocket對象的set集合
private static Set<ChatSocket> sockets=new HashSet<ChatSocket>();
/**存儲以<用戶名,ChatSocket對象>的map,注意:這里必須使用static修飾,因為
在每個客戶端連接過來的時候tomcat都會創建一個ChatSocket實例,當我們使用廣播給多個用戶發消息
或者在TestServlet中自己實例化ChatSocket的時候,給指定用戶發消息,我們會根據sMap(key)里面的session發送
如果不指定為static,sMap會為每個對象局部私有,作用域只在本對象中
使用static修飾,也就意味著所有對象共享sMap
*/
private static Map<String, ChatSocket> sMap=new HashMap<String, ChatSocket>();
//用來存儲所有登錄人的用戶名
private static List<String> names=new ArrayList<String>();
//websocket會話
private Session session;
//當前用戶名
public String username;
//Gson用來處理json和java對象轉換的谷歌開源類庫
public Gson gson=new Gson();
/**
* 當客戶端建立連接的時候觸發
* @param session
*/
@OnOpen
public void open(Session session){
this.session=session;
sockets.add(this);
System.out.println("連接成功:sessionId="+session.getId());
String queryString = session.getQueryString();
System.out.println(queryString);
this.username = queryString.substring(queryString.indexOf("=")+1);
names.add(this.username);
sMap.put(username, this);
Message message=new Message();
message.setAlert(this.username+"進入聊天室!!當前聊天室人數"+sockets.size());
message.setNames(names);
broadcast(sockets, gson.toJson(message) );
}
public Map<String, ChatSocket> getSMap()
{
return this.sMap;
}
/**
* 行客戶端發送消息 ,當客戶端觸發ws.send()方法時觸發
* @param session
* @param msg
*/
@OnMessage
public void receive(Session session,String msg ) throws IOException{
//this.session.getBasicRemote().sendText(msg+"人數:"+sockets.size());
Message message=new Message();
message.setSendMsg(msg);
message.setFrom(this.username);
message.setDate(new Date().toLocaleString());
//指定一個用戶發消息
sendSingleMsg(this.username, message);
//給所有用戶廣播發消息
//broadcast(sockets, gson.toJson(message));
}
/**
* 客戶端關閉瀏覽器時觸發
* @param session
*/
@OnClose
public void close(Session session){
sockets.remove(this);
names.remove(this.username);
Message message=new Message();
message.setAlert(this.username+"退出聊天室!!");
message.setNames(names);
broadcast(sockets, gson.toJson(message));
}
/**
* 給指定客戶端發消息
* @param ss
* @param msg
* @throws IOException
*/
public void sendSingleMsg(String uname, Message message) throws IOException
{
if(sMap.get(uname)!=null)
{
ChatSocket chatSocket = (ChatSocket)sMap.get(uname);
System.out.println(chatSocket.session.getBasicRemote());
//chatSocket.session.getBasicRemote().sendText("hello 111");
chatSocket.session.getBasicRemote().sendText(gson.toJson(message));
}else{
System.out.println("獲取對應用戶信息丟失: " + username);
}
}
/**
* 給所有登錄用戶廣播
* @param ss
* @param msg
*/
public void broadcast(Set<ChatSocket> ss ,String msg ){
for (Iterator iterator = ss.iterator(); iterator.hasNext();) {
ChatSocket chatSocket = (ChatSocket) iterator.next();
try {
System.out.println(chatSocket.session.getBasicRemote());
chatSocket.session.getBasicRemote().sendText(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 用戶登錄login.jsp內容
<%@ 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>
<script type="text/javascript" src="jquery-1.4.4.min.js"></script>
</head>
<body>
<form name="ff" action="LoginServlet" method="post" >
用戶名:<input name="username" /><br/>
<input type="submit" />
</form>
</body>
</html>
- 聊天室chat.jsp內容
該文件可以參考tomcat的
/usr/local/tomcat/webapps/examples/websocket/chat.xhtml
<%@ 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>
<script type="text/javascript" src="jquery-1.4.4.min.js"></script>
<script type="text/javascript">
var ws;
var url="ws://localhost:8081/webSocket1/chatSocket?username=${sessionScope.username}";
window.onload=connect;
function connect(){
if ('WebSocket' in window) {
ws = new WebSocket(url);
} else if ('MozWebSocket' in window) {
ws = new MozWebSocket(url);
} else {
alert('WebSocket is not supported by this browser.');
return;
}
ws.onmessage=function(event){
eval("var result="+event.data);
if(result.alert!=undefined){
$("#content").append(result.alert+"<br/>");
}
if(result.names!=undefined){
$("#userList").html("");
$(result.names).each(function(){
$("#userList").append(this+"<br/>");
});
}
if(result.from!=undefined){
$("#content").append(result.from+" "+result.date+
" 說:<br/>"+result.sendMsg+"<br/>");
}
};
}
function send(){
var value= $("#msg").val();
ws.send(value);
}
</script>
</head>
<body>
<h3>歡迎 ${sessionScope.username } 使用本系統!!</h3>
<div id="content" style="
border: 1px solid black; width: 400px; height: 300px;
float: left;
" ></div>
<div id="userList" style="
border: 1px solid black; width: 100px; height: 300px;
float:left;
" ></div>
<div style="clear: both;" >
<input id="msg" /><button onclick="send();" >send</button>
</div>
</body>
</html>
- 以上完成后,啟動tomcat,啟動的過程中會在控制臺中看到:
websocket config---------------掃描到有websocket的endPoint服務個數:2
以上是SocketConfig.java的getAnnotatedEndpointClasses方法執行的,因為我的項目中有簡單輸出和聊天室兩個webSocket服務,所以websocket的endPoint服務個數:2
- 訪問用戶登錄頁面
http://localhost:8081/webSocket1/login.jsp
- 登錄后跳轉到
http://localhost:8081/webSocket1/chat.jsp
進行聊天
- 此時可以多開幾個瀏覽器測試
六.總結
- 配置好服務端后每個客戶端瀏覽器都可以 Chat.socket = new WebSocket(host);這種方式與服務端生成管道,進行通訊;
- tomcat服務器啟動的時候會掃碼所有帶注解
@ServerEndpoint("/chatSocket")
的類,并將其發在ServerApplicationConfig的實現類中的scaned的set入參中
@Override
public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scaned) {
// TODO Auto-generated method stub
System.out.println("websocket config---------------掃描到有websocket的endPoint服務個數:"+scaned.size());
//在這里我們可以做一些過濾操作和一些自己的業務邏輯
//最后給服務器返回,服務器端就會注冊這些socket server,工前臺訪問
return scaned;
}
- 以上這個類負責過濾一些必要的信息,然后必須返回
return scaned
,tomcat啟動返回的scaned集合中的webSocket服務(也就相當于在web容器中有對應的webSocket實例),供客戶端連接; - 注意:webSocket是多個實例的,每個通道一個實例;
- 所以我們如果想在servlet中或者是java類中自己指定用戶發消息,必須將每個通道實例放在webSocket類中有
static
修飾的Map中,集合key=用戶標識,value=該通道的實例,如下:
private static Map<String, ChatSocket> sMap=new HashMap<String, ChatSocket>();
- 如果想在servlet中自行給指定用戶發送消息可以進行如下操作:(注意:指定的用戶必須提前通過客戶端瀏覽器發起連接)TestServlet.java
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
//response.getWriter().append("Served at: ").append(request.getContextPath());
ChatSocket chat = new ChatSocket();
Map<String, ChatSocket> sMap = chat.getSMap();
Message message=new Message();
message.setSendMsg("我是自己的servlet給liangxifeng發送的信息,別人是收不到!");
message.setFrom("servlet");
message.setDate(new Date().toLocaleString());
sMap.get("liangxifeng").sendSingleMsg("liangxifeng", message);
response.getWriter().append("Served at: ").append(request.getContextPath());
}
以上完整代碼請查看
- 訪問:http://localhost:8081/webSocket1/TestServlet,
就可以單獨給用戶liangxifeng發送消息了,而不經過客戶端瀏覽器.