A、Comet- Ajaxベースのロングポーリング
定義:クライアントは、サーバーのAjaxにリクエストを送信し、要求に応じてライブ接続サーバを保持し、新しいメッセージ応答情報まで戻り、接続を閉じていない、クライアントがサーバーへの要求の後に、新たな応答情報を送信します。
- サーバーは、要求がタイムアウトまたは戻りデータ転送までブロックされます。
- クライアント側のJavaScript関数の処理応答メッセージは接続を再確立するために、再度、処理の完了時に、サーバによって要求を返さ。
- クライアントが受信したデータは、再構築の接続を処理するとき、サーバは、新しいデータが到着有することができ、この情報は接続の再確立をクライアントまで保存され、クライアントはサーバ側ですべての情報を取得するために、現在のサーバーいったん終了します。
利点は:ない、頻繁場合、リソースの小さな消費せずにメッセージをお願いします。
欠点は:サーバーは、接続がデータを管理し、維持することが難しいために、返されないことを確保するために、リソースを消費します保持します。
<%@ 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>
<head>
<base href="<%=basePath%>">
<title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<link rel="stylesheet"
href="http://apps.bdimg.com/libs/jquerymobile/1.4.5/jquery.mobile-1.4.5.min.css">
<!-- 引入 jQuery 库 -->
<script src="http://apps.bdimg.com/libs/jquery/1.10.2/jquery.min.js"></script>
<!-- 引入 jQuery Mobile 库 -->
<script
src="http://apps.bdimg.com/libs/jquerymobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
<script type="text/javascript">
$(function() {
getMsgNum();
});
function getMsgNum() {
$.ajax({
url : 'JsLongPollingMsgServlet',
type : 'post',
dataType : 'json',
data : {
"pageMsgNum" : $("#pageMsgNum").val()
},
timeout : 5000,
success : function(data, textStatus) {
if (data && data.msgNum) {
//请求成功,刷新数据
$("#msgNum").html(data.msgNum);
//这个是用来和后台数据作对比判断是否发生了改变
$("#pageMsgNum").val(data.msgNum);
}
if (textStatus == "success") {
//成功之后,再发送请求,递归调用
getMsgNum();
}
},
error : function(XMLHttpRequest, textStatus, errorThrown) {
if (textStatus == "timeout") {
//有效时间内没有响应,请求超时,重新发请求
getMsgNum();
} else {
// 其他的错误,如网络错误等
getMsgNum();
}
}
});
}
</script>
</head>
<body>
<div id="page1" data-role="page">
<div data-role="header">
<h1>AJAX长轮询</h1>
</div>
<div data-role="content">
<input id="pageMsgNum" name="pageMsgNum" type="hidden" /> 您有<span
id="msgNum" style="color: red;">0</span>条消息!
</div>
<div data-role="footer">
<h1>CopyRight 2019</h1>
</div>
</div>
</body>
</html>
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class JsLongPollingMsgServlet
*/
@WebServlet("/JsLongPollingMsgServlet")
public class JsLongPollingMsgServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public JsLongPollingMsgServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
ServletContext application = this.getServletContext();
List msglist= (List)application.getAttribute("msg");
request.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String pageMsgNumStr = request.getParameter("pageMsgNum");
if(pageMsgNumStr==null || "".equals(pageMsgNumStr)){
pageMsgNumStr = "0";
}
int pageMsgNum = Integer.parseInt(pageMsgNumStr);
int num = 0;
StringBuffer json = null;
while(true){
num = msglist.size();
//数据发生改变 将数据响应客户端
if(num != pageMsgNum){
json = new StringBuffer("{");
json.append("\"msgNum\":"+num);
json.append("}");
break;
}else{
//没有新的数据 保持住连接
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
out.write(json.toString());
out.close();
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
二、流れとiframe(ストリーミング)モードに基づいてComet- HTMLFILE
- ページ内のデータを返さないクライアントプログラムはiframe、iframeのサーバが直接表示され、代わりに、このような「ます。<script type =」text / javascriptの「> js_func(」サーバーからのデータ」として呼び出すクライアントJavaScript関数に戻り、 )</スクリプト>」。クライアント側のJavaScript関数のパラメータが渡されたサーバーはデータを返します。クライアントブラウザのJavaScriptエンジンを見返りに呼び出しが、サーバーのJavaScriptコードの実行に行きます受け取ります。
- 接続は、接続が唯一の通信エラーが発生した場合、またはオフ(一部のファイアウォールは、多くの場合、接続をドロップするように、あまりにも長く設定されている各データ転送が近い、サーバがタイムアウト通知クライアントの接続再確立後にタイムアウトを設定することはありません)接続を再確立し、元の接続を閉じます。
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<ul id="content"></ul>
<form class="form">
<input type="text" placeholder="请输入发送的消息" class="message" id="message"/>
<input type="button" value="发送" id="send" class="connect"/>
<input type="button" value="连接" id="connect" class="connect"/>
</form>
<script>
var oUl=document.getElementById('content');
var oConnect=document.getElementById('connect');
var oSend=document.getElementById('send');
var oInput=document.getElementById('message');
var ws=null;
oConnect.onclick=function(){
ws=new WebSocket('ws://localhost:3000');
ws.onopen=function(){
oUl.innerHTML+="<li>客户端已连接</li>";
}
ws.onmessage=function(evt){
oUl.innerHTML+="<li>"+evt.data+"</li>";
}
ws.onclose=function(){
oUl.innerHTML+="<li>客户端已断开连接</li>";
};
ws.onerror=function(evt){
oUl.innerHTML+="<li>"+evt.data+"</li>";
};
};
oSend.onclick=function(){
if(ws){
ws.send(oInput.value);
}
}
</script>
</body>
</html>
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class SetMsg
*/
@WebServlet("/SetMsg")
public class SetMsg extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public SetMsg() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
ServletContext application = this.getServletContext();
List msglist= new ArrayList();
if(application.getAttribute("msg")!=null)
{
msglist=(List)application.getAttribute("msg");
}
msglist.add(request.getParameter("msgstr"));
application.setAttribute("msg", msglist);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
三、SSH
- SSEとのWebSocket同様の効果は、ブラウザに情報をプッシュするブラウザとサーバとサーバ間の通信チャネルを確立しています。
- 全体的に、WebSocketをより強力で柔軟な。それは、全二重チャネル、双方向通信可能であるため、SSEは一方通行チャネルである情報の流れは、本質的にダウンロードされるので、唯一のサーバーは、ブラウザに送信します。ブラウザがサーバーに情報を送信する場合、それは別のHTTPリクエストとなります。
- SSEの利点
- SSEは、既存のサーバソフトウェアのサポートをHTTPプロトコルを使用して。WebSocketのは、別の契約です。
- SSEは、使用する軽量、シンプルに属し;のWebSocketプロトコルは比較的複雑です。
- SSEはのWebSocketは、独自の実装を必要とする、デフォルトの再接続でサポートされています。
- SSEは、一般に、送信テキスト、転写後に符号化されるバイナリデータ、バイナリデータのAのWebSocketデフォルト支持送信に使用されます。
- カスタムメッセージタイプのSSEのサポートが送られました。
- SSEは頻繁に更新、低レイテンシのに適しているとのデータがサービスからクライアントにしています。
通信プロトコルは、プレーンテキストに基づく単純なプロトコルです。
SSEデータ・サーバがブラウザに送信すると、次のHTTPヘッダで、UTF-8のテキストにエンコードする必要があります。
コンテンツタイプ:テキスト/イベントストリーム
Cache-Control:キャッシュなし
接続:キープアライブ
MIMEのContent-Typeの最初の行は、のタイプを指定する必要があります。イベント・スチームを。
各時間で、メッセージ組成物の複数から送信された情報\ n \ nは各メッセージの間の分離。各内部メッセージは行で構成され、各行は、次の形式です。
[フィールド]:値\ nは
データ型は、行にデータが含まれていることを示します。データで始まる行は複数回出現することができます。すべてのこれらの行は、イベントのデータです。
データが長い場合には、複数の行、\ nの最前列の端部と最後の行\ n \終了N、に分けることができます。
上記ID上記のタイプ、バンク識別子は、データの各部分の当量数のイベントを宣言するために使用されると述べました。
タイプイベントのうち、銀行はイベントの種類を宣言するために使用されていると述べました。ブラウザがデータを受信すると、そのイベントの対応するタイプを生成します。デフォルトでは、メッセージイベントです。ブラウザは、()のaddEventListenerで、このイベントに耳を傾けることができます。
タイプの再試行は、銀行が切断後再接続までの時間を待って、ブラウザを宣言するために使用されていると述べました。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script>
var eventSource;
function start() {
eventSource = new EventSource("HelloServlet");
eventSource.onmessage = function(event) {
document.getElementById("foo").innerHTML = event.data;
};
eventSource.addEventListener("ms",function(){})
}
function close(){
eventSource.close();
}
</script>
</head>
<body>
Time: <span id="foo"></span>
<br><br>
<button onclick="start()">Start</button>
<button onclick="close()">Close</button>
</body>
</html>
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/HelloServlet")
public class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public HelloServlet() {
super();
// TODO Auto-generated constructor stub
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
// ContentType 必须指定为 text/event-stream
response.setContentType("text/event-stream");
// CharacterEncoding 必须指定为 UTF-8
response.setCharacterEncoding("UTF-8");
PrintWriter pw = response.getWriter();
// 每次发送的消息必须以\n\n结束
pw.write("event:ms\n data: " + new Date() + " 这是第1次测试\n\n");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
pw.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
四、WebSocketを
原理:
クライアントがサーバーに接続すると、それがサーバーに似た次のHTTPパケットを送信します:
あなたが見ることができ、これはHTTP GETリクエストメッセージ、アップグレードヘッダがあるメッセージを注意され、その役割は、サービスを伝えることですエンド通信プロトコルは、サーバがサポート用WebSocket契約は、それはのWebSocketの通信プロトコルの切り替えを所有する場合、クライアントに同時に、のWebSocketに次のヘッダのような応答切り替える必要がある。
リターン・ステータス・コード101 、それはクライアントプロトコル変換要求に同意した、とのWebSocket契約に変換します。このハンドシェイク、接続用WebSocketを確立するために、クライアントとサーバーに行った後に通信がWebSocketの合意を取った後に上記のプロセスは、HTTP通信と呼ばれるのWebSocketプロトコルのハンドシェイク(のWebSocketプロトコルハンドシェイク)を用いて達成されます。だから、のWebSocketは、HTTPプロトコルのハンドシェイクの助けを必要と要約すると、接続は、通信処理用WebSocket契約を使用して確立されます。接続はWebSocketのに基づいていることを理解するために同じ時間が必要で、私たちはTCP、HTTP接続を開始しました。テキストデータとバイナリデータ:接続が確立された後、我々はデータを転送することができたら、WebSocketのは、2つのデータ伝送を提供します。
サーバー側-javax.websocket
クライアント
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE HTML>
<html>
<head>
<title>Java后端WebSocket的Tomcat实现</title>
<script type="text/javascript">
var websocket = null;
var host = document.location.host;
function connect() {
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
var value = document.getElementById("b").value;
websocket = new WebSocket("ws://" + host + "/exam5/websocket/" + value);
//连接发生错误的回调方法
websocket.onerror = function() {
setMessageInnerHTML("WebSocket连接发生错误");
};
//连接成功建立的回调方法
websocket.onopen = function() {
setMessageInnerHTML("WebSocket连接成功");
}
//接收到消息的回调方法
websocket.onmessage = function(event) {
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function() {
setMessageInnerHTML("WebSocket连接关闭");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function() {
closeWebSocket();
}
} else {
alert('当前浏览器 Not support websocket')
}
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//关闭WebSocket连接
function closeWebSocket() {
websocket.close();
}
//发送消息
function send() {
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</head>
<body>
Welcome
<br />
<input id="text" type="text" />
<input type="button" onclick="send()" value="发送消息"/>
<br />
<input id="b" type="text" />
<!-- 这里用于注册不同的clientId, 多个webSocket客户端只能同步收到相同clientId的消息 -->
<input type="button" onclick="connect()" value="连接"/>
<hr />
<input type="button" onclick="closeWebSocket()" value="关闭WebSocket连接"/>
<hr />
<div id="message"></div>
</body>
</html>
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
* 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
*/
@ServerEndpoint("/websocket/{clientId}")
public class WebSocket {
// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static AtomicInteger onlineCount = new AtomicInteger(0);
// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
//若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
// private static CopyOnWriteArraySet<WebSocket> webSocketSet = new
// CopyOnWriteArraySet<WebSocket>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
//记录每个客户端的实例变量, 现在拿下面的全局map记录
//private Session session;
private static Map<String, Session> webSocketMap = new ConcurrentHashMap<String, Session>();
/**
* 连接建立成功调用的方法
*
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void onOpen(@PathParam("clientId") String clientId, Session session) {
// 用登录用户编号和sessionId的拼接来做webSocket通信的唯一标识
String key = getWebSocketMapKey(clientId, session);
webSocketMap.put(key, session);
addOnlineCount(); // 在线数加1
System.out.println("WebSocket有新连接加入!当前在线人数为" + getOnlineCount());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(@PathParam("clientId") String clientId, Session session, CloseReason closeReason) {
String key = getWebSocketMapKey(clientId, session);
webSocketMap.remove(key, session);
subOnlineCount(); // 在线数减1
System.out.println("WebSocket有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
* @param session 可选的参数
*/
@OnMessage
public void onMessage(@PathParam("clientId") String clientId, String message, Session session) {
System.out.println("WebSocket收到来自客户端的消息:" + message);
sendMessageByClientId(clientId, message);
}
/**
* 获取webSocketMap集合的Key
*
* @param clientId 用户编号
* @param session webSocket的Session
* @return
*/
private String getWebSocketMapKey(String clientId, Session session) {
if (clientId==null) {
return session.getId();
} else {
return clientId + "_" + session.getId();
}
}
/**
* 发生错误时调用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("WebSocket发生错误");
}
// 群发消息
public static void doSend(String message) {
if (webSocketMap.size() > 0) {
for (Map.Entry<String, Session> entry : webSocketMap.entrySet()) {
try {
sendMessage(entry.getValue(), message);
} catch (IOException e) {
System.out.println("WebSocket doSend is error:");
continue;
}
}
}
}
public static void sendMessage(Session session, String message) throws IOException {
session.getBasicRemote().sendText(message);
}
public static int sendMessageByClientIdList(List<String> clientIdList, String message) {
int status = 0;
for (String clientId : clientIdList) {
status = sendMessageByClientId(clientId, message);
}
return status;
}
/**
* 通过用户的编号来发送webSocket消息
*
* @param clientId
* @param message
*/
public static int sendMessageByClientId(String clientId, String message) {
int status = 0;
if (webSocketMap.size() > 0) {
for (Map.Entry<String, Session> entry : webSocketMap.entrySet()) {
try {
String key = entry.getKey();
// 判断webSocketMap中的clientId和发送的clientId是否相同
// 若相同则进行发送消息
String key1 = key.substring(0, key.lastIndexOf("_"));
if (key1.equals(clientId)) {
sendMessage(entry.getValue(), message);
status = 200;
}
} catch (IOException e) {
System.out.println("WebSocket doSend is error:");
continue;
}
}
}
return status;
}
public static void sendSpeechMessageByClientId(String clientId, String message) {
if (webSocketMap.size() > 0) {
for (Map.Entry<String, Session> entry : webSocketMap.entrySet()) {
try {
String key = entry.getKey();
// 判断webSocketMap中的clientId和发送的clientId是否相同
// 若相同则进行发送消息
String key1 = key.substring(0, key.lastIndexOf("_"));
if (key1.equals(clientId)) {
sendMessage(entry.getValue(), message);
}
} catch (IOException e) {
System.out.println("WebSocket doSend is error:");
continue;
}
}
}
}
public static synchronized AtomicInteger getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocket.onlineCount.getAndIncrement();
}
public static synchronized void subOnlineCount() {
WebSocket.onlineCount.getAndDecrement();
}
}