赞
踩
webSocket是什么:
1、WebSocket是一种在单个TCP连接上进行全双工通信的协议
2、WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据
3、在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
4、WebSocket 是一种标准协议,用于在客户端和服务端之间进行双向数据传输。但它跟 HTTP 没什么关系,它是一种基于 TCP 的一种独立实现。
以前客户端想知道服务端的处理进度,要不停地使用 Ajax 进行轮询,让浏览器隔个几秒就向服务器发一次请求,这对服务器压力较高。另外一种轮询就是采用 long poll 的方式,这就跟打电话差不多,没收到消息就一直不挂电话,也就是说,客户端发起连接后,如果没消息,就一直不返回 Response 给客户端,连接阶段一直是阻塞的。
而 WebSocket 解决了 HTTP 的这几个难题。首先,当服务器完成协议升级后( HTTP -> WebSocket ),服务端可以主动推送信息给客户端,解决了轮询造成的同步延迟问题。由于 WebSocket 只需要一次 HTTP 握手,服务端就能一直与客户端保持通讯,直到关闭连接,这样就解决了服务器需要反复解析 HTTP 协议,减少了资源的开销。
WebSocket协议支持(在受控环境中运行不受信任的代码的)客户端与(选择加入该代码的通信的)远程主机之间进行全双工通信。用于此的安全模型是Web浏览器常用的基于原始的安全模式。 协议包括一个开放的握手以及随后的TCP层上的消息帧。 该技术的目标是为基于浏览器的、需要和服务器进行双向通信的(服务器不能依赖于打开多个HTTP连接(例如,使用XMLHttpRequest或和长轮询))应用程序提供一种通信机制。
websocket 是一个基于应用层的网络协议,建立在tcp 协议之上,和 http 协议可以说是兄弟的关系,但是这个兄弟有点依赖 http ,为什么这么说呢?我们都知道 HTTP 实现了三次握手来建立通信连接,实际上 websocket 的创始人很聪明,他不想重复的去造轮子,反正我兄弟已经实现了握手了,我干嘛还要重写一套呢?先让它去冲锋陷阵呢,我坐收渔翁之利不是更香 吗,所以一般来说,我们会先用 HTTP 先进行三次握手,再向服务器请求升级为websocket 协议,这就好比说,嘿兄弟你先去给我排个队占个坑位建个小房子,到时候我在把这房子改造成摩天大楼。而且一般来说 80 和 443 端口一般 web 服务端都会外放出去,这样可以有效的避免防火墙的限制。当然,你创建的 websocket 服务端进程的端口也需要外放出去。
很多人会想问,web开发 使用 HTTP 协议不是已经差不多够用了吗?为什么还要我再多学一种呢?这不是搞事情嘛,仔细想想,一门新技术的产生必然有原因的,如果没有需求,我们干嘛那么蛋疼去写那么多东西,就是因为 HTTP 这个协议有些业务需求支持太过于鸡肋了,从 HTTP 0.9 到现在的 HTTP3.0 ,HTTP协议可以说说是在普通的web开发领域已经是十分完善且高效的了,说这个协议养活了全球半数的公司也不为过吧,像 2.0 服务器推送技术,3.0 采用了 UDP 而放弃了原来的 TCP ,这些改动都是为了进一步提升协议的性能,然而大家现在还是基本使用的 HTTP 1.1 这个最为经典的协议, 也是让开发者挺尴尬的。
绝大多数的web开发都是应用层开发者,大多数都是基于已有的应用层去开发应用,可以说我们最熟悉、日常打交道最多的就是应用层协议了,底下 TCP/IP 协议我们基本很少会去处理,当然大厂可能就不一样了,自己弄一套协议也是正常的,这大概也是程序员和码农的区别吧,搬砖还是创新,差别还是很大的。网络这种分层协议的好处我在之前的文章也说过了,这种隔离性很方便就可以让我们基于原来的基础去拓展,具有较好的兼容性。
总的来说,它就是一种依赖HTTP协议的,支持全双工通信的一种应用层网络协议。
简单的说,WebSocket协议之前,双工通信是通过多个http链接来实现,这导致了效率低下。WebSocket解决了这个问题。下面是标准RFC6455中的产生背景概述。
长久以来, 创建实现客户端和用户端之间双工通讯的web app都会造成HTTP轮询的滥用: 客户端向主机不断发送不同的HTTP呼叫来进行询问。
这会导致一系列的问题:
一个更简单的解决方案是使用单个TCP连接双向通信。 这就是WebSocket协议所提供的功能。 结合WebSocket API ,WebSocket协议提供了一个用来替代HTTP轮询实现网页到远程主机的双向通信的方法。
WebSocket协议被设计来取代用HTTP作为传输层的双向通讯技术,这些技术只能牺牲效率和可依赖性其中一方来提高另一方,因为HTTP最初的目的不是为了双向通讯。
在实现websocket连线过程中,需要通过浏览器发出websocket连线请求,然后服务器发出回应,这个过程通常称为“握手” 。在 WebSocket API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket 协议中,为我们实现即时服务带来了两大好处:
1. Header:互相沟通的Header是很小的-大概只有 2 Bytes。
2. Server Push:服务器的推送,服务器不再被动的接收到浏览器的请求之后才返回数据,而是在有新数据时就主动推送给浏览器。
普遍认为,WebSocket的优点有如下几点:
1)较少的控制开销:在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小;
2)更强的实时性:由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少;
3)保持连接状态:与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息;
4)更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容;
5)可以支持扩展:WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。
由于 WebSocket 拥有上述的优点,所以它被广泛地应用在即时通讯/IM、实时音视频、在线教育和游戏等领域。
对于前端开发者来说,要想使用 WebSocket 提供的强大能力,就必须先掌握 WebSocket API,下面带大家一起来认识一下 WebSocket API。
简单例子使用:
环境配置
开发环境配置为:
JDK 8
IntelliJ IDEA 2020.1.2 x64
服务端实现
本章节介绍服务端的实现。
1、引入Jar包
首先,在IDEA中创建一个Maven项目,在pom.xml文件中引入所需要的Jar包,如下所示:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
一个是springboot的starter,另一个是springboot内部集成的websocket。
2、Springboot集成的Websocket
使用Springboot集成的websocket需要使用到以下几个类/接口:
WebSocketHandler:WebSocket消息以及生命周期事件的处理器。
WebSocketConfigurer:对WebSocket进行配置,包括配置拦截器、配置接口地址。
HttpSessionHandshakeInterceptor:拦截器,可以对Websocket通信过程中的请求进行拦截处理。
除了上述三个官方提供的类/接口之外,我们还需要实现一个WebSocket的包装类,用于为每一个WebSocket实例添加额外的信息,比如客户端的ID。在Spring内,WebSocket实例以WebSocketSession形式存在,每个session都代表了一个服务端与客户端的会话。
2.1、创建WebSocket包装类
import org.springframework.web.socket.WebSocketSession;
public class WebSocketBean {
private WebSocketSession webSocketSession;
private int clientId;
public int getClientId() {
return clientId;
}
public void setClientId(int clientId) {
this.clientId = clientId;
}
public WebSocketSession getWebSocketSession() {
return webSocketSession;
}
public void setWebSocketSession(WebSocketSession webSocketSession) {
this.webSocketSession = webSocketSession;
}
}
这里的包装类很简单,仅添加了一个客户端ID的属性,这里仅作为简单的示例。
2.2、实现WebSocketHandler接口
WebSocketHandler接口用于处理WebSocket的消息,Spring提供了一个抽象类AbstractWebSocketHandler实现了WebSocketHandler接口,因此我们可以直接继承抽象类,重写需要实现的方法即可。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class MyWebsocketHandler extends AbstractWebSocketHandler {
private final Logger logger = LoggerFactory.getLogger(getClass());
private static final Map<String, WebSocketBean> webSocketBeanMap;
private static final AtomicInteger clientIdMaker; //仅用用于标识客户端编号
static {
webSocketBeanMap = new ConcurrentHashMap<>();
clientIdMaker = new AtomicInteger(0);
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
//当WebSocket连接正式建立后,将该Session加入到Map中进行管理
WebSocketBean webSocketBean = new WebSocketBean();
webSocketBean.setWebSocketSession(session);
webSocketBean.setClientId(clientIdMaker.getAndIncrement());
webSocketBeanMap.put(session.getId(), webSocketBean);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
//当连接关闭后,从Map中移除session实例
webSocketBeanMap.remove(session.getId());
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
//传输过程中出现了错误
if (session.isOpen()){
session.close();
}
webSocketBeanMap.remove(session.getId());
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
//处理接收到的消息
logger.info("Received message from client[ID:" + webSocketBeanMap.get(session.getId()).getClientId() +
"]; Content is [" + message.getPayload() + "].");
TextMessage textMessage = new TextMessage("Server has received your message.");
session.sendMessage(textMessage);
}
}
实现的逻辑很简单,在每个方法都有了注释。值得注意的是,这里在handleTextMessage处理接收到的消息,表示处理接收到的字符串消息。除此之外,AbstractWebSocketHandler还提供了handleBinaryMessage以及handlePongMessage,前者表示处理二进制消息,而后者表示处理心跳数据包的信息。
2.3、实现WebSocket拦截器
WebSocket拦截器可以在Websocket连接建立之前的权限校验等功能,Spring提供了HttpSessionHandshakeInterceptor这个接口作为拦截器。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import java.util.Map;
public class MyWebSocketInterceptor extends HttpSessionHandshakeInterceptor {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
logger.info("[MyWebSocketInterceptor#BeforeHandshake] Request from " + request.getRemoteAddress().getHostString());
if (request instanceof ServletServerHttpRequest){
ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request;
String token = serverHttpRequest.getServletRequest().getHeader("token");
//这里做一个简单的鉴权,只有符合条件的鉴权才能握手成功
if ("token-123456".equals(token)){
return super.beforeHandshake(request, response, wsHandler, attributes);
}else {
return false;
}
}
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
logger.info("[MyWebSocketInterceptor#afterHandshake] Request from " + request.getRemoteAddress().getHostString());
}
}
从代码可以看出,笔者在握手之前对请求进行了一个简单的校验,符合条件的请求才会进行下一步的握手。
2.4、对WebSocket进行配置
该步骤实现Springboot对WebSocket的支持,包括了配置接口地址、配置拦截器等。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {
@Bean
public MyWebSocketInterceptor webSocketInterceptor(){return new MyWebSocketInterceptor();}
@Bean
public ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry.addHandler(new MyWebsocketHandler(), "/websocket").addInterceptors(webSocketInterceptor());
}
}
从上面的代码看出,笔者将websocket的接口地址放在了“/websocket”上,当客户端访问“/websocket”时,就会尝试与服务端进行握手连接。
2.5、配置Application主类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
@ServletComponentScan
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class);
}
}
客户端实现
本小节介绍WebSocket的客户端实现,这里使用Java所提供的WebSocket库来实现。在IDEA中新建一个工程:WebsocketDemoClient,用来保存我们的客户端代码。在pol.xml文件中引入:
<dependencies>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
<scope>compile</scope>
</dependency>
</dependencies>
1、创建Websocket client
WebsocketClient提供了连接到服务端的WebSocket的一切必要准备,我们只需要继承该类,重写所需要的方法来实现我们的业务逻辑,就能方便地使用WebSocket了。
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
public class MyWebSocketClient extends WebSocketClient {
private Logger logger = LoggerFactory.getLogger(getClass());
public MyWebSocketClient(URI serverUri) {
super(serverUri);
}
@Override
public void onOpen(ServerHandshake serverHandshake) {
logger.info("[MyWebSocketClient#onOpen]The WebSocket connection is open.");
}
@Override
public void onMessage(String s) {
logger.info("[MyWebSocketClient#onMessage]The client has received the message from server." +
"The Content is [" + s + "]");
}
@Override
public void onClose(int i, String s, boolean b) {
logger.info("[MyWebSocketClient#onClose]The WebSocket connection is close.");
}
@Override
public void onError(Exception e) {
logger.info("[MyWebSocketClient#onError]The WebSocket connection is error.");
}
}
从上述代码可以看出,实现逻辑很简单,这里主要是打印出各个方法执行时的情况。
2、使用WebSocketClient
下面创建Main函数来使用我们的WebSocketClient客户端,同时创建一个定时任务,使得客户端可以连续不间断地给服务端发送消息,代码如下所示:
import java.net.URI;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
private static final AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) {
URI uri = URI.create("ws://127.0.0.1:8080/websocket"); //注意协议号为ws
MyWebSocketClient client = new MyWebSocketClient(uri);
client.addHeader("token", "token-123456"); //这里为header添加了token,实现简单的校验
try {
client.connectBlocking(); //在连接成功之前会一直阻塞
Timer timer = new Timer();
MyTimerTask timerTask = new MyTimerTask(client);
timer.schedule(timerTask, 1000, 2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class MyTimerTask extends TimerTask{
private final MyWebSocketClient client;
public MyTimerTask(MyWebSocketClient client){
this.client = client;
}
@Override
public void run() {
client.send("Test message from client, the number is " + count.getAndIncrement());
}
}
}
运行结果
服务端和客户端的代码编写完毕后,我们来看一下结果。先运行服务端代码,将服务端应用程序部署于8080端口中,然后运行客户端代码,结果分别如下图所示:
运行结果正常!客户端与服务端建立了WebSocket链路,并实时发送消息到服务端中。
感谢大家的阅读,觉得有所帮助的朋友点点赞。
代码已上传csdn,0积分下载,觉得这片博文有用请留下你的点赞,有问题的朋友可以一起交流讨论。
基于springboot实现websocket客户端和服务端
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。