websocket是一种通信协议,建立在 TCP 协议之上,也依赖于http协议。
什么情况下会用websocket 呢?
我们经常用的http协议是一种单工协议,就是只能从client发出请求然后server响应请求,单方面的请求。有这样一种场景就是当server端的数据更新后要实时的推送给client,此时http协议能做的就是通过ajax定时去轮询的请求server,显然这种方式是不合理的。这时就需要websocket出马,websocket的特点就是服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是双向平等对话,协议标识符是ws(相当于http)
(如果加密,则为wss(相当于https)
)
websocket简单使用
在tomcat 中为我们提供了学习websocket 的案例,我们需要下载一个tomcat然后配置tomcat环境(此处省略配置)。我们配置好tomcat后,在他的examples中可以找到,如下图所示,这里面就是提供的案例
我们把tomcat启动起来,在浏览器中输入http://localhost:8080/examples/websocket/index.xhtml,就会看到提供的四个例子,首先点击进去第一个例子。
点击进去以后,我们按f12 打开监控台,在输入框中输入 ws://localhost:8080/examples/websocket/echoAnnotation 服务器的连接地址,然后点击connect 按钮,在右边会打印出 opened,同时在监控台中会看到捕捉到了一个http 的GET请求,请求地址就是我们输入的地址,并且在请求头中会携带一些特殊的请求头,如图中。然后你在点击Echo message 按钮,右边框会有消息打印出,但是监控台是没有变化的,这就是简单案例
到这里,我们说下websocket 的通信分为,握手建立连接通道,连接建立消息通信,通道关闭三个步骤。上图中的3 就是建立连接通道的的过程,通过http协议建立连接,使用GET方式并在请求头中添加了通信凭证等消息。连接建立以后,就相当于client和server中间就有了一条管道,消息通过管道进行双向传输,这条管道通过client和server的心跳消息维持,直到服务器关闭或者页面关闭,通道一直存在,所以在4发送消息的过程中没有捕捉到新的请求
websocket服务端开发
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
配置类
@Configuration
public class WebSocketConfig {
/**
* 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
Endpoint编写
//使用ServerEndpoint注解 标识一个websocket的终端,就相当controller里面的访问路径
//{id}相当于传递的参数
@ServerEndpoint("/socket/{id}")
@Component
@Slf4j
public class WebSocketEndpoint {
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(Session session,@PathParam(value = "id") String id) {
log.info(this.hashCode() +":onOpen session="+session.getId() +" 参数id="+id);
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(@PathParam(value = "id") String id) {
log.info("onClose 参数id="+id);
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session,@PathParam(value = "id") String id) {
log.info(this.hashCode() +":onMessage session="+session.getId()+" message ="+message+" 参数id="+id);
}
/**
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("onError");
}
}
启动类
@RestController
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
把服务端启动起来,我们还使用刚才那个前端,我们同时打开两个页面
我们分别点解A 和 B 的connect按钮,查看服务端的打印结果
两次连接的WebSocketEndpoint的hashCode 不一样,说明对于每次连接都会创建一个WebSocketEndpoint对象来处理,而且session也不一样,说明不是一个会话,即使在同一个浏览器。
接下来我们多点击几次发送消息的按钮,看看情况,打印结果
在发送消息的时候对象的hashCode 不会变,session不会变
当把刚才的两个客户端关闭时,服务端打印出
到此我们大概对websocket的服务端编写有了一个流程上的认识
应用编程
根据上面的实现,我们大致了解要实现一个websocket应用程序,我们大体有几个步骤
- 建立连接
- 客户端给服务端发送数据(或者服务端给客户端发送数据)
- 服务端接收(或者客户端接收数据)
- 当关闭的时候断开连接
- 监听事件OnOpen,OnMessage,OnClose,OnError
客户端实现,客户端我使用vue ,在前端的方法里面我都把event 打印了一下,大家可以打开浏览器监控台看看event里面都有什么东西,这里就不说了
<template>
<div>
连接地址: <input type="text" id="conn" placeholder="ws://tmp.server.com:port/socket" v-model="connAddress"/>
<button v-on:click="click">连接</button>
<div>
<textarea v-model="msg"></textarea>
<button v-on:click="webSocketSend">发送消息</button>
</div>
接收到的消息:
<div>
<textarea v-model="receive"></textarea>
</div>
</div>
</template>
<script>
export default {
name: 'websocket',
data() {
return {
connAddress: 'ws://localhost:8081/socket',
msg: '',
webSocket: null,
receive: ''
}
},
created() {
// this.initWebSocket();
},
destroyed() {
this.webSocket.close() //离开路由之后断开websocket连接
},
methods: {
click() {
this.initWebSocket();
},
initWebSocket(){
this.webSocket = new WebSocket(this.connAddress);
this.webSocket.onopen =this. webSocketOnOpen();
this.webSocket.onmessage = this.webSocketOnMessage;
this.webSocket.onopen = this.webSocketOnOpen;
this.webSocket.onerror = this.webSocketOnError;
this.webSocket.onclose = this.webSocketClose;
},
webSocketOnOpen(event){ //连接建立之后执行send方法发送数据
console.log('连接建立',event);
},
webSocketOnError(event){//连接建立失败重连
console.log('失败',event);
this.initWebSocket();
},
webSocketOnMessage(event){ //数据接收
console.log('数据接收',event);
this.receive = event.data;
},
webSocketClose(event){ //关闭
console.log('断开连接',event);
},
webSocketSend(){//数据发送
console.log(this.msg)
this.webSocket.send(this.msg);
},
}
}
</script>
<style scoped>
#conn {
width: 30%;
}
textarea {
width: 30%;
height: 150px;
}
</style>
服务端 ,这里我把构造方法加上了,当每次建立连接的时候构造方法都会执行,更说明每次都会建立新的服务端对象
@ServerEndpoint("/socket")
@Component
@Slf4j
public class WebSocketEndpoint {
public WebSocketEndpoint(){
log.info("构造方法执行:"+this.hashCode());
}
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(Session session ) {
log.info( " onOpen session="+session.getId() );
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(Session session ) {
log.info(session.getId()+":关闭了");
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onTextMessage( Session session,String message) {
log.info("onMessage session="+session.getId()+" message ="+message );
session.getAsyncRemote().sendText("我是服务端:"+message);
}
/**
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("onError");
}
}
下一篇 websocket由浅入深二 我们写一个稍微复杂一点的例子,websocket文件流的传输