利用websocket和java完成的消息推送功能,服務器用的是tomcat7.0.42,一些東西是本身琢磨的,也不知道恰不恰當,不恰當處,還請各位見諒,並指出。javascript
程序簡單來講,就是客戶A能夠發送消息給客戶B,但有不少能夠擴展的地方,html
好比html5
1.若是加入數據庫後,A發消息時客戶B未上線,服務端將會把消息存在數據庫中,等客戶B上線後,在將消息取出發送給客戶Bjava
2.服務端也可發送消息到任意客戶端上。node
程序的運行效果截圖以下(在chrome,搜狗,firefox下測試經過):代碼將在最後給出jquery
首先咱們打開一個瀏覽器,顯示輸入您的名字,這裏我輸入soarweb
在打開第二個瀏覽器,這裏我輸入billajax
這是若是我發送hello bill i am soar給bill,點擊sendchrome
在另外一個瀏覽器上就能夠看到數據庫
Websocket
1.websocket是什麼?
WebSocket是爲解決客戶端與服務端實時通訊而產生的技術。其本質是先經過HTTP/HTTPS協議進行握手後建立一個用於交換數據的TCP鏈接,
此後服務端與客戶端經過此TCP鏈接進行實時通訊。
2.websocket的優勢
之前咱們實現推送技術,用的都是輪詢,在特色的時間間隔有瀏覽器自動發出請求,將服務器的消息主動的拉回來,在這種狀況下,咱們須要不斷的向服務器發送請求,然而HTTP request 的header是很是長的,裏面包含的數據可能只是一個很小的值,這樣會佔用不少的帶寬和服務器資源。會佔用大量的帶寬和服務器資源。
WebSocket API最偉大之處在於服務器和客戶端能夠在給定的時間範圍內的任意時刻,相互推送信息。在創建鏈接以後,服務器能夠主動傳送數據給客戶端。
此外,服務器與客戶端之間交換的標頭信息很小。
WebSocket並不限於以Ajax(或XHR)方式通訊,由於Ajax技術須要客戶端發起請求,而WebSocket服務器和客戶端能夠彼此相互推送信息;
關於ajax,comet,websocket的詳細介紹,和websocket報文的介紹,你們能夠參看http://www.shaoqun.com/a/54588.aspx 網頁設計]Ajax、Comet與Websocket,
我若是之後有時間,也會寫出來的
3.如何使用websocket
客戶端
在支持WebSocket的瀏覽器中,在建立socket以後。能夠經過onopen,onmessage,onclose即onerror四個事件實現對socket進行響應
一個簡單是示例
var ws = new WebSocket(「ws://localhost:8080」); ws.onopen = function() { console.log(「open」); ws.send(「hello」);
}; ws.onmessage = function(evt) { console.log(evt.data) }; ws.onclose = function(evt) { console.log(「WebSocketClosed!」); }; ws.onerror = function(evt) { console.log(「WebSocketError!」); };
1.var ws = new WebSocket(「ws://localhost:8080」);
申請一個WebSocket對象,參數是須要鏈接的服務器端的地址,同http協議使用http://開頭同樣,WebSocket協議的URL使用ws://開頭,另外安全的WebSocket協議使用wss://開頭。
ws.send(「hello」);
用於叫消息發送到服務端
2.ws.onopen = function() { console.log(「open」)};
當websocket建立成功時,即會觸發onopen事件
3.ws.onmessage = function(evt) { console.log(evt.data) };
當客戶端收到服務端發來的消息時,會觸發onmessage事件,參數evt.data中包含server傳輸過來的數據
4.ws.onclose = function(evt) { console.log(「WebSocketClosed!」); };
當客戶端收到服務端發送的關閉鏈接的請求時,觸發onclose事件
5.ws.onerror = function(evt) { console.log(「WebSocketError!」); };
若是出現鏈接,處理,接收,發送數據失敗的時候就會觸發onerror事件
咱們能夠看出全部的操做都是採用事件的方式觸發的,這樣就不會阻塞UI,使得UI有更快的響應時間,獲得更好的用戶體驗。
服務端
如今有不少的服務器軟件支持websocket,好比node.js,jetty,tomcat等
這裏我使用的是tomcat-7.0.42和eclipse4.2
在tomcat下使用websocket首先須要導入相關的jar
tomcat7提供的與WebSocket相關的類均位於包org.apache.catalina.websocket之中(包org.apache.catalina.websocket的實現包含於文件catalina.jar之中
這裏咱們把tomcat的所有導入就好了
在build path->configure build path->librarise->add library->server runtime->apache tomcat v7.0
同時須要import如下包
import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WsOutbound;
import org.apache.catalina.websocket.WebSocketServlet;
咱們須要兩個類
第一個用於處理websocket請求
第二個用於處理每一次具體的WebSocket任務
第一個類
public class SocketServer extends WebSocketServlet { private static final long serialVersionUID = 1L; //…… @Override protected StreamInbound createWebSocketInbound(String arg0, HttpServletRequest arg1) { // TODO Auto-generated method stub return new ChatWebSocket(users); } }
這個Servlet繼承自WebSocketServlet,實現createWebSocketInbound方法。該方法返回第二個類的實例。
第二個類
public class ChatWebSocket extends MessageInbound { @Override protected void onTextMessage(CharBuffer message) throws IOException { } @Override protected void onOpen(WsOutbound outbound) { } @Override protected void onClose(int status) { } @Override protected void onBinaryMessage(ByteBuffer arg0) throws IOException { } //其他略 }
protected void onTextMessage(CharBuffer message) throws IOException { }
文本消息響應
protected void onBinaryMessage(ByteBuffer arg0) throws IOException { }
二進制消息響應
protected void onOpen(WsOutbound outbound) { }
創建鏈接的觸發的事件
protected void onClose(int status) { }
關閉鏈接時觸發的事件
4.程序代碼
html部分
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <script type="text/javascript" src="js/jquery.js"></script> <script type="text/javascript" src="js/socket.js"></script> <title>無標題文檔</title> </head> <script language="javascript"> </script> <body> <table> <tr> <td>Message</td> <td><input type="text" id="message"></td> </tr> <tr> <td>Name</td> <td><input type="text" id="othername"></td> </tr> <tr> <td><input id="sendbutton" type="button" value="send" onClick="click" disabled="true"> </input></td> </tr> </table> <script> </script> </body> </html>
js部分(關於jquery部分不進行講解)
var username = window.prompt("輸入你的名字:"); document.write("Welcome<p id=\"username\">"+username+"</p>"); if (!window.WebSocket && window.MozWebSocket) window.WebSocket=window.MozWebSocket; if (!window.WebSocket) alert("No Support "); var ws; $(document).ready(function(){ $("#sendbutton").attr("disabled", false); $("#sendbutton").click(sendMessage); startWebSocket(); }) function sendMessage() { var othername=$("#othername").val(); var msg="MSG\t"+username+"_"+othername+"_"+$("#message").val(); send(msg); } function send(data) { console.log("Send:"+data); ws.send(data); } function startWebSocket() { ws = new WebSocket("ws://" + location.host + "/WebSocket/SocketServer"); ws.onopen = function(){ console.log("success open"); $("#sendbutton").attr("disabled", false); }; ws.onmessage = function(event) { console.log("RECEIVE:"+event.data); handleData(event.data); }; ws.onclose = function(event) { console.log('Client notified socket has closed',event); }; } function handleData(data) { var vals=data.split("\t"); var msgType=vals[0]; switch(msgType) { case "NAME": var msg=vals[1]; var mes="NAME"+"\t"+msg+"_"+ username; send(mes); break; case "MSG": var val2s=vals[1].split("_"); var from=val2s[0]; var message=val2s[2]; alert(from+":"+message); break; default: break; } }
java部分
import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import javax.servlet.http.HttpServletRequest; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import org.apache.catalina.websocket.MessageInbound; import org.apache.catalina.websocket.StreamInbound; import org.apache.catalina.websocket.WsOutbound; import org.apache.catalina.websocket.WebSocketServlet; public class SocketServer extends WebSocketServlet { private static final long serialVersionUID = 1L; public final Set<ChatWebSocket> users = new CopyOnWriteArraySet<ChatWebSocket>(); public static int USERNUMBER = 1; @Override protected StreamInbound createWebSocketInbound(String arg0, HttpServletRequest arg1) { // TODO Auto-generated method stub return new ChatWebSocket(users); } public class ChatWebSocket extends MessageInbound { private String username; private Set<ChatWebSocket> users = new CopyOnWriteArraySet<ChatWebSocket>();; public ChatWebSocket() { } public ChatWebSocket(Set<ChatWebSocket> users) { this.users = users; } @Override protected void onTextMessage(CharBuffer message) throws IOException { // 這裏處理的是文本數據 } public void onMessage(String data) { String[] val1 = data.split("\\t"); if(val1[0].equals("NAME")) { String[] val2=val1[1].split("_"); for(ChatWebSocket user:users){ if (user.username.equals(val2[0])){ user.username=val2[1]; } } } else if(val1[0].equals("MSG")) { String[] val2=val1[1].split("_"); for(ChatWebSocket user:users){ if (user.username.equals(val2[1])){ try { CharBuffer temp=CharBuffer.wrap(data); user.getWsOutbound().writeTextMessage(temp); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } else { System.out.println("ERROR"); } } @Override protected void onOpen(WsOutbound outbound) { // this.connection=connection; this.username = '#' + String.valueOf(USERNUMBER); USERNUMBER++; try { String message = "NAME" + "\t" + this.username; CharBuffer buffer = CharBuffer.wrap(message); this.getWsOutbound().writeTextMessage(buffer); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } users.add(this); } @Override protected void onClose(int status) { users.remove(this); } @Override protected void onBinaryMessage(ByteBuffer arg0) throws IOException { } } }
解釋
這裏個人想法是
1 每一個用戶在訪問的時候首先須要輸入本身的名字,接着向服務端發送鏈接請求
2 服務端在接受到客戶端的鏈接請求後,會new ChatWebSocket(users);用於處理這個請求,並把它加入在線的用戶列表中,因爲這個時候,服務端尚不知道客戶的名字。它會給這個用戶假定一個名字,#1,而後服務端會發送"NAME" + "\t" +「#1」給客戶端,你叫什麼?
3 客戶端收到這個消息會知道,服務器在問本身叫什麼名字,因而客戶端會發送"NAME"+"\t"+「#1」+"_"+ 本身的名字到服務端,(我叫xxx)
4 服務端收到這個消息後根據#1在當前在線的用戶列表中進行查找,將#1替換爲客戶的名字,這樣服務端就知道了這個客戶的名字了
5 當客戶離開時,服務端會觸發onClose事件,服務端會把當前用戶從在線列表中移除
用圖畫出來相似這樣(畫的很差,—_—!!)
代碼
js
ws = new WebSocket("ws://" + location.host + "/WebSocket/SocketServer");
鏈接服務端
java
protected StreamInbound createWebSocketInbound(String arg0, HttpServletRequest arg1) { // TODO Auto-generated method stub return new ChatWebSocket(users); }
建立一個chatwebsocket用於處理這個請求,觸發該chatwebsocket對象的onOpen事件
@Override protected void onOpen(WsOutbound outbound) { // this.connection=connection; this.username = '#' + String.valueOf(USERNUMBER); USERNUMBER++; try { String message = "NAME" + "\t" + this.username; CharBuffer buffer = CharBuffer.wrap(message); this.getWsOutbound().writeTextMessage(buffer); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } users.add(this); }
爲這個客戶假定一個姓名,併發送NAME+「\t」+假定的姓名 給該客戶端,同時將該客戶端加入當前鏈接的客戶列表中
js
function handleData(data) { var vals=data.split("\t"); var msgType=vals[0]; switch(msgType) { case "NAME": var msg=vals[1]; var mes="NAME"+"\t"+msg+"_"+ username; send(mes); break; //……… } }
接受並處理服務端發來到的消息,發現是服務端問本身叫什麼名字,因而發送」NAME"+"\t"+假定的名字+"_"+ 真正的名字 給服務端
java
public void onMessage(String data) { String[] val1 = data.split("\\t"); if(val1[0].equals("NAME")) { String[] val2=val1[1].split("_"); for(ChatWebSocket user:users){ if (user.username.equals(val2[0])){ user.username=val2[1]; } } } //……… }
處理並接受客戶端發來的消息,發現是客戶端回覆本身叫什麼名字,因而在根據先前假定的名字在當前鏈接的客戶列表中進行查找,將假名變成真名
js
function sendMessage() { var othername=$("#othername").val(); var msg="MSG\t"+username+"_"+othername+"_"+$("#message").val(); send(msg); }
客戶對另外一我的發起對話,消息格式爲:「MSG」+本身的名字+_+對方的名字+_+消息
java
public void onMessage(String data) { ///………… else if(val1[0].equals("MSG")) { String[] val2=val1[1].split("_"); for(ChatWebSocket user:users){ if (user.username.equals(val2[1])){ try { CharBuffer temp=CharBuffer.wrap(data); user.getWsOutbound().writeTextMessage(temp); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } ///………… }
發現是客戶發送的消息,根據對方的姓名,在當前鏈接的客戶列表中查找,並將消息發給他
js
function handleData(data) { var vals=data.split("\t"); var msgType=vals[0]; switch(msgType) { ///… case "MSG": var val2s=vals[1].split("_"); var from=val2s[0]; var message=val2s[2]; alert(from+":"+message); break; default: break; } }
發現是另外一個客戶發來的消息,經過alert顯示出來
java
@Override protected void onClose(int status) { users.remove(this); }
發現客戶離開了,將客戶從鏈接的客戶列表中移除
能夠改進的地方
1.若客戶端A發送消息給B時,B不在線,可將消息存入數據庫中,當發現B上線時,從數據庫中取出,發送給B
2 服務端發送你叫什麼時,可加入超時機制,若客戶端必定時間內沒有回覆本身叫什麼,則可將該客戶從在線列表中刪掉