小例子说明
不是所有的Client都是前端页面,服务器也可能发起一个WebSocket连接,向其他服务器请求某项服务。小例子模拟两个WebSocket客户端,向server建立连接,当server收到消息时,向所有的连接的client分发该消息,当某个client连接或者关闭连接时,向其他client发布状态变化消息。
为了方便测试,client和server都在同一个web app中,要求client启动连接和发送消息,使用servlet进行触发。
二进制的消息
二进制消息需要进行序列化。在Java中对象序列化比JSON要高效,但是与非Java的程序互动,流行JSON。
public class ClusterMessage implements Serializable{
private static final long serialVersionUID = 1L;
private String nodeId;
private String message;
public ClusterMessage(){
}
public ClusterMessage(String nodeId , String message){
this.nodeId = nodeId;
this.message = message;
}
public String getNodeId() {
return nodeId;
}
public void setNodeId(String nodeId) {
this.nodeId = nodeId;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Cluster Server代码
我们重点学习如何解析二进制消息,如何发送二进制消息。
@ServerEndpoint("/clusterNodeSocket/{nodeId}")
public class ClusterNodeServerEndpoint {
//存储所有的client,以便进行消息广播
private static final List<Session> nodes = new ArrayList<>(2);
@OnOpen
public void onOpen(Session session, @PathParam("nodeId") String nodeId){
System.out.println("Node [" + nodeId + "] connected to cluster server");
ClusterMessage message = new ClusterMessage(nodeId, "Joined the cluster.");
try {
byte[] bytes = ClusterNodeServerEndpoint.toByteArray(message);
for(Session node : nodes){
node.getBasicRemote().sendBinary(ByteBuffer.wrap(bytes));
}
} catch (IOException e) {
System.out.println("Exception when notifying of new node : " + e.toString());
e.printStackTrace();
}
ClusterNodeServerEndpoint.nodes.add(session);
}
@OnMessage
public void onMessage(Session session, byte[] message){
try{
for(Session node : ClusterNodeServerEndpoint.nodes){
if(node != session)
node.getBasicRemote().sendBinary(ByteBuffer.wrap(message)); //发送二进制消息byte[]
}
}catch (IOException e) {
logger.error("Exception when handling message on server : " + e.toString());
e.printStackTrace();
}
}
@OnClose
public void onClose(Session session, @PathParam("nodeId") String nodeId){
System.out.println("Node [" + nodeId + "] disconnected.");
ClusterNodeServerEndpoint.nodes.remove(session);
ClusterMessage message = new ClusterMessage(nodeId, "Left the cluster.");
try{
for(Session node : ClusterNodeServerEndpoint.nodes){
node.getBasicRemote().sendBinary(ByteBuffer.wrap(
ClusterNodeServerEndpoint.toByteArray(message)));
}
}catch (IOException e) {
System.out.println("Exception when notifying of left node : " + e.toString());
e.printStackTrace();
}
}
//将对象转换为byte[]: message(ClusterMessage) -> stream(ObjectOutputStream) --> output(ByteArrayOutputStream)
private static byte[] toByteArray(ClusterMessage message) throws IOException {
try(ByteArrayOutputStream output = new ByteArrayOutputStream();
ObjectOutputStream stream = new ObjectOutputStream(output)){
stream.writeObject(message);
return output.toByteArray();
}
}
}
Cluster Client代码
我们模拟两个webSocket clients,为了方便触发,采用Servlet。在web.xml中定义如下,将两个servlet指向同一个类
<servlet>
<servlet-name>clusterNode1</servlet-name>
<servlet-class>cn.wei.flowingflying.chapter10.cluster.ClusterNodeServlet</servlet-class>
<init-param>
<param-name>nodeId</param-name>
<param-value>1</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>clusterNode2</servlet-name>
<url-pattern>/clusterNode2</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>clusterNode2</servlet-name>
<servlet-class>cn.wei.flowingflying.chapter10.cluster.ClusterNodeServlet</servlet-class>
<init-param>
<param-name>nodeId</param-name>
<param-value>2</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>clusterNode2</servlet-name>
<url-pattern>/clusterNode2</url-pattern>
</servlet-mapping>
WebSocket代码如下:
//annotation @ClientEndpoint 不会像@ServiceEndpoint 那样自动实例化,只是作为一个有效endpoint的标记
@ClientEndpoint
public class ClusterNodeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private String nodeId;
private Session session;
//在init()时读取配置信息,建立webSocket连接
@Override
public void init() throws ServletException {
this.nodeId = this.getInitParameter("nodeId");
String path = this.getServletContext().getContextPath() +
"/clusterNodeSocket/" + this.nodeId;
try {
//【1】连接webSocket server
URI uri = new URI("ws","localhost:8080",path,null,null);
this.session = ContainerProvider.getWebSocketContainer().connectToServer(this, uri);
} catch (URISyntaxException | DeploymentException | IOException e) {
e.printStackTrace();
throw new ServletException("Cannot connect to " + path + ".", e);
}
}
@Override
public void destroy() {
try {
this.session.close(); //关闭webSocket连接
} catch (IOException e) {
e.printStackTrace();
}
super.destroy();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//【2】发送对象的二进制消息
ClusterMessage message = new ClusterMessage(this.nodeId,
"request:{ip:\"" + request.getRemoteAddr() + "\",queryString:\"" + request.getQueryString() + "\"}");
try(OutputStream output = this.session.getBasicRemote().getSendStream();
ObjectOutputStream stream = new ObjectOutputStream(output)){
stream.writeObject(message);
}
response.getWriter().append("OK");
}
@OnMessage
public void onMessage(InputStream input){
//【3】读二进制信息
try(ObjectInputStream stream = new ObjectInputStream(input)){
ClusterMessage message = (ClusterMessage)stream.readObject();
System.out.println("Node [" + this.nodeId + "]: Message received from cluster; node = " +
message.getNodeId() + ", message = " + message.getMessage());
}catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
@OnClose
public void onClose(CloseReason reason){
CloseReason.CloseCode code = reason.getCloseCode();
if(code != CloseReason.CloseCodes.NORMAL_CLOSURE){
System.out.println("WebSocket connection closed unexpectedly;" +
" code = " + code + ", reason = " + reason.getReasonPhrase());
}
}
}
相关链接: 我的Professional Java for Web Applications相关文章