之前实现websocket基于stomp的,觉得springboot封装的太高,不怎么灵活,现在实现一个纯h5的,也大概了解websocket在内部是怎么传输的。
1.环境搭建
因为在上一篇基于stomp协议实现的websocket里已经有大概介绍过web的基本情况了,所以在这篇就不多说了,我们直接进入正题吧,在springboot中,我们还是需要导入websocket的包。
在pox.xml加上对springboot对websocket的支持:
1
2
3
4
5
|
<!-- websocket --> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-websocket</artifactid> </dependency> |
这里大概说一下自己的一点小见解:客户端与服务器建立websocket连接,实际上是创建了一个socket,这个socket是共享与客户端和服务器的。两者只要往对应的socket里操作,就可以实现双方实时通讯了
2.编码实现
一、在springboot中,添加websocket的配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package com.cloud.sbjm.configure; 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 com.cloud.sbjm.security.websocketinterceptor; import com.cloud.sbjm.service.imp.myhandler; //实现接口来配置websocket请求的路径和拦截器。 @configuration @enablewebsocket public class websocketh5config implements websocketconfigurer{ @override public void registerwebsockethandlers(websockethandlerregistry registry) { //handler是websocket的核心,配置入口 registry.addhandler( new myhandler(), "/myhandler/{id}" ).setallowedorigins( "*" ).addinterceptors( new websocketinterceptor()); } } |
1.@configuration:注解标识该类为spring的配置类
2.@enablewebsocket:开启注解接收和发送消息
3.实现websocketconfigurer接口,重写registerwebsockethandlers方法,这是一个核心实现方法,配置websocket入口,允许访问的域、注册handler、定义拦截器。客户端通过“/myhandler/{id}”直接访问handler核心类,进行socket的连接、接收、发送等操作,这里由于还加了个拦截器,所以建立新的socket访问时,都先进来拦截器再进去handler类,“new websocketinterceptor()”是我实现的拦截器,“new myhandler()”是我实现的一个handler类。
二、websocketinterceptor拦截器的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package com.cloud.sbjm.security; import java.util.map; import javax.servlet.http.httpsession; 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.handshakeinterceptor; public class websocketinterceptor implements handshakeinterceptor { //进入hander之前的拦截 @override public boolean beforehandshake(serverhttprequest request, serverhttpresponse serverhttpresponse, websockethandler websockethandler, map<string, object> map) throws exception { if (request instanceof servletserverhttprequest) { string id = request.geturi().tostring().split( "id=" )[ 1 ]; system.out.println( "当前session的id=" +id); servletserverhttprequest serverhttprequest = (servletserverhttprequest) request; httpsession session = serverhttprequest.getservletrequest().getsession(); map.put( "websocket_userid" ,id); } return true ; } @override public void afterhandshake(serverhttprequest serverhttprequest, serverhttpresponse serverhttpresponse, websockethandler websockethandler, exception e) { system.out.println( "进来websocket的afterhandshake拦截器!" ); } } |
1.实现了handshakeinterceptor 接口,并实现了beforehandshake该方法,该方法是在进入handler核心类之前进行拦截。
这里主要实现的逻辑是:
截取客户端建立websocket连接时发送的url地址字符串,并通过对该字符串进行特殊标识截取操作,获取客户端发送的唯一标识(由自己定义的,一般是系统用户id唯一标识,用以标识该用户),并把它以键值对的形式放到session里,这样后期可以通过该session获取它对应的用户id了。【一个session对应着一个websocketsession】
三、myhandler核心类的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
package com.cloud.sbjm.service.imp; import java.io.ioexception; import java.util.hashmap; import java.util.map; import java.util.set; import net.sf.json.jsonobject; import org.springframework.stereotype.service; import org.springframework.web.socket.closestatus; import org.springframework.web.socket.textmessage; import org.springframework.web.socket.websockethandler; import org.springframework.web.socket.websocketmessage; import org.springframework.web.socket.websocketsession; @service public class myhandler implements websockethandler { //在线用户列表 private static final map<string, websocketsession> users; static { users = new hashmap<>(); } //新增socket @override public void afterconnectionestablished(websocketsession session) throws exception { system.out.println( "成功建立连接" ); string id = session.geturi().tostring().split( "id=" )[ 1 ]; system.out.println(id); if (id != null ) { users.put(id, session); session.sendmessage( new textmessage( "成功建立socket连接" )); system.out.println(id); system.out.println(session); } system.out.println( "当前在线人数:" +users.size()); } //接收socket信息 @override public void handlemessage(websocketsession websocketsession, websocketmessage<?> websocketmessage) throws exception { try { jsonobject jsonobject = jsonobject.fromobject(websocketmessage.getpayload()); system.out.println(jsonobject.get( "id" )); system.out.println(jsonobject.get( "message" )+ ":来自" +(string)websocketsession.getattributes().get( "websocket_userid" )+ "的消息" ); sendmessagetouser(jsonobject.get( "id" )+ "" , new textmessage( "服务器收到了,hello!" )); } catch (exception e){ e.printstacktrace(); } } /** * 发送信息给指定用户 * @param clientid * @param message * @return */ public boolean sendmessagetouser(string clientid, textmessage message) { if (users.get(clientid) == null ) return false ; websocketsession session = users.get(clientid); system.out.println( "sendmessage:" + session); if (!session.isopen()) return false ; try { session.sendmessage(message); } catch (ioexception e) { e.printstacktrace(); return false ; } return true ; } /** * 广播信息 * @param message * @return */ public boolean sendmessagetoallusers(textmessage message) { boolean allsendsuccess = true ; set<string> clientids = users.keyset(); websocketsession session = null ; for (string clientid : clientids) { try { session = users.get(clientid); if (session.isopen()) { session.sendmessage(message); } } catch (ioexception e) { e.printstacktrace(); allsendsuccess = false ; } } return allsendsuccess; } @override public void handletransporterror(websocketsession session, throwable exception) throws exception { if (session.isopen()) { session.close(); } system.out.println( "连接出错" ); users.remove(getclientid(session)); } @override public void afterconnectionclosed(websocketsession session, closestatus status) throws exception { system.out.println( "连接已关闭:" + status); users.remove(getclientid(session)); } @override public boolean supportspartialmessages() { return false ; } /** * 获取用户标识 * @param session * @return */ private integer getclientid(websocketsession session) { try { integer clientid = (integer) session.getattributes().get( "websocket_userid" ); return clientid; } catch (exception e) { return null ; } } } |
1.实现了websockethandler接口,并实现了关键的几个方法。
① afterconnectionestablished(接口提供的):建立新的socket连接后回调的方法。主要逻辑是:将成功建立连接的websocketsssion放到定义好的常量[private static final map<string, websocketsession> users;]中去。这里也截取客户端访问的url的字符串,拿到标识,以键值对的形式讲每一个websocketsession存到users里,以记录每个socket。
② handlemessage(接口提供的):接收客户端发送的socket。主要逻辑是:获取客户端发送的信息。这里之所以可以获取本次socket的id,是因为客户端在第一次进行连接时,拦截器进行拦截后,设置好id,这样也说明,双方在相互通讯的时候,只是对第一次建立好的socket持续进行操作。
③ sendmessagetouser(自己定义的):发送给指定用户信息。主要逻辑是:根据用户id从常量users(记录每一个socket)中,获取socket,往该socket里发送消息,只要客户端还在线,就能收到该消息。
④sendmessagetoallusers (自己定义的):这个广播消息,发送信息给所有socket。主要逻辑是:跟③类型,只不过是遍历整个users获取每一个socket,给每一个socket发送消息即可完广播发送
⑤handletransporterror(接口提供的):连接出错时,回调的方法。主要逻辑是:一旦有连接出错的socket,就从users里进行移除,有提供该socket的参数,可直接获取id,进行移除。这个在客户端没有正常关闭连接时,会进来,所以在开发客户端时,记得关闭连接
⑥afterconnectionclosed(接口提供的):连接关闭时,回调的方法。主要逻辑:一旦客户端/服务器主动关闭连接时,将个socket从users里移除,有提供该socket的参数,可直接获取id,进行移除。
后台的开发就开发完了,大家有没有发现比基于stomp协议实现要灵活得多?
四、客户端页面的实现【基于h5】
不需要加入任何的js包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
<!doctype html> <html> <head> <title>socket.html</title> <meta name= "keywords" content= "keyword1,keyword2,keyword3" > <meta name= "description" content= "this is my page" > <meta name= "content-type" content= "text/html" charset= "utf-8" > <!--<link rel= "stylesheet" type= "text/css" href= "./styles.css" rel= "external nofollow" >--> </head> <body> welcome<br/> <input id= "text" type= "text" /><button onclick= "send()" >send</button> <button onclick= "closewebsocket()" >close</button> <div id= "message" > </div> <!-- 公共js --> <script type= "text/javascript" src= "../websocket/jquery.min.js" ></script> <script type= "text/javascript" > var userid= "888" ; var websocket= null ; $(function() { //创建websocket connectwebsocket(); }) //强制关闭浏览器 调用websocket.close(),进行正常关闭 window.onunload = function() { //关闭连接 closewebsocket(); } //建立websocket连接 function connectwebsocket(){ console.log( "开始..." ); //建立websocket连接 websocket = new websocket( "ws://127.0.0.1:9091/cloud-sbjm/myhandler/id=" +userid); //打开websokcet连接时,回调该函数 websocket.onopen = function () { console.log( "onpen" ); } //关闭websocket连接时,回调该函数 websocket.onclose = function () { //关闭连接 console.log( "onclose" ); } //接收信息 websocket.onmessage = function (msg) { console.log(msg.data); } } //发送消息 function send(){ var postvalue={}; postvalue.id=userid; postvalue.message=$( "#text" ).val(); websocket.send(json.stringify(postvalue)); } //关闭连接 function closewebsocket(){ if (websocket != null ) { websocket.close(); } } </script> </body> </html> |
页面比较简单,简单解释一下:
1.new websocket("ws://127.0.0.1:9091/cloud-sbjm/myhandler/id="+userid),与服务器建立websocket连接,后面的id="+userid,是动态参数,跟服务器配置handler的访问地址时对应"/myhandler/{id}"。
2.h5也提供多个回调函数
onopen:打开websokcet连接时,回调该函数
onclose:关闭websocket连接时,回调该函数
onmessage:服务器给该socket发送消息时,回调该函数,获取消息
websocket.send(json.stringify(postvalue));:给socket发送消息,服务器获取
websocket.close();客户端主要关闭连接,会触发客户端的onclose方法和服务器的afterconnectionclosed方法
到此服务端的开发也完成了,下面执行一下程序效果图:
一、建立连接
客户端:
服务器:
二、发送消息
客户端:
服务器:
三、服务器主动推送消息
服务器代码:
到此已经完成了,各位可以根据自己需求进行修改,这会灵活多了!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/Ouyzc/article/details/79994401