websocket簡單實現在線聊天


WebSocket簡介與消息推送

B/S架構的系統多使用HTTP協議,HTTP協議的特點:

1 無狀態協議
2 用於通過 Internet 發送請求消息和響應消息
3 使用端口接收和發送消息,默認為80端口
底層通信還是使用Socket完成。

HTTP協議決定了服務器與客戶端之間的連接方式,無法直接實現消息推送(F5已壞),一些變相的解決辦法:

雙向通信與消息推送

輪詢:客戶端定時向服務器發送Ajax請求,服務器接到請求后馬上返回響應信息並關閉連接。  優點:后端程序編寫比較容易。  缺點:請求中有大半是無用,浪費帶寬和服務器資源。  實例:適於小型應用。

長輪詢:客戶端向服務器發送Ajax請求,服務器接到請求后hold住連接,直到有新消息才返回響應信息並關閉連接,客戶端處理完響應信息后再向服務器發送新的請求。  優點:在無消息的情況下不會頻繁的請求,耗費資小。  缺點:服務器hold連接會消耗資源,返回數據順序無保證,難於管理維護。 Comet異步的ashx, 實例:WebQQ、Hi網頁版、Facebook IM。

長連接:在頁面里嵌入一個隱蔵iframe,將這個隱蔵iframe的src屬性設為對一個長連接的請求或是采用xhr請求,服務器端就能源源不斷地往客戶端輸入數據。  優點:消息即時到達,不發無用請求;管理起來也相對便。  缺點:服務器維護一個長連接會增加開銷。  實例:Gmail聊天

Flash Socket:在頁面中內嵌入一個使用了Socket類的 Flash 程序JavaScript通過調用此Flash程序提供的Socket接口與服務器端的Socket接口進行通信,JavaScript在收到服務器端傳送的信息后控制頁面的顯示。  優點:實現真正的即時通信,而不是偽即時。  缺點:客戶端必須安裝Flash插件;非HTTP協議,無法自動穿越防火牆。  實例:網絡互動游戲。

Websocket:
WebSocket是HTML5開始提供的一種瀏覽器與服務器間進行全雙工通訊的網絡技術。依靠這種技術可以實現客戶端和服務器端的長連接,雙向實時通信。
特點:
事件驅動
異步
使用ws或者wss協議的客戶端socket

能夠實現真正意義上的推送功能

缺點:

少部分瀏覽器不支持,瀏覽器支持的程度與方式有區別。

 

JavaEE 7中出了JSR-356:Java API for WebSocket規范。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。

一、下面demo使用jetty實現:

項目結構:

java后台代碼:

package edu.nf.ws.server;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Set;

/**
 * @author wangl
 * @date 2018-12-05
 * websocket服務端
 */
@ServerEndpoint("/chat/server/{userName}")
public class ChatServer {

    /**
     * 當有客戶端連接到服務端的時候就會調用這個方法
     * session代表客戶端和服務端的一個連接會話對象
     * ,由容器負責創建和維護
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userName") String userName){
        System.out.println("有客戶端連接..."+userName);
        //將用戶名保存到當前用戶會話的屬性中(有點類似作用域的概念)
        session.getUserProperties().put("user", userName);
    }

    /**
     * 客戶端和服務器之間通信的方法,
     * 服務端每當接收到客戶端的消息就會調用這個方法
     * ,注意:必須指定一個String類型的參數,表示接收到客戶端的文本消息
     */
    @OnMessage
    public void onMessage(String message, Session session) throws IOException{
        System.out.println("接收消息..." + message);
        //將消息發送給所有人
        sendAllUser(message, session);
    }

    /**
     * 當客戶端關閉或者斷開連接時,服務端會調用此方法
     * @param session
     */
    @OnClose
    public void opnClose(Session session) throws IOException{
        System.out.println("客戶端失去連接...");
        //關閉會話
        session.close();
    }

    private void sendAllUser(String message, Session session) throws IOException{
        //獲取所有人的會話對象
        Set<Session> users = session.getOpenSessions();
        //獲取發送人
        String sendUser = session.getUserProperties().get("user").toString();
        //發送給所有人
        for (Session user : users) {
            user.getBasicRemote().sendText(sendUser + " : " + message);
        }
    }
}
View Code

html代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="loginDiv">
    用戶名:<input type="text" id="userName" name="userName"/>
    <input type="button" id="login" value="login"/>
    </div>

    <div id="container" style="display: none">
        <div id="content"></div>
        <input type="text" name="msg" id="msg"/>
        <input type="button" id="send" value="send"/>
    </div>

<script src="js/jquery-3.3.1.min.js"></script>
<script>
    $(function(){
        var ws;
        //登陸
        $('#login').on('click',function(){
            var userName = $('#userName').val();
            //創建websocket對象並連接服務端
             ws = new WebSocket('ws://localhost:8080/chat/server/' + userName);

            //客戶端打開連接時會回調此方法
            /*ws.onopen = function(){
                //...
            }*/

            //客戶端關閉或斷開連接時執行此方法
            /*ws.onclose = function(){
                //...
            }*/

             //接收服務端發送的消息
             ws.onmessage = function(message){
                $('#content').append(message.data + "<br>");
             }
             $('#loginDiv').css('display','none');
             $('#container').css('display','block');
        });
        //發送消息
        $('#send').on('click',function(){
            var msg = $('#msg').val();
            //發送消息
            ws.send(msg);
        });
    })
</script>
</body>
</html>
View Code

 

 


 

 二、spring+jetty實現

 項目結構:

 

pom 配置:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>edu.nf</groupId>
    <artifactId>spring-ws</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
        <!-- spring版本 -->
        <spring.version>5.1.1.RELEASE</spring.version>
        <servlet.version>4.0.1</servlet.version>
        <jackson.version>2.9.7</jackson.version>
    </properties>

    <!-- 添加依賴 -->
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
    </dependencies>

    <!-- war插件 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <warSourceDirectory>web</warSourceDirectory>
                    <!-- 指定web.xml路徑 -->
                    <webXml>web\WEB-INF\web.xml</webXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
View Code

web.xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:dispatcher-servlet.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
</web-app>
View Code

ServerEndpointHandler(服務端)

package edu.nf.demo.websocket;

import edu.nf.demo.entity.Users;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author wangl
 * @date 2018-12-06
 * websocket服務端
 */
public class ServerEndpointHandler extends TextWebSocketHandler {

    /**
     * 維護一個用戶列表(key存放用戶名,value存放每一個用戶的WebSocketSession)
     */
    private static Map<String, WebSocketSession> users = new ConcurrentHashMap<>();

    /**
     * 客戶端建立連接之后執行此方法(onOpen)
     * @param session 每當客戶端連接后,容器會為其創建一個Session對象,
     *                這個對象在Spring中就是WebSocketSession
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("客戶端建立了連接...");
        //獲取用戶名,getAttributes方法得到的是一個Map,
        //這個map里面存放了握手攔截器將HttpSession作用域拷貝過去的數據
        Users user = (Users)session.getAttributes().get("user");
        //將用戶的session保存到用戶列表中
        users.put(user.getUserName(), session);
    }

    /**
     * 每當客戶端發送消息時執行此方法(onmessage)
     * @param session
     * @param message TextMessage對象表示接收客戶端的文本消息對象,
     *                它的getPayload方法將獲取具體消息內容
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.out.println("接收客戶端消息..." + message.getPayload());
        //獲取用戶名
        Users sendUser = (Users)session.getAttributes().get("user");
        //群發消息
        for(String userName : users.keySet()){
            //重新構建一個TextMessage對象
            TextMessage newMessage = new TextMessage(sendUser.getUserName() + " : " + message.getPayload());
            //發送所有人
            users.get(userName).sendMessage(newMessage);
        }
    }

    /**
     * 哭護短關閉或斷開連接時執行此方法(onclose)
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.out.println("客戶端斷開連接...");
        session.close();
    }
}
View Code

UserController(請求控制類)

package edu.nf.demo.controller;

import edu.nf.demo.controller.vo.ResponseVO;
import edu.nf.demo.entity.Users;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

/**
 * @author wangl
 * @date 2018-12-06
 */
@RestController
public class UserController {

    @PostMapping("/userLogin")
    public ResponseVO login(Users user, HttpSession session){
        //執行用戶驗證
        //代碼省略.......
        //驗證成功后將用戶放入會話作用域
        session.setAttribute("user", user);
        ResponseVO vo = new ResponseVO();
        vo.setCode(HttpStatus.OK.value());
        vo.setData("index.html");
        return vo;
    }
}
View Code

index.html(登錄成功的聊天網頁)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="js/jquery-3.3.1.min.js"></script>
</head>
<body>
  <div id="content"></div>
  <input type="text" id="msg" name="msg"/>
  <input type="button" value="send"/>
<script>
    $(function () {
       var ws = new WebSocket('ws://localhost:8080/websocket');
       ws.onmessage = function (event) {
           $('#content').append(event.data + '<br>');
       }
       $(':button').on('click',function () {
           var msg = $('#msg').val();
           ws.send(msg);
       });

    })
</script>
</body>
</html>
View Code

運行結果:

 

 原理參照博客:https://www.cnblogs.com/best/p/5695570.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM