WebSocket是应用层上的一个应用层协议,持久化的协议,他必须依赖http协议进行一次握手,成功后就直接从tcp通道传输,后续就与http无关了。
WebSocket是以frame形式传输数据的,比如会将一条消息分为多个frame,按照先后顺序传输出去,他的好处有以下几点:
- 大数据的传输可以分片传输,不用考虑到数据大小导致的长度标志不足的请求。
- 和http的chunk一样,可以一边产生数据,一边传递数据,即提高传输效率。
1. 特点
- 长连接。
- 主要是服务端给客户端发送数据。
- WebSocket使用时浏览器与服务端最开始建立的还是http连接,之后再将协议转换为ws,在请求头上增加额外信息 upgrade:webSocket,connection:upgrade,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //是一个 Base64 encode 的值,这个是浏览器随机生成的 Sec-WebSocket-Protocol: chat, superchat //是一个用户定义的字符串,用来区分同 URL 下,不同的服务所需要的协议 Sec-WebSocket-Version: 13 //告诉服务器所使用的 WebSocket Draft (协议版本) Origin: http://example.com
- 端口与http协议一样,默认80,wss和https一样,默认443
- 减少了http长轮询造成的资源浪费,解决了不能由服务端向客户端主动推送的问题,提高了实时性。
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 建立在 TCP 协议之上,服务器端的实现比较容易。
2. 在WebSocket之前,web上实现实时数据交互的方式
- 定期轮询:客户端按照某个时间间隔不断地向服务器发送请求,请求服务端的最新数据然后更新数据到客户端显示,这种方式浪费了大量的流量,并且对服务端造成了很大的压力。
- 基于长轮询的服务端推送技术:客户端首先给服务端发送一个请求,服务端收到该请求之后如果数据没有更新则并不立刻返回,服务端会阻塞请求的放回,直到数据发生了更新或者发生了连接超时,服务端返回数据之后客户端再次发送同样的请求。
3. 服务端代码实现(tomcat)
package com.scoket.scoketTest;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
/**
* @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
* 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
*/
@ServerEndpoint("/websocket")
public class WebSocketTest {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
/**
* 连接建立成功调用的方法
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void onOpen(Session session){
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(){
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
* @param session 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("来自客户端的消息:" + message);
//群发消息
for(WebSocketTest item: webSocketSet){
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
/**
* 发生错误时调用
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error){
System.out.println("发生错误");
error.printStackTrace();
}
/**
* 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
* @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;
}
public static synchronized void addOnlineCount() {
WebSocketTest.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketTest.onlineCount--;
}
}
网友评论