websocket由浅入深

    技术2022-07-20  94

    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文件流的传输

     

     

     

     

    Processed: 0.014, SQL: 9