一、通讯方式
1.单工通讯
消息只能单方向传输的工作方式。
2.半双工通讯
可以实现双向的通信,但不能双向同时进行传输,必须轮流交替地进行传输。
3.双工通讯
同一时刻可以进行双向传输。
二、实现web实时通讯有很多,如下:
1.常轮询
由浏览器发送一个请求,服务端接收请求后,再没有新数据时先将请求阻塞,直到有新数据或者超时时才返回
2.短轮询
由浏览器发送一个请求,服务端无论是否有新数据都及时响应,http连接结束;(可以通过自定义返回数据标识或者判断数据是否为空来判断这次请求是否有新数据返回)
3.webSocket协议
是一种网络通信协议,是真正的双向平等对话,属于服务器推送技术的一种
4.长连接
服务器端发送完新数据也不断开连接,继续等待下一份新数据,直到超时
5.心跳机制
客户端每个一段时间向服务端发送一个心跳消息,服务器接收心跳消息后恢复同样的心跳消息,直到超出心跳时长还没有响应则认为断开
三、通信技术之websocke实现
1.服务端:
需要导入的依赖:
< dependency >
< groupId > javax.websocket </ groupId >
< artifactId > javax.websocket-api </ artifactId >
< version > 1.1 </ version >
</ dependency >
< dependency >
< groupId > org.springframework </ groupId >
< artifactId > spring-websocket </ artifactId >
< version > 4.0.5.RELEASE </ version >
</ dependency >
服务端具体实现:
import java.io.IOException;
import java.util.Hashtable;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Controller;
import org.springframework.web.socket.server.standard.SpringConfigurator;
// 该注解用来指定一个URI,客户端可以通过这个URI来连接到WebSocket。
/**
类似Servlet的注解mapping。无需在web.xml中配置。
* configurator = SpringConfigurator.class是为了使该类可以通过Spring注入。
*/
@Controller
@ServerEndpoint(value = "/websocket.do/{userId}",configurator = SpringConfigurator.class)
public class WebsocketController {
// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
private static int index = 0;
public WebsocketController(){}
// Hashtable是线程安全的。
private static Hashtable<WebsocketController, Long> webSocketMap = new Hashtable<WebsocketController, Long>();
// 与客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
/**
* 连接建立成功调用的方法
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void onOpen(@PathParam("userId") Long userId,Session session)
{
System.out.println("userId:" + userId);
this.session = session;
webSocketMap.put(this, userId);
addOnlineCount();
System.out.println("有新连接加入!当前在线人数为:" + getOnlineCount());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose()
{
webSocketMap.remove(this);
subOnlineCount();
System.out.println("当前连接已退出!当前在线人数为:" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
* @param session 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session)
{
System.out.println("来自客户端的消息:" + message);
// 这里就是随便模拟的,请不要在意,实际可以通过userId获得用户名称
for(WebsocketController soc : webSocketMap.keySet())
{
try {
if(index % 2 == 0)
{
soc.sendMessage("小强" + ":" + message);
}
else
{
soc.sendMessage("璐璐" + ":" + message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
index ++;
}
/**
* 发生错误时调用
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error)
{
System.out.println("出错了!!!!");
}
/**
* 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException
{
this.session.getBasicRemote().sendText(message);
//this.session.getAsyncRemote().sendText(message);
}
// 获取连接数量,保持同步
public static synchronized int getOnlineCount()
{
return onlineCount;
}
// 连接数量加1,保持同步
public static synchronized void addOnlineCount()
{
WebsocketController.onlineCount++;
}
// 连接数量减1,保持同步
public static synchronized void subOnlineCount()
{
WebsocketController.onlineCount--;
}
}
前端页面
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>聊天室</title>
</head>
<body >
<input id="text" type="text"/>
<button onclick="send()">发送</button>
<button onclick="closeWebSocket()">关闭连接</button>
<div id="message">
</div>
在线人数<span id="onlineCount">1</span>人
</body>
<script type="text/javascript" src="http://localhost:8080/leasplat/newFile/js/jquery-3.0.0.js"></script>
<!-- <script type="text/javascript" src="http://localhost:8080/leasplat/newFile/js/websocket.js"></script> -->
<script type="text/javascript">
var websocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/leasplat/websocket.do/1");
}
else {
alert("对不起!你的浏览器不支持webSocket")
}
//连接发生错误的回调方法
websocket.onerror = function () {
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function (event) {
setMessageInnerHTML("加入连接");
};
//接收到消息的回调方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
};
//连接关闭的回调方法
websocket.onclose = function () {
setMessageInnerHTML("断开连接");
};
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,
// 防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
var is = confirm("确定关闭窗口?");
if (is){
websocket.close();
}
};
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
// $("#msg").append(innerHTML+"<br/>")
console.log(innerHTML);
};
//关闭连接
function closeWebSocket() {
websocket.close();
}
//发送消息
function send() {
var message = $("#text").val() ;
websocket.send(message);
$("#text").val("") ;
}
</script>
</html>
四、服务端信息推送技术sse
全称:server-sent-event
后端代码:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.Random;
/**
* Created by shiqiang on 2018/2/9.
*/
@Controller
public class SSEController {
/**
* 消息推送sse
*
* 这里不能进行return 否则后使得连接关闭,sse会默认重连,就陷入了连接、关闭的死循环了
*
* evensource.js 不支持sse当浏览器可以引入这个js进行支持
*/
@RequestMapping(value = "/push", produces = "text/event-stream")
public void push(HttpServletResponse response) throws Exception{
response.setContentType("text/event-stream");
response.setCharacterEncoding("utf-8");
while (true) {
Random r = new Random();
try {
Thread.sleep(1000);
PrintWriter pw = response.getWriter();
pw.write("data:Testing 1,2,3" + r.nextInt() + "\n\n");
pw.flush();
// 当浏览器页面关闭,检测pw flush断开后刷新异常,return进行断开sse
if (pw.checkError()) {
System.out.println("客户端断开连接");
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
前度页面代码块:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
</body>
<!-- 我是在csdn网站上提取的一个jquery连接地址,大家可以换成你们下载的本地的jquery -->
<script src="http://c.csdnimg.cn/public/common/libs/jquery/jquery-1.9.1.min.js" type="text/javascript"></script>
<script type="text/javascript">
//只有新式的浏览器才有,EventSource是SSE的客户端
if (!!window.EventSource) {
var source = new EventSource('../../push');
s = '';
source.addEventListener('open', function(e) {
console.log("连接打开");
}, false);
//添加SSE监听,获得服务器推送的消息
source.addEventListener('message', function(e) {
console.log(e);
s += e.data + "<br/>";
$('#msgFromPush').html(s);
}, false);
// 上次回调事件
source.addEventListener("connecttime", function(event) {
console.log("Connection was last established at: " + event.data);
}, false);
// 发生错误时
source.addEventListener('error', function(e) {
if (e.readyState == EventSource.CLOSED) {
console.log('连接关闭');
} else {
// 关闭sse
source.close();
console.log(e.target.readyState);
}
},false);
} else {
console.log('你的浏览器不支持SSE');
}
/**
* 上面的方法也可以写成 : source.onMessage = function(e){console.log(e)}
* 上面的几个方法都可以以类似的方法写
*/
</script>
</html>
更详细的讲解可以参考:
SSE:
http://javascript.ruanyifeng.com/htmlapi/eventsource.html
webSocket:
http://www.ruanyifeng.com/blog/2017/05/websocket.html