搜文章
推荐 原创 视频 Java开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发
Lambda在线 > It编程技术交流 > Java WebSocket教程

Java WebSocket教程

It编程技术交流 2020-07-31

WebSocket 是web客户端和服务器之间新的通讯方式, 依然架构在HTTP协议之上。使用WebSocket连接, web应用程序可以执行实时的交互, 而不是以前的poll方式。

WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,可以用来创建快速的更大规模的健壮的高性能实时的web应用程序。WebSocket通信协议于2011年被IETF定为标准RFC 6455,WebSocketAPI被W3C定为标准。
在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

什么是WebSocket?

WebSocket协议利用HTTP 升级头信息来把一个HTTP连接升级为一个WebSocket连接。HTML5 WebSockets 解决了许多导致HTTP不适合于实时应用的问题,并且它通过避免复杂的工作方式使得应用结构很简单。

最新的浏览器都支持WebSockets,

WebSocket是如何工作的?

每一个WebSocket连接的生命都是从一个HTTP请求开始的。HTTP请求跟其他请求很类似,除了它拥有一个Upgrade头信息。Upgrade头信息表示一个客户端希望把连接升级为不同的协议。对WebSockets来说,它希望升级为WebSocket协议。当客户端和服务器通过底层连接第一次握手时,WebSocket连接通过把HTTP协议转换升级为WebSockets协议而得以建立。一旦WebSocket连接成功建立,消息就可以在客户端和服务器之间进行双向发送

  • WebSockets比其它工作方式比如轮询更有效也更高效。因为它需要更少的带宽并且降低了延时。

  • WebSockets简化了实时应用的结构体系。

  • WebSockets在点到点发送消息时不需要头信息。这显著的降低了带宽。

一些可能的WebSockets使用案例有:

  • 聊天应用

  • 多人游戏

  • 股票交易和金融应用

  • 文档合作编辑

  • 社交应用

JSR 356,WebSocket的Java API,规定了开发者把WebSockets 整合进他们的应用时可以使用的Java API — 包括服务器端和Java客户端。JSR 356是Java EE 7标准中的一部分。这意味着所有Java EE 7兼容的应用服务器都将有一个遵守JSR 356标准的WebSocket协议的实现。开发者也可以在Java EE 7应用服务器之外使用JSR 356。目前Apache Tomcat 8提供了JSR 356 API的WebSocket支持。Jboss Wildfly 8 (原JBoss Application Server)也支持JSR 356.

一个Java客户端可以使用兼容JSR 356的客户端实现,来连接到WebSocket服务器。对web客户端来说,开发者可以使用WebSocket JavaScript API来和WebSocket服务器进行通讯。WebSocket客户端和WebSocket服务器之间的区别,仅在于两者之间是通过什么方式连接起来的。一个WebSocket客户端是一个WebSocket终端,它初始化了一个到对方的连接。一个WebSocket服务器也是一个WebSocket终端,它被发布出去并且等待来自对方的连接。在客户端和服务器端都有回调监听方法 -- onOpen , onMessage , onError, onClose。

怎么创建你的第一个WebSocket应用呢?基本上我们还是会使用Javascript API编写WebSocket客户端, 在服务器端, 本文使用JSR 356规范定义的通用模式和技术处理WebSocket的通讯。

下面看一个简单的例子, 演示了如果使用JavaScript WebSocket客户端与运行在Wildfly 8服务器通信.

客户端代码

      
        
        
      
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
      
        
        
      
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
</head>
<body>
<meta charset="utf-8">
<title>HelloWorld Web sockets </title>
<script language="javascript" type="text/javascript">
var wsUri = getRootUri() + "/websocket-hello/hello";
function getRootUri() {
return "ws://" + ( document.location.hostname == "" ? "localhost" : document.location.hostname) + ":" +
( document.location.port == "" ? "8080" : document.location.port);
}
function init() {
output = document.getElementById( "output");
}
function send_message() {
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) {
onOpen(evt)
};
websocket.onmessage = function(evt) {
onMessage(evt)
};
websocket.onerror = function(evt) {
onError(evt)
};
}
function onOpen(evt) {
writeToScreen( "Connected to Endpoint!");
doSend(textID.value);
}
function onMessage(evt) {
writeToScreen( "Message Received: " + evt.data);
}
function onError(evt) {
writeToScreen( '<span style="color: red;">ERROR:</span> ' + evt.data);
}
function doSend(message) {
writeToScreen( "Message Sent: " + message);
websocket.send(message);
}
function writeToScreen(message) {
var pre = document.createElement( "p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
window.addEventListener( "load", init, false);
</script>
<h1 style="text-align: center;">Hello World WebSocket Client </h2>
<br>
<div style="text-align: center;">
<form action="">
<input onclick="send_message()" value="Send" type="button">
<input id="textID" name="message" value="Hello WebSocket!" type="text"> <br>
</form>
</div>
<div id="output"> </div>
</body>
</html>

如你所见,要想使用WebSocket协议与服务器通信, 需要一个WebSocket对象。它会自动连接服务器.

      
        
        
      
1
      
        
        
      
websocket = new WebSocket(wsUri);

连接上会触发open事件:

      
        
        
      
1
2
3
      
        
        
      
websocket.onopen = function(evt) {
onOpen(evt)
};

一旦连接成功,则向服务器发送一个简单的hello消息。

      
        
        
      
1
      
        
        
      
websocket.send(message);

服务器端代码

有两种创建服务器端代码的方法:

  • 注解方式Annotation-driven: 通过在POJO加上注解, 开发者就可以处理WebSocket 生命周期事件.

  • 实现接口方式Interface-driven: 开发者可以实现Endpoint接口和声明周期的各个方法.

建议开发时采用注解方式, 这样可以使用POJO就可以实现WebSocket Endpoint. 而且不限定处理事件的方法名。代码也更简单。

本例就采用注解的方式, 接收WebSocket请求的类是一个POJO, 通过@ServerEndpoint标注. 这个注解告诉容器此类应该被当作一个WebSocket的Endpoint。value值就是WebSocket endpoint的path.

      
        
        
      
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
      
        
        
      
package com.sample.websocket;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint( "/hello")
public class HelloWorldEndpoint {
@OnMessage
public String hello(String message) {
System.out.println( "Received : "+ message);
return message;
}
@OnOpen
public void myOnOpen(Session session) {
System.out.println( "WebSocket opened: " + session.getId());
}
@OnClose
public void myOnClose(CloseReason reason) {
System.out.println( "Closing a WebSocket due to " + reason.getReasonPhrase());
}
}

注意:这个例子还包括了其它两个回调函数:@OnOpen标注的方法在WebSocket连接开始时被调用, Web Session作为参数。另外一个@OnClose标注的方法在连接关闭时被调用。

就是这么简单。但是为了编译这个例子你还需要Websockets API的实现,它在WildFly 8发布中(或者你用JSR 356的参考实现,或其它的容器提供的jar, 如tomcat):

      
        
        
      
1
      
        
        
      
modules \system \layers \base \javax \websocket \api \main \jboss-websocket-api_1.0_spec-1.0.0.Final.jar

对于Maven用户, 你需要增加undertow-websockets-jsr依赖:

      
        
        
      
1
2
3
4
5
      
        
        
      
<dependency>
<groupId>org.jboss.spec.javax.websocket </groupId>
<artifactId>jboss-websocket-api_1.0_spec </artifactId>
<version>1.0.0.Final </version>
</dependency>

这个例子比较早,应该是2013年写的,jsr 256还未发布。现在,你应该直接使用Java EE提供的API

      
        
        
      
1
2
3
4
5
      
        
        
      
<dependency>
<groupId>javax.websocket </groupId>
<artifactId>javax.websocket-api </artifactId>
<version>1.1 </version>
</dependency>

编解码器

前面的例子中WebSocket通信的消息类型默认为String。接下来的例子演示如何使用Encoder和Decoder传输更复杂的数据。
Websocket使用Decoder将文本消息转换成Java对象,然后传给@OnMessage方法处理; 而当对象写入到session中时,Websocket将使用Encoder将Java对象转换成文本,再发送给客户端。
更常用的, 我们使用XML 或者 JSON 来传送数据,所以将会会将Java对象与XML/JSON数据相互转换.

下图描绘了客户端和服务器使用encoder/decoder标准通信过程。

声明Encoder/Decoder也是相当的简单: 你只需在@ServerEndpoint注解中增加encoder/decoder设置:


      
        
        
      
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
      
        
        
      
package com.sample.websocket;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint(value = "/hello",
decoders = {
MessageDecoder.class,},
encoders = {
MessageEncoder.class
})
public class HelloWorldEndpoint {
@OnMessage
public Person hello(Person person, Session session) {
if (person.getName().equals( "john")) {
person.setName( "Mr. John");
}
try {
session.getBasicRemote().sendObject(person);
System.out.println( "sent ");
} catch (Exception ex) {
Logger.getLogger(HelloWorldEndpoint.class.getName()).log(Level.SEVERE, null, ex);
}
return person;
}
@OnOpen
public void myOnOpen(Session session) {
}
}


正像你看到的, OnMessage方法使用Java Object person作为参数, 我们为名字增加个尊称再返回给客户端。通过session.getBasicRemote().sendObject(Object obj)返回数据.
容器负责使用你指定的Decoder将接收到的XML消息转为Java对象:


      
        
        
      
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
      
        
        
      
package com.sample.websocket;
import java.io.StringReader;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;
import javax.xml.bind.*;
public class MessageDecoder implements Decoder.Text<Person> {
@Override
public Person decode(String s) {
System.out.println( "Incoming XML " + s);
Person person = null;
JAXBContext jaxbContext;
try {
jaxbContext = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StringReader reader = new StringReader(s);
person = (Person) unmarshaller.unmarshal(reader);
} catch (Exception ex) {
ex.printStackTrace();
}
return person;
}
@Override
public boolean willDecode(String s) {
return (s != null);
}
@Override
public void init(EndpointConfig endpointConfig) {
// do nothing.
}
@Override
public void destroy() {
// do nothing.
}
}


这里我们使用JAXB做转换。我们只要实现一个泛型接口Decoder.Text  或者 Decoder.Binary , 根据你传输的数据是文本还是二进制选择一个.

所以数据由Decoder解码, 传给Endpoint (这里的 HelloWorldEndpoint), 在返回给client之前, 它还会被下面的Encoder转换成XML:


      
        
        
      
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
      
        
        
      
package com.sample.websocket;
import java.io.StringWriter;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class MessageEncoder implements Encoder.Text<Person> {
@Override
public String encode(Person object) throws EncodeException {
JAXBContext jaxbContext = null;
StringWriter st = null;
try {
jaxbContext = JAXBContext.newInstance(Person.class);
Marshaller marshaller = jaxbContext.createMarshaller();
st = new StringWriter();
marshaller.marshal(object, st);
System.out.println( "OutGoing XML " + st.toString());
} catch (Exception ex) {
ex.printStackTrace();
}
return st.toString();
}
@Override
public void init(EndpointConfig endpointConfig) {
// do nothing.
}
@Override
public void destroy() {
// do nothing.
}
}


为了测试这个例子,将客户端的网页稍微修改一下以便能在textarea中粘帖XML:


      
        
        
      
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
      
        
        
      
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
</head>
<body>
<meta charset="utf-8">
<title>HelloWorld Web sockets </title>
<script language="javascript" type="text/javascript">
var wsUri = getRootUri() + "/websocket-hello/hello";
function getRootUri() {
return "ws://" + ( document.location.hostname == "" ? "localhost" : document.location.hostname) + ":" +
( document.location.port == "" ? "8080" : document.location.port);
}
function init() {
output = document.getElementById( "output");
}
function send_message() {
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) {
onOpen(evt)
};
websocket.onmessage = function(evt) {
onMessage(evt)
};
websocket.onerror = function(evt) {
onError(evt)
};
}
function onOpen(evt) {
writeToScreen( "Connected to Endpoint!");
doSend(textID.value);
}
function onMessage(evt) {
writeToScreen( "Message Received: " + evt.data);
}
function onError(evt) {
writeToScreen( '<span style="color: red;">ERROR:</span> ' + evt.data);
}
function doSend(message) {
writeToScreen( "Message Sent: " + message);
websocket.send(message);
}
function writeToScreen(message) {
alert(message);
}
window.addEventListener( "load", init, false);
</script>
<h1 style="text-align: center;">Hello World WebSocket Client </h2>
<br>
<div style="text-align: center;">
<form action="">
<input onclick="send_message()" value="Send" type="button">
<textarea id="textID" rows="4" cols="50" name="message" >
</textarea>
</form>
</div>
<div id="output"> </div>
</body>
</html>


在文本框中输入下面的XML进行测试。



      
        
        
      
1
2
3
4
      
        
        
      
<person>
<name>john</name>
<surname>smith</surname>

</person>


实例
package com.cloud.beatles.websocket.chat;
import com.cloud.beatles.websocket.chat.WebSocketUtil;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import java.io.IOException;
/**
* @Author xiaochen
* @Description 说明创建websocketendpoint
* @Date 22:54 2020/7/22
* @Param
* @Return
**/
//@Controller
//@ServerEndpoint("/chat/{username}")
public class ChatServerEndpoint {
/**
* @Author xiaochen
* @Description onopen 在连接创建(用户进入聊天室)时触发
* @Date 22:57 2020/7/22
* @Param [username, session]
* @Return void
**/
@OnOpen
public void openSession(@PathParam("username") String username, Session session) {
//存储用户
WebSocketUtil.USERS_ONLINE.put(username, session);
//向所有在线用户发送用户上线通知消息
String message = "[" + username + "]进入聊天室";
System.out.println(message);
WebSocketUtil.sendMessageToAllOnlineUser(message);
}
/**
* @Author xiaochen
* @Description onclose 在连接断开(用户离开聊天室)时触发
* @Date 22:57 2020/7/22
* @Param [username, session]
* @Return void
**/
@OnClose
public void closeSession(@PathParam("username") String username, Session session) {
//删除用户
WebSocketUtil.USERS_ONLINE.remove(username);
//向所有在线用户发送用户下线通知消息
String message = "[" + username + "]离开了聊天室";
System.out.println(message);
WebSocketUtil.sendMessageToAllOnlineUser(message);
//下线后关闭session
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @Author xiaochen
* @Description onmessage 在接收到消息时触发
* @Date 22:56 2020/7/22
* @Param [username, message]
* @Return void
**/
@OnMessage
public void onMessage(@PathParam("username") String username, String message) {
//向聊天室中的人发送消息
message = "[" + username + "]" + message;
System.out.println(message);
WebSocketUtil.sendMessageToAllOnlineUser(message);
}
/**
* @Author xiaochen
* @Description orerror 在连接发生异常时触发
* @Date 22:56 2020/7/22
* @Param
* @Return
**/
@OnError
public void sessionError(Session session, Throwable throwable) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("WebSocket连接发生异常,message:" + throwable.getMessage());
}
}


package com.cloud.beatles.websocket.chat;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @author xiaochen
**/
@Configuration
public class WebsocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}

package com.cloud.beatles.websocket.chat;
import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class WebSocketUtil {
/**
* 模拟存储 在线用户
*/
public static final Map<String, Session> USERS_ONLINE = new ConcurrentHashMap<>();
/**
* 向所有在线用户发送消息(遍历 向每一个用户发送)
*
* @param message
*/
public static void sendMessageToAllOnlineUser(String message) {
USERS_ONLINE.forEach((username, Session) -> sendMessage(Session, message));
}
/**
* 向指定用户发送消息
*
* @param session 用户session
* @param message 发送消息内容
*/
private static void sendMessage(Session session, String message) {
if (session == null) {
return;
}
final RemoteEndpoint.Basic basic = session.getBasicRemote();
if (basic == null) {
return;
}
try {
basic.sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}

<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<title>聊天室</title>
<script src="../../lib/jquery/jquery.min.js"></script>
</head>
<body>
<center>
<table>
<tr>
<td>
<label for="messageArea">聊天信息:</label><textarea id="messageArea" cols="50" rows="30"></textarea></br>
</td>
</tr>
<tr>
<td>
<label for="username">用 户 名:</label><input type="text" id="username">
</td>
</tr>
<tr>
<td>
<input type="button" id="joinRoomBtn" value="进入聊天室"/>
<input type="button" id="leaveRoomBtn" value="离开聊天室"/>
</td>
</tr>
<tr>
<td>
<label for="sendMessage">输入消息:</label><textarea id="sendMessage" cols="50" rows="3"></textarea></br>
</td>
</tr>
<tr>
<td>
<input type="button" id="sendBtn" value="发送消息"/>
</td>
</tr>
</table>
</center>
</body>
<script>
$(function () {
var webSocket;
var url = 'ws://localhost:8080/chat/';
//进入聊天室
$('#joinRoomBtn').click(function () {
var username = $('#username').val();
webSocket = new WebSocket(url + username);
webSocket.onopen = function () {
console.log('webSocket连接创建。。。');
};
webSocket.onclose = function () {
$('#messageArea').append('[' + username + ']离开了聊天室\n');
};
webSocket.onmessage = function (event) {
$('#messageArea').append(event.data + '\n');
};
webSocket.onerror = function (event) {
console.log('webSocket连接异常。。。');
}
});
//退出聊天室
$('#leaveRoomBtn').click(function () {
if (webSocket) {
//关闭连接
webSocket.close();
}
});
//发送消息
$('#sendBtn').click(function () {
var msg = $('#sendMessage').val();
webSocket.send(msg);
$('#sendMessage').val('');
});
});
</script>
</html>


版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《Java WebSocket教程》的版权归原作者「It编程技术交流」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

关注It编程技术交流微信公众号

It编程技术交流微信公众号:PandasBeatles

It编程技术交流

手机扫描上方二维码即可关注It编程技术交流微信公众号

It编程技术交流最新文章

精品公众号随机推荐