1. 什么是WebSocket
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
2 使用websocket实现网络通信
2.1 客户端开发
var Socket = new WebSocket(url, [protocol] );
第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。
WebSocket对象提供了一些事件函数,用于监听WebSocket的不同状态。
事件函数 | 描述 |
---|---|
onopen | 建立连接时候触发 |
onmessage | 客户端接收到服务端数据时触发 |
onerror | 通信发生错误时触发 |
onclose | 连接关闭时触发 |
除此以外,WebSocket还提供了下面两个常用函数:
函数名 | 描述 |
---|---|
send | 向服务端发送数据 |
close | 关闭连接 |
WebSocket本质上是一个TCP协议,为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。
下面是WebSocket客户端的示例代码:
<%@ 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=UTF-8">
<title>Insert title here</title>
</head>
<body>
<input type="text" id="content">
<input type="button" value="send" onclick="send()"/>
<script>
// 创建WebSocket对象
var ws = new WebSocket("ws://localhost:8080/websocket2");
// 建立连接自动调用该方法
ws.onopen = function() {
console.log("建立连接成功!");
};
// 接收到消息时候自动调用该方法
ws.onmessage = function(evt) {
console.log("接收来自服务器的通知...");
alert(evt.data);
}
// 关闭连接时自动调用该方法
ws.onclose = function() {
console.log("连接已经关闭!");
}
//连接发生错误的回调方法
ws.onerror = function () {
setMessageInnerHTML("error");
};
// 发送按钮事件
function send() {
var content = document.getElementById("content").value;
ws.send(content);
}
</script>
</body>
</html>
2.2 服务端开发
目前支持WebSocket的服务器包括:tomcat、weblogic、node.js、nginx等等。下面以tomcat为例开发websocket服务端程序。
从.Tomcat从7.0.47开始支持JSR356规范,该规范对不同容器(如tomcat、jetty等)使用WebSocket作出统一的规范要求。
JSR356实现WebSocket有两种方式,一种是使用注解的,另一种是继承javax.websocket.Endpoint类。下面以注解为例介绍如何开发WebSocket的服务端。
第一步:创建一个Maven项目,并加入相关websocket相关依赖。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.chinasofti</groupId>
<artifactId>websockettest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<!-- 配置Tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
第二步:新建一个Endpoint类,并使用过@ServerEndpoint注解进行标注。
@ServerEndpoint("/websocket")
public class EchoEndpoint2 {}
上面括号中指定了WebSocket服务的名字。如果客户端需要连接该服务,那么就需要通过该名字来访问。
第三步:根据WebSocket不同状态提供相应的处理方法;
@ServerEndpoint("/websocket")
public class EchoEndpoint {
private Session session;
// 建立连接时自动调用
@OnOpen
public void open(final Session session) throws IOException {
System.out.println("建立连接!");
this.session = session;
}
// 接收消息时自动调用
@OnMessage
public void inMessage(String message) throws IOException {
System.out.println("websocket读取到的数据:" + message);
// 发送消息给客户端
session.getBasicRemote().sendText("hello " + message);
}
// 关闭连接时自动调用
@OnClose
public void end() {
System.out.println("websocket关闭了!");
}
}
3. WebSocket和Socket的区别
就像Java和JavaScript,并没有什么太大的关系。而WebSocket的名字虽然与Socket很相似,但是它们是两个完全不同的东西。
从网络结构上看,Socket 是属于传输控制层的协议,而WebSocket 则是一个典型的应用层协议。
从使用上看,Socket是半双工通讯技术,即数据能双向传送但不能同时双向传送;而WebSocket是全双向通讯技术,即数据能够同时双向传送。
4. 案例:Web聊天室
实现思路:
1)客户端连接服务端后,服务端需要把每一个客户端的session保存到一个集合中;
2)当客户端向服务端发送消息后,服务端会先把消息保存到集合里面,然后服务端就会把该集合的所有消息读取出来并转换成字符串后,一次性地发送给每一个客户端;
3)客户端接收到服务端推送的消息后,把消息显示出来;
4.1 客户端开发
<%@ 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=UTF-8">
<title>Insert title here</title>
</head>
<body>
<textarea id="chatarea" rows="30" cols="200"></textarea><br/>
昵称:<input type="text" id="name" size="10" value=""><br/>
消息内容:<input type="text" id="content" size="100">
<input type="button" value="send" onclick="send()"/>
<script>
// 创建WebSocket对象
var ws = new WebSocket("ws://localhost:8080/websocket2");
// 建立连接自动调用该方法
ws.onopen = function() {
console.log("建立连接成功!");
};
// 接收到消息时候自动调用该方法
ws.onmessage = function(evt) {
console.log("接收来自服务器的通知...");
document.getElementById("chatarea").value = evt.data;
}
// 关闭连接时自动调用该方法
ws.onclose = function() {
console.log("连接已经关闭!");
}
//连接发生错误的回调方法
ws.onerror = function () {
setMessageInnerHTML("error");
};
// 发送按钮事件
function send() {
var name = document.getElementById("name").value;
var content = document.getElementById("content").value;
ws.send(name + ": " + content);
document.getElementById("content").value = "";
document.getElementById("content").focus();
}
</script>
</body>
</html>
4.2 服务端开发
@ServerEndpoint("/websocket")
public class EchoEndpoint {
// 该集合用于保存所有客户端的session
static HashSet<Session> sessions = new HashSet<Session>();
// 该集合保存所有的历史消息
static LinkedList<String> messages = new LinkedList<String>();
private Session session;
@OnOpen
public void open(final Session session) throws IOException {
System.out.println("建立连接!");
this.session = session;
sessions.add(session);
}
@OnMessage
public void inMessage(String message) throws IOException {
System.out.println("websocket读取到的数据:" + message);
messages.add(message);
StringBuilder sb = new StringBuilder();
for (String msg : messages) {
sb.append(msg + "\r\n");
}
for (Session session : sessions) {
session.getBasicRemote().sendText(sb.toString());
}
}
@OnClose
public void end() {
System.out.println("websocket关闭了!");
sessions.remove(this.session);
}
}