WebSocket是JavaEE7新支持的:
Javax.websocket.server包含注解,類,接口用於創建和配置服務端點
Javax.websocket包則包含服務端點和客戶斷電公用的注解,類,接口,異常
創建一個編程式的端點,需要繼承Endpoint類,重寫它的方法。
創建一個注解式的端點,將自己的寫的類以及類中的一些方法用前面提到的包中的注解裝飾(@EndPoint,@OnOpen等等)。
編程式注解示例:
-
-
-
public class MyWebSocket {
-
-
private static final Logger log = LoggerFactory.getLogger(MyWebSocket.class);
-
-
// 靜態變量,用來記錄當前在線連接數。應該把它設計成線程安全的。
-
private static int onlineCount = 0;
-
-
// concurrent包的線程安全Map,用來存放每個客戶端對應的MyWebSocket對象。
-
private static ConcurrentHashMap<String, MyWebSocket> webSocketMap = new ConcurrentHashMap<String, MyWebSocket>();
-
-
// 與某個客戶端的連接會話,需要通過它來給客戶端發送數據
-
private Session session;
-
-
private String ip; // 客戶端ip
-
public static final String ACTION_PRINT_ORDER = "printOrder";
-
public static final String ACTION_SHOW_PRINT_EXPORT = "showPrintExport";
-
-
/**
-
* 連接建立成功調用的方法
-
*/
-
-
public void onOpen(Session session, @PathParam("ip") String ip) {
-
this.session = session;
-
this.ip = ip;
-
webSocketMap.put(ip, this);
-
addOnlineCount(); // 在線數加1
-
// System.out.println("有新連接加入!當前在線人數為" + getOnlineCount());
-
log.info( "有新連接加入,ip:{}!當前在線人數為:{}", ip, getOnlineCount());
-
ExportService es = BeanUtils.getBean(ExportService.class);
-
List<String> list = es.listExportCodesByPrintIp(ip);
-
ResponseData<String> rd = new ResponseData<String>();
-
rd.setAction(MyWebSocket.ACTION_SHOW_PRINT_EXPORT);
-
rd.setList(list);
-
sendObject(rd);
-
}
-
-
/**
-
* 連接關閉調用的方法
-
*/
-
-
public void onClose(@PathParam("ip") String ip) {
-
webSocketMap.remove(ip); // 從set中刪除
-
// Map<String, String> map = session.getPathParameters();
-
// webSocketMap.remove(map.get("ip"));
-
subOnlineCount(); // 在線數減1
-
// System.out.println("有一連接關閉!當前在線人數為" + getOnlineCount());
-
log.info( "websocket關閉,IP:{},當前在線人數為:{}", ip, getOnlineCount());
-
}
-
-
/**
-
* 收到客戶端消息后調用的方法
-
*
-
* @param message
-
* 客戶端發送過來的消息
-
*/
-
-
public void onMessage(String message, Session session) {
-
// System.out.println("來自客戶端的消息:" + message);
-
log.debug( "websocket來自客戶端的消息:{}", message);
-
OrderService os = BeanUtils.getBean(OrderService.class);
-
OrderVo ov = os.getOrderDetailByOrderNo(message);
-
// System.out.println(ov);
-
ResponseData<OrderVo> rd = new ResponseData<OrderVo>();
-
ArrayList<OrderVo> list = new ArrayList<OrderVo>();
-
list.add(ov);
-
rd.setAction(MyWebSocket.ACTION_PRINT_ORDER);
-
rd.setList(list);
-
sendObject(rd);
-
// log.info("推送打印信息完成,單號:{}", ov.getOrderNo());
-
}
-
-
/**
-
* 發生錯誤時調用
-
*/
-
-
public void onError(Session session, Throwable error) {
-
// System.out.println("發生錯誤");
-
log.error( "webSocket發生錯誤!IP:{}", ip);
-
error.printStackTrace();
-
}
-
-
/**
-
* 像當前客戶端發送消息
-
*
-
* @param message
-
* 字符串消息
-
* @throws IOException
-
*/
-
public void sendMessage(String message) {
-
try {
-
this.session.getBasicRemote().sendText(message);
-
// this.session.getAsyncRemote().sendText(message);
-
} catch (IOException e) {
-
e.printStackTrace();
-
log.error( "發送數據錯誤,ip:{},msg:{}", ip, message);
-
}
-
}
-
-
/**
-
* 向當前客戶端發送對象
-
*
-
* @param obj
-
* 所發送對象
-
* @throws IOException
-
*/
-
public void sendObject(Object obj) {
-
ObjectMapper mapper = new ObjectMapper();
-
mapper.setSerializationInclusion(Include.NON_NULL);
-
String s = null;
-
try {
-
s = mapper.writeValueAsString(obj);
-
} catch (JsonProcessingException e) {
-
e.printStackTrace();
-
log.error( "轉json錯誤!{}", obj);
-
}
-
this.sendMessage(s);
-
}
-
-
/**
-
* 群發自定義消息
-
*/
-
public static void sendInfo(String message) {
-
for (Entry<String, MyWebSocket> entry : webSocketMap.entrySet()) {
-
MyWebSocket value = entry.getValue();
-
value.sendMessage(message);
-
}
-
}
-
-
public static synchronized int getOnlineCount() {
-
return onlineCount;
-
}
-
-
public static synchronized void addOnlineCount() {
-
MyWebSocket.onlineCount++;
-
}
-
-
public static synchronized void subOnlineCount() {
-
MyWebSocket.onlineCount--;
-
}
-
-
public static ConcurrentHashMap<String, MyWebSocket> getWebSocketMap() {
-
return webSocketMap;
-
}
-
-
}
當創建好一個(服務)端點之后,將它以一個指定的URI發布到應用當中,這樣遠程客戶端就能連接上它了。
Websocket(服務)端點以URI表述,有如下的訪問方式:
ws://host:port/path?query
wss://host:port/path?query
注解詳解:
@ServerEndPoint:
RequiredElements :
Value: URI映射
OptionalElemens:
subprotocols:
decoders:解碼器
encoders:編碼器
configurator
建立連接相關:
| Annotation |
Event |
Example |
| OnOpen |
Connection opened |
@OnOpen Public void open(Sessionsession, EndpointConfig conf) { } |
| OnMessage |
Message received |
@OnMessage public void message(Sessionsession, String msg) { } |
| OnError |
Connection error |
@OnError public void error(Sessionsession, Throwable error) { } |
| OnClose |
Connection closed |
@OnClose public void close(Sessionsession, CloseReason reason) { } |
Session代表着服務端點與遠程客戶端點的一次會話。
容器會為每一個連接創建一個EndPoint的實例,需要利用實例變量來保存一些狀態信息。
Session.getUserProperties提供了一個可編輯的map來保存properties,
例如,下面的端點在收到文本消息時,將前一次收到的消息回復給其他的端點
-
-
-
public class DelayedEchoEndpoint
-
-
{
-
-
-
-
public void open(Sessionsession)
-
-
{
-
-
session.getUserProperties().put( "previousMsg", "");
-
-
}
-
-
-
-
-
-
public void message(Session session, Stringmsg)
-
-
{
-
-
-
-
String prev= (String) session.getUserProperties().get( "previousMsg");
-
-
-
-
session.getUserProperties().put( "previousMsg",msg);
-
-
try {
-
-
session.getBasicRemote().sendText(prev);
-
-
-
-
} catch (IOException e){ ... }
-
-
}
-
-
}
發送、接收消息:
Websocketendpoint能夠發送和接收文本、二進制消息,另外,也可以發送ping幀和接收pong 幀
發送消息:
Obtain the Session object from theconnection.
從連接中獲得Session對象
Session對象是endPoint中那些被注解標注的方法中可得到的參數
當你的message作為另外的消息的響應
在@OnMessage標注的方法中,有session對象接收message
如果你必須主動發送message,需要在標注了@OnOpen的方法中將session對象作為實例變量保存
這樣,你可以再其他方法中得到該session對象
1.Use the Session object to obtain aRemote Endpoint object.
通過Session對象獲得Remoteendpoint對象
2.Use the RemoteEndpoint object to sendmessages to the peer.
利用RemoteEndpoint對象來發送message
代碼示例:
@ServerEndpoint("/echoall")
public class EchoAllEndpoint
{
@OnMessage
public void onMessage(Session session, Stringmsg)
{
try {
for (Session sess :session.getOpenSessions())
{
if (sess.isOpen())
sess.getBasicRemote().sendText(msg);
}
} catch (IOExceptione) { ... }
}
}
接收消息
OnMessage注解指定方法來處理接收的messages
在一個端點類中,至多可以為三個方法標注@OnMessage注解
消息類型分別為:text、binary、pong。
我們查看一下ServerEndpoint類源碼:
-
-
-
public
-
-
public String value();
-
-
public String[] subprotocols() default {};
-
-
public Class<? extends Decoder>[] decoders() default {};
-
-
public Class<? extends Encoder>[] encoders() default {};
-
-
public Class<? extends ServerEndpointConfig.Configurator> configurator() default ServerEndpointConfig.Configurator.class;
Encoders and Decoders(編碼器和解碼器):
WebSocket Api 提供了encoders 和 decoders用於 Websocket Messages 與傳統java 類型之間的轉換
An encoder takes a Java object and produces a representation that can be transmitted as a WebSocket message;
編碼器輸入java對象,生成一種表現形式,能夠被轉換成Websocket message
for example, encoders typically produce JSON, XML, or binary representations.
例如:編碼器通常生成json、XML、二進制三種表現形式
A decoder performs the reverse function; it reads a WebSocket message and creates a Java object.
解碼器執行相反的方法,它讀入Websocket消息,然后輸出java對象
編碼器編碼:
looks for an encoder that matches your type and uses it to convert the object to a WebSocket message.
利用RemoteEndpoint.Basic 或者RemoteEndpoint.Async的sendObject(Object data)方法將對象作為消息發送,容器尋找一個符合此對象的編碼器,
利用此編碼器將此對象轉換成Websocket message
代碼示例:可以指定為自己的一個消息對象
-
package com.zlxls.information;
-
-
import com.alibaba.fastjson.JSON;
-
import com.common.model.SocketMsg;
-
import javax.websocket.EncodeException;
-
import javax.websocket.Encoder;
-
import javax.websocket.EndpointConfig;
-
-
/**
-
* 配置WebSocket解碼器,用於發送請求的時候可以發送Object對象,實則是json數據
-
* sendObject()
-
* @ClassNmae:ServerEncoder
-
* @author zlx-雄雄
-
* @date 2017-11-3 15:47:13
-
*
-
*/
-
public class ServerEncoder implements Encoder.Text<SocketMsg> {
-
-
-
public void destroy() {
-
// TODO Auto-generated method stub
-
-
}
-
-
-
public void init(EndpointConfig arg0) {
-
// TODO Auto-generated method stub
-
-
}
-
-
-
public String encode(SocketMsg socketMsg) throws EncodeException {
-
try {
-
return JSON.toJSONString(socketMsg);
-
} catch (Exception e) {
-
// TODO Auto-generated catch block
-
e.printStackTrace();
-
return "";
-
}
-
}
-
-
}
Then, add the encodersparameter to the ServerEndpointannotation as follows:
@ServerEndpoint(
value = "/myendpoint",
encoders = { ServerEncoder.class, ServerEncoder1.class }
)
解碼器解碼:
Decoder.Binary<T>for binary messages
These interfaces specify the willDecode and decode methods.
the container calls the method annotated with @OnMessage that takes your custom Java type as a parameter if this method exists.
-
package com.zlxls.information;
-
-
import com.common.model.SocketMsg;
-
import javax.websocket.DecodeException;
-
import javax.websocket.Decoder;
-
import javax.websocket.EndpointConfig;
-
/**
-
* 解碼器執,它讀入Websocket消息,然后輸出java對象
-
* @ClassNmae:ServerDecoder
-
* @author zlx-雄雄
-
* @date 2017-11-11 9:12:09
-
*
-
*/
-
public class ServerDecoder implements Decoder.Text<SocketMsg>{
-
-
-
public void init(EndpointConfig ec){}
-
-
-
public void destroy(){}
-
-
-
public SocketMsg decode(String string) throws DecodeException{
-
// Read message...
-
return new SocketMsg();
-
}
-
-
-
public boolean willDecode(String string){
-
// Determine if the message can be converted into either a
-
// MessageA object or a MessageB object...
-
return false;
-
}
-
}
Then, add the decoderparameter to the ServerEndpointannotation as follows:
@ServerEndpoint(
value = "/myendpoint",
encoders = { ServerEncoder.class, ServerEncoder1.class },
decoders = {ServerDecoder.class }
)
處理錯誤:
To designate a method that handles errors in an annotated WebSocket endpoint, decorate it with @OnError:
-
/**
-
* 發生錯誤是調用方法
-
* @param t
-
* @throws Throwable
-
*/
-
-
public void onError(Throwable t) throws Throwable {
-
System.out.println( "錯誤: " + t.toString());
-
}
為一個注解式的端點指定一個處理error的方法,為此方法加上@OnError注解:
This method is invoked when there are connection problems, runtime errors from message handlers, or conversion errors when decoding messages.
當出現連接錯誤,運行時錯誤或者解碼時轉換錯誤,該方法才會被調用
指定端點配置類:
The Java API for WebSocket enables you to configure how the container creates server endpoint instances.
Websocket的api允許配置容器合適創建server endpoint 實例
You can provide custom endpoint configuration logic to:
Access the details of the initial HTTP request for a WebSocket connection
Perform custom checks on the OriginHTTP header
Modify the WebSocket handshake response
Choose a WebSocket subprotocol from those requested by the client
Control the instantiation and initialization of endpoint instances
To provide custom endpoint configuration logic, you extend the ServerEndpointConfig.Configurator class and override some of its methods.
繼承ServerEndpointConfig.Configurator 類並重寫一些方法,來完成custom endpoint configuration 的邏輯代碼
In the endpoint class, you specify the configurator class using the configurator parameter of the ServerEndpoint annotation.
代碼示例:
-
package com.zlxls.information;
-
-
import javax.servlet.http.HttpSession;
-
import javax.websocket.HandshakeResponse;
-
import javax.websocket.server.HandshakeRequest;
-
import javax.websocket.server.ServerEndpointConfig;
-
import javax.websocket.server.ServerEndpointConfig.Configurator;
-
/**
-
* 由於websocket的協議與Http協議是不同的,
-
* 所以造成了無法直接拿到session。
-
* 但是問題總是要解決的,不然這個websocket協議所用的場景也就沒了
-
* 重寫modifyHandshake,HandshakeRequest request可以獲取httpSession
-
* @ClassNmae:GetHttpSessionConfigurator
-
* @author zlx-雄雄
-
* @date 2017-11-3 15:47:13
-
*
-
*/
-
public class GetHttpSessionConfigurator extends Configurator{
-
-
public void modifyHandshake(ServerEndpointConfig sec,HandshakeRequest request, HandshakeResponse response) {
-
-
HttpSession httpSession=(HttpSession) request.getHttpSession();
-
-
sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
-
-
}
-
}
-
-
public void open(Session s, EndpointConfig conf){
-
-
HandshakeRequest req = (HandshakeRequest) conf.getUserProperties().get( "sessionKey");
-
-
}
@ServerEndpoint(
value = "/myendpoint",
configurator=GetHttpSessionConfigurator.class
)
不過要特別說一句:
HandshakeRequest req = (HandshakeRequest) conf.getUserProperties().get("sessionKey"); 目前獲取到的是空值。會報錯:java.lang.NullPointerException,這個錯誤信息,大家最熟悉不過了。
原因是:請求頭里面並沒有把相關的信息帶上
這里就需要實現一個監聽,作用很明顯:將所有request請求都攜帶上httpSession,這樣就可以正常訪問了
說明:注解非常簡單可以直接使用注解@WebListener,也可以再web.xml配置監聽
-
package com.zlxls.information;
-
-
import javax.servlet.ServletRequestEvent;
-
import javax.servlet.ServletRequestListener;
-
import javax.servlet.annotation.WebListener;
-
import javax.servlet.http.HttpServletRequest;
-
-
/**
-
* http://www.cnblogs.com/zhuxiaojie/p/6238826.html
-
* 配置監聽器,將所有request請求都攜帶上httpSession
-
* 用於webSocket取Session
-
* @ClassNmae:RequestListener
-
* @author zlx-雄雄
-
* @date 2017-11-4 11:27:33
-
*
-
*/
-
-
public class RequestListener implements ServletRequestListener {
-
-
public void requestInitialized(ServletRequestEvent sre) {
-
-
//將所有request請求都攜帶上httpSession
-
((HttpServletRequest) sre.getServletRequest()).getSession();
-
-
}
-
public RequestListener() {}
-
-
-
public void requestDestroyed(ServletRequestEvent arg0) {}
-
}
-
