WebSocket (轉(zhuǎn)載)

開發(fā)環(huán)境:Windows x64,Eclipse 4.6.2,Tomcat 7.0.79,Jdk 1.8。

介紹

現(xiàn)很多網(wǎng)站為了實現(xiàn)即時通訊,所用的技術(shù)都是輪詢(polling)。輪詢是在特定的的時間間隔(如每1秒),由瀏覽器對服務(wù)器發(fā)出HTTP request,然后由服務(wù)器返回最新的數(shù)據(jù)給客服端的瀏覽器。這種傳統(tǒng)的HTTP request 的模式帶來很明顯的缺點 – 瀏覽器需要不斷的向服務(wù)器發(fā)出請求,然而HTTP request 的header是非常長的,里面包含的數(shù)據(jù)可能只是一個很小的值,這樣會占用很多的帶寬。

而最比較新的技術(shù)去做輪詢的效果是Comet – 用了AJAX。但這種技術(shù)雖然可達到全雙工通信,但依然需要發(fā)出請求。

在 WebSocket API,瀏覽器和服務(wù)器只需要要做一個握手的動作,然后,瀏覽器和服務(wù)器之間就形成了一條快速通道。兩者之間就直接可以數(shù)據(jù)互相傳送。

依賴:

Tomcat 7 或者 J2EE7

我用的tomcat 7.0.79和Java jdk1.8.0(1.7Linux服務(wù)器架設(shè)有問題),我使用的時候,沒有需要自己手動引用別的jar包,tomcat默認jar包就可以運行。

遇到的問題:

開始用的eclipse4.3版本,不支持jdk1.8,后改用的4.6.2。

雖然tomcat7.0.47就已支持WebSocket,但是tomcat7.0.47版本只支持jdk1.6,所以改用tomcat7.0.79.

注意:早前業(yè)界沒有統(tǒng)一的標準,各服務(wù)器都有各自的實現(xiàn),現(xiàn)在J2EE7的JSR356已經(jīng)定義了統(tǒng)一的標準,請盡量使用支持最新通用標準的服務(wù)器。

詳見:

http://www.oracle.com/technetwork/articles/java/jsr356-1937161.html

http://jinnianshilongnian.iteye.com/blog/1909962

用nginx做反向代理的需要注意啦,socket請求需要做特殊配置的,切記!

##服務(wù)端

服務(wù)端使用注解模式后,不需要在web.xml中做額外的配置,Tomcat啟動后就可以直接連接了(去掉注解,使用web.xml中配置后,啟動服務(wù)報錯cannot be cast to javax.servlet.Servlet)。

源碼:

package com.hdtytech.websocket;

import java.io.IOException;

import javax.websocket.OnClose;

import javax.websocket.OnError;

import javax.websocket.OnMessage;

import javax.websocket.OnOpen;

import javax.websocket.Session;

import javax.websocket.server.ServerEndpoint;

@ServerEndpoint(value = "/websocket")?

public class MyWebSocket{

? ? //與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)

? ? private Session session;

? ? /**

? ? * 連接建立成功調(diào)用的方法

? ? * @param session? 可選的參數(shù)。session為與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)

? ? * @throws Exception

? ? */

? ? @OnOpen

? ? public void onOpen(Session session) throws Exception{

? ? ? ? this.session = session;

? ? ? ? WebSocketMapUtil.put(session.getQueryString(),this);

? ? }


? ? /**

? ? * 連接關(guān)閉調(diào)用的方法

? ? * @throws Exception

? ? */

? ? @OnClose

? ? public void onClose() throws Exception{

? ? //從map中刪除

? ? WebSocketMapUtil.remove(session.getQueryString());

? ? }


? ? /**

? ? * 收到客戶端消息后調(diào)用的方法

? ? * @param message 客戶端發(fā)送過來的消息

? ? * @param session 可選的參數(shù)

? ? * @throws IOException

? ? */

? ? @OnMessage

? ? public void onMessage(String message, Session session) throws IOException {

? ? ? ? try {

? ? ? ? MyWebSocket myWebSocket= ((MyWebSocket) WebSocketMapUtil.get(session.getQueryString().replace("service", "client")));

? ? ? ? if(myWebSocket != null){

? ? ? ? myWebSocket.sendMessage(message);

? ? ? ? }

? ? ? ? } catch (IOException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }


? ? /**

? ? * 發(fā)生錯誤時調(diào)用

? ? * @param session

? ? * @param error

? ? */

? ? @OnError

? ? public void onError(Session session, Throwable error){

? ? ? ? error.printStackTrace();

? ? }



? ? /**

? ? * 發(fā)送消息方法。

? ? * @param message

? ? * @throws IOException

? ? */

? ? public void sendMessage(String message) throws IOException{

? ? ? ? this.session.getBasicRemote().sendText(message);

? ? }


? ? /**

? ? * 群發(fā)消息方法。

? ? * @param message

? ? * @throws IOException

? ? */

? ? public void sendMessageAll(String message) throws IOException{

? ? for(MyWebSocket myWebSocket : WebSocketMapUtil.getValues()){

? ? myWebSocket.sendMessage(message);

? ? }

? ? }

}

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

##客戶端

源碼:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

<%

String path = request.getContextPath();

String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";

%>

<!DOCTYPE HTML>

<html>

? <head>

? ? <base href="<%=basePath%>">

? ? <title>My WebSocket</title>

? </head>


? <body>

? ? Welcome<br/>

? ? <input id="text" type="text" /><button onclick="send()">Send</button>? ? <button onclick="closeWebSocket()">Close</button>

? ? <div id="message">

? ? </div>

? </body>


? <script type="text/JavaScript">

? ? ? var websocket = null;

? ? ? //判斷當(dāng)前瀏覽器是否支持WebSocket

? ? ? if('WebSocket' in window){

? ? ? ? ? websocket = new WebSocket("ws://localhost:8080/MyWebSocket/websocket?client123");

? ? ? }

? ? ? else{

? ? ? ? ? alert('Not support websocket');

? ? ? }


? ? ? //連接發(fā)生錯誤的回調(diào)方法

? ? ? websocket.onerror = function(){

? ? ? ? ? setMessageInnerHTML("error");

? ? ? };


? ? ? //連接成功建立的回調(diào)方法

? ? ? websocket.onopen = function(event){

? ? ? ? ? setMessageInnerHTML("open");

? ? ? };


? ? ? //接收到消息的回調(diào)方法

? ? ? websocket.onmessage = function(event){

? ? ? ? ? setMessageInnerHTML(event.data);

? ? ? };


? ? ? //連接關(guān)閉的回調(diào)方法

? ? ? websocket.onclose = function(){

? ? ? ? ? setMessageInnerHTML("close");

? ? ? };


? ? ? //監(jiān)聽窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時,主動去關(guān)閉websocket連接,防止連接還沒斷開就關(guān)閉窗口,server端會拋異常。

? ? ? window.onbeforeunload = function(){

? ? ? ? ? websocket.close();

? ? ? };


? ? ? //將消息顯示在網(wǎng)頁上

? ? ? function setMessageInnerHTML(innerHTML){

? ? ? ? ? document.getElementById('message').innerHTML += innerHTML + '<br/>';

? ? ? }


? ? ? //關(guān)閉連接

? ? ? function closeWebSocket(){

? ? ? ? ? websocket.close();

? ? ? }


? ? ? //發(fā)送消息

? ? ? function send(){

? ? ? ? ? var message = document.getElementById('text').value;

? ? ? ? ? websocket.send(message);

? ? ? }

? </script>

</html>

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

##工具類ConcurrentMap

用來存儲唯一key和連接(ConcurrentMap是線性安全的Map)

這個是我業(yè)務(wù)的需要,因為我的業(yè)務(wù)是服務(wù)器需要主動點對點發(fā)送消息給客戶端,所以需要用Map來存儲客戶端連接。

遇到的問題:

本來想直接調(diào)用服務(wù)端的發(fā)送接口,從Map中取到需要發(fā)送的客戶端連接信息,但是只要在外部調(diào)用MapUtil,MapUtil就會重新實例化,Map為空,也就是把連接放在單獨靜態(tài)Map中不可行。

后來也考慮把Map放在session中,但是考慮到session容易丟失,就放棄了這個想法。

又考慮使用memcache,但是在往memcache放的時候,報需要序列化的錯,也放棄了這個想法。

解決辦法:

后來就模仿微信聊天一樣,把服務(wù)端做為一個中間轉(zhuǎn)發(fā)消息的中間件,在服務(wù)端定義一個客戶端類,在需要給客戶端發(fā)送消息時,調(diào)用客戶端類, 發(fā)送消息到中間件,通過中間件轉(zhuǎn)發(fā)消息到你想發(fā)送的客戶端,若需要取法消息,則可便利Map客戶端進行群發(fā)通知公告等。

源碼:

package com.hdtytech.websocket;

import java.util.Collection;

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.ConcurrentMap;

public class WebSocketMapUtil {

public static ConcurrentMap<String, MyWebSocket> webSocketMap = new ConcurrentHashMap<>();

? ? public static void put(String key, MyWebSocket myWebSocket){

? ? webSocketMap.put(key, myWebSocket);

? ? }


? ? public static MyWebSocket get(String key){

? ? return webSocketMap.get(key);

? ? }


public static void remove(String key){

webSocketMap.remove(key);

}

public static Collection<MyWebSocket> getValues(){

return webSocketMap.values();

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

##服務(wù)端用于主動發(fā)消息的客戶端類

源碼:

package com.hdtytech.websocket;

import java.io.IOException;

import java.net.URI;

import javax.websocket.ClientEndpoint;

import javax.websocket.ContainerProvider;

import javax.websocket.OnClose;

import javax.websocket.OnError;

import javax.websocket.OnMessage;

import javax.websocket.OnOpen;

import javax.websocket.Session;

import javax.websocket.WebSocketContainer;

@ClientEndpoint

public class MyClient {

private Session session;

@OnOpen

public void onOpen(Session session) throws IOException {

this.session = session;

}

@OnMessage

public void onMessage(String message) {

}

@OnError

public void onError(Throwable t) {

t.printStackTrace();

}

/**

? ? * 連接關(guān)閉調(diào)用的方法

? ? * @throws Exception

? ? */

? ? @OnClose

? ? public void onClose() throws Exception{

? ? }


? ? /**

? ? * 關(guān)閉鏈接方法

? ? * @param message

? ? * @throws IOException

? ? */

? ? public void closeSocket() throws IOException{

? ? ? ? this.session.close();

? ? }


? ? /**

? ? * 發(fā)送消息方法。

? ? * @param message

? ? * @throws IOException

? ? */

? ? public void sendMessage(String message) throws IOException{

? ? ? ? this.session.getBasicRemote().sendText(message);

? ? }

? ? //啟動客戶端并建立鏈接

? ? public void start(String uri) {

WebSocketContainer container = ContainerProvider.getWebSocketContainer();

try {

this.session = container.connectToServer(MyClient.class, URI.create(uri));

} catch (Exception e) {

e.printStackTrace();

}

}

}

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

##測試main函數(shù)

說明:為了測試,寫了一個timer,每個兩秒,調(diào)用發(fā)送給客戶端發(fā)送一條消息;

源碼:

package com.hdtytech.websocket;

import java.io.IOException;

import java.util.Timer;

import java.util.TimerTask;

public class MyClientApp {

public static void main(String[] args){

// TODO todo.generated by zoer?

? ? ? ? Timer timer = new Timer();?

? ? ? ? timer.schedule(new MyTask(), 1000, 2000);?

}

static int num = 0;

static class MyTask extends TimerTask {?


? ? @Override?

? ? public void run() {?

? ? MyClient client = new MyClient();

String uri = "ws://localhost:8080/MyWebSocket/websocket?service123";

client.start(uri);

try {

num++;

client.sendMessage("消息測試"+num);

client.closeSocket();

System.out.println(num);

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

? ? }?


}

}

————————————————

版權(quán)聲明:本文為CSDN博主「君子丨莫笑」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。

原文鏈接:https://blog.csdn.net/gaolele_92/article/details/76158087

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容