国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

基于WebSocket的簡易聊天室的基本實(shí)現(xiàn)梳理

這篇具有很好參考價(jià)值的文章主要介紹了基于WebSocket的簡易聊天室的基本實(shí)現(xiàn)梳理。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

一,前言

目前在很多網(wǎng)站為了實(shí)現(xiàn)推送技術(shù)所用的技術(shù)都是 Ajax 輪詢。輪詢是在特定的的時(shí)間間隔(如每1秒),由瀏覽器對服務(wù)器發(fā)出HTTP請求,然后由服務(wù)器返回最新的數(shù)據(jù)給客戶端的瀏覽器。HTTP 協(xié)議是一種無狀態(tài)的、無連接的、單向的應(yīng)用層協(xié)議。它采用了請求/響應(yīng)模型。通信請求只能由客戶端發(fā)起,服務(wù)端對請求做出應(yīng)答處理。

然而,這種通信模型有一個(gè)弊端: HTTP協(xié)議無法實(shí)現(xiàn)服務(wù)器主動(dòng)向客戶端發(fā)起消息。這種單向請求的特點(diǎn)注定了如果服務(wù)器有連續(xù)的狀態(tài)變化,客戶端要獲知就非常麻煩。大多數(shù)web 應(yīng)用程序?qū)⑼ㄟ^頻繁的異步AJAX請求實(shí)現(xiàn)長輪詢。輪詢的效率低,非常浪費(fèi)資源(因?yàn)楸仨毑煌_B接,或者 HTTP連接始終打開)。因此在這種情況下WebSocket應(yīng)運(yùn)而生。

二,WebSocket介紹

WebSocket協(xié)議是基于TCP的一種新的網(wǎng)絡(luò)協(xié)議,是 HTML5 開始提供的一種在單個(gè) TCP 連接上進(jìn)行全雙工通訊的協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工(full-duplex)通信--允許服務(wù)器主動(dòng)發(fā)送信息給客戶端,使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單。彌補(bǔ)了Http協(xié)議在持久通信能力上的不足。

WebSocket通信協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC6455,并被RFC7936所補(bǔ)充規(guī)范。

WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù)。在 WebSocket API 中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。

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

HTML5 定義的 WebSocket 協(xié)議,能更好的節(jié)省服務(wù)器資源和帶寬,并且能夠更實(shí)時(shí)地進(jìn)行通訊。

基于WebSocket的簡易聊天室的基本實(shí)現(xiàn)梳理

?瀏覽器通過 JavaScript 向服務(wù)器發(fā)出建立 WebSocket 連接的請求,連接建立以后,客戶端和服務(wù)器端就可以通過 TCP 連接直接交換數(shù)據(jù)。

當(dāng)你獲取 Web Socket 連接后,你可以通過 send() 方法來向服務(wù)器發(fā)送數(shù)據(jù),并通過onmessage 事件來接收服務(wù)器返回的數(shù)據(jù)。

1.1實(shí)現(xiàn)原理

在實(shí)現(xiàn)WebSocket連線過程中,需要通過瀏覽器發(fā)出WebSocket連線請求,然后服務(wù)器發(fā)出回應(yīng),這個(gè)過程通常稱為"握手" 。在 WebSocket API,瀏覽器和服務(wù)器只需要做一個(gè)握手的動(dòng)作,然后,瀏覽器和服務(wù)器之間就形成了一條快速通道。兩者之間就直接可以數(shù)據(jù)互相傳送。在此WebSocket 協(xié)議中,為我們實(shí)現(xiàn)即時(shí)服務(wù)帶來了兩大好處:

  1. Header:互相溝通的Header是很小的-大概只有 2 Bytes

  2. Server Push:服務(wù)器的推送,服務(wù)器不再被動(dòng)的接收到瀏覽器的請求之后才返回?cái)?shù)據(jù),而是在有新數(shù)據(jù)時(shí)就主動(dòng)推送給瀏覽器。

1.2 WebSocket協(xié)議

該協(xié)議有兩部分:握手和數(shù)據(jù)傳輸。握手是基于http協(xié)議的。

一個(gè)典型的WebSocket握手請求如下:

客戶端請求

GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

來自服務(wù)器的握手看起來像如下形式:

HTTP/1.1 101 switching Protocols
Upgrade: websocket
Connection: upgrade
Sec-websocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+XOo=
Sec-websocket-Extensions: permessage-def1ate

字段說明:

頭名稱 說明
Connection 必須設(shè)置 Upgrade,表示客戶端希望連接升級。
Upgrade 字段必須設(shè)置 Websocket,表示希望升級到 Websocket 協(xié)議。
sec-websocket-version 表示支持的 Websocket 版本。RFC6455 要求使用的版本是 13,之前草案的版本均應(yīng)當(dāng)棄用。
sec-websocket-Key 是隨機(jī)的字符串,服務(wù)器端會用這些數(shù)據(jù)來構(gòu)造出一個(gè) SHA-1 的信息摘要。把 “Sec-WebSocket-Key” 加上一個(gè)特殊字符串 “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后計(jì)算 SHA-1 摘要,之后進(jìn)行 BASE-64 編碼,將結(jié)果做為 “Sec-WebSocket-Accept” 頭的值,返回給客戶端。如此操作,可以盡量避免普通 HTTP 請求被誤認(rèn)為 Websocket 協(xié)議。
sec-websocket-Extensions 協(xié)議擴(kuò)展類型
Origin 字段是可選的,通常用來表示在瀏覽器中發(fā)起此 Websocket 連接所在的頁面,類似于 Referer。但是,與 Referer 不同的是,Origin 只包含了協(xié)議和主機(jī)名稱。

WebSocket 連接的過程是:

首先,客戶端發(fā)起http請求,經(jīng)過3次握手后,建立起TCP連接;http 請求里存放 WebSocket 支持的版本號等信息,如:Upgrade、Connection、WebSocket-Version等;

然后,服務(wù)器收到客戶端的握手請求后,同樣采用HTTP協(xié)議回饋數(shù)據(jù);

最后,客戶端收到連接成功的消息后,開始借助于TCP傳輸信道進(jìn)行全雙工通信。

三,WebSocket在B/S端的實(shí)現(xiàn)基礎(chǔ)

2.1客戶端(瀏覽器)實(shí)現(xiàn)

2.1.1 WebSocket對象

實(shí)現(xiàn)webSockets的 web瀏覽器將通過webSocket對象公開所有必需的客戶端功能(主要指支持 Html5的瀏覽器)。以下API用于創(chuàng)建websocket對象:

 ?var ws = new websocket(ur1);

參數(shù)url格式說明: ws : / /ip地址:端口號/資源名稱

2.1.2 WebSocket事件
事件 事件處理程序 描述
open websocket對象.onopen 連接建立時(shí)觸發(fā)
message websocket對象.onmessage 客戶端接收服務(wù)端數(shù)據(jù)時(shí)觸發(fā)
error websocket對象.onerror 通信發(fā)生錯(cuò)誤時(shí)觸發(fā)
close websocket對象.onclose 連接關(guān)閉時(shí)觸發(fā)
2.1.3WebSocket方法

WebSocket對象的相關(guān)方法:

方法 描述
send() 使用連接發(fā)送數(shù)據(jù),WebSocket.send() 方法將需要通過 WebSocket 鏈接傳輸至服務(wù)器的數(shù)據(jù)排入隊(duì)列,并根據(jù)所需要傳輸?shù)?data bytes 的大小來增加bufferedAmount的值。若數(shù)據(jù)無法傳輸(例如數(shù)據(jù)需要緩存而緩沖區(qū)已滿)時(shí),套接字會自行關(guān)閉。
close() WebSocket.close() 方法關(guān)閉 WebSocket 連接或連接嘗試(如果有的話)。如果連接已經(jīng)關(guān)閉,則此方法不執(zhí)行任何操作。
2.1.4客戶端實(shí)例

WebSocket 協(xié)議本質(zhì)上是一個(gè)基于 TCP 的協(xié)議。

為了建立一個(gè) WebSocket 連接,客戶端瀏覽器首先要向服務(wù)器發(fā)起一個(gè) HTTP 請求,這個(gè)請求和通常的 HTTP 請求不同,包含了一些附加頭信息,其中附加頭信息"Upgrade: WebSocket"表明這是一個(gè)申請協(xié)議升級的 HTTP 請求,服務(wù)器端解析這些附加的頭信息然后產(chǎn)生應(yīng)答信息返回給客戶端,客戶端和服務(wù)器端的 WebSocket 連接就建立起來了,雙方就可以通過這個(gè)連接通道自由的傳遞信息,并且這個(gè)連接會持續(xù)存在直到客戶端或者服務(wù)器端的某一方主動(dòng)的關(guān)閉連接?;趈s和html的基礎(chǔ)實(shí)現(xiàn)如下:

<!DOCTYPE HTML>
<html>
   <head>
   <meta charset="utf-8">
   <title>測試</title>
    
      <script type="text/javascript">
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               alert("您的瀏覽器支持 WebSocket!");
               
               // 打開一個(gè) web socket
               var ws = new WebSocket("ws://localhost:8099/chat");
                
               ws.onopen = function()
               {
                  // Web Socket 已連接上,使用 send() 方法發(fā)送數(shù)據(jù)
                  ws.send("發(fā)送數(shù)據(jù)");
                  alert("數(shù)據(jù)發(fā)送中...");
               };
                
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  alert("數(shù)據(jù)已接收...");
               };
                
               ws.onclose = function()
               { 
                  // 關(guān)閉 websocket
                  alert("連接已關(guān)閉..."); 
               };
            }
            
            else
            {
               // 瀏覽器不支持 WebSocket
               alert("您的瀏覽器不支持 WebSocket!");
            }
         }
      </script>
        
   </head>
   <body>
   
      <div id="sse">
         <a href="javascript:WebSocketTest()">運(yùn)行 WebSocket</a>
      </div>
   </body>
</html>

2.2 服務(wù)端實(shí)現(xiàn)

Tomcat的7.0.5 版本開始支持webSocket,并且實(shí)現(xiàn)了Java WebSocket規(guī)范(JSR356)。

Java websocket應(yīng)用由一系列的websocketEndpoint組成。Endpoint是一個(gè)java對象代表websocket鏈接的一端,對于服務(wù)端,我們可以視為處理具體websocket消息的接口,就像Servlet之與http請求一樣。

我們可以通過兩種方式定義Endpoint:

  • 第一種是編程式,即繼承類javax.websocket.Endpoint并實(shí)現(xiàn)其方法。

  • 第二種是注解式,即定義一個(gè)服務(wù)組件,并添@serverEndpoint相關(guān)注解。

Endpoint實(shí)例在webSocket握手時(shí)創(chuàng)建,并在客戶端與服務(wù)端鏈接過程中有效,最后在鏈接關(guān)閉時(shí)結(jié)束。在Endpoint接口中明確定義了與其生命周期相關(guān)的方法,規(guī)范實(shí)現(xiàn)者確保生命周期的各個(gè)階段調(diào)用實(shí)例的相關(guān)方法。生命周期方法如下:

方法 含義描述 注解
onClose 當(dāng)會話關(guān)閉時(shí)調(diào)用。 onclose
onOpen 當(dāng)開啟一個(gè)新的會話時(shí)調(diào)用,該方法是客戶端與服務(wù)端握手成功后調(diào)用的方法。 onopen
onError 當(dāng)連接過程中異常時(shí)調(diào)用。 onError

服務(wù)端如何接收客戶端發(fā)送的數(shù)據(jù)呢?

通過為 session添加MessageHandler消息處理器來接收消息,當(dāng)采用注解方式定義Endpoint時(shí),我們還可以通過@onMessage 注解指定接收消息的方法。

服務(wù)端如何推送數(shù)據(jù)給客戶端呢?

發(fā)送消息則由RemoteEndpoint 完成,其實(shí)例由session維護(hù),根據(jù)使用情況,我們可以通過session.getBasicRemote獲取同步消息發(fā)送的實(shí)例,然后調(diào)用其sendXxx ()方法就可以發(fā)送消息,可以通過session. getAsyncRemote 獲取異步消息發(fā)送實(shí)例。

服務(wù)端代碼示例:

//該注解用來指定一個(gè)URI,客戶端可以通過這個(gè)URI來連接到WebSocket。類似Servlet的注解mapping。無需在web.xml中配置。
  @ServerEndpoint("/webSocket/{id}")
  @Component("webSocket")
public class WebSocket {
 
              private static Logger logger = Logger.getLogger(WebSocket.class);
      //靜態(tài)變量,用來記錄當(dāng)前在線連接數(shù)。應(yīng)該把它設(shè)計(jì)成線程安全的。
              private static int onlineCount = 0;
      //與某個(gè)客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
              private Session session;
      //concurrent包的線程安全Map,用來存放每個(gè)客戶端對應(yīng)的MyWebSocket對象。若要實(shí)現(xiàn)服務(wù)端與單一客戶端通信的話,可以使用Map來存放,其中Key可以為用戶標(biāo)識
             private static ConcurrentMap<String, WebSocket> webSocketMap = new ConcurrentHashMap<>();
     private static ConcurrentMap<String, WebSocket> webSocketMapAdmin = new ConcurrentHashMap<>();
 
      public Session getSession() {
         return session;
     }
 
      public static WebSocket getWebSocket(String id) {
         return webSocketMap.get(id);
     }
 
      /**
       * 連接建立成功調(diào)用的方法
       *
       * @param session 可選的參數(shù)。session為與某個(gè)客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
       */
              @OnOpen
    public void onOpen(Session session, @PathParam("id") String id) {
          this.session = session;
          //String sessionId = session.getId();
          webSocketMap.put(id, this);     //加入map中
          if (id.contains("admin")) {// 后臺登陸用戶,加入list
                  webSocketMapAdmin.put(id, this);
              }
          addOnlineCount();           //在線數(shù)加1
          System.out.println("有新連接加入!當(dāng)前在線人數(shù)為" + getOnlineCount());
      }
 
              /**
        * 連接關(guān)閉調(diào)用的方法
        */
              @OnClose
      public void onClose(@PathParam("id") String id) {
         webSocketMap.remove(id);  //從map中刪除
         webSocketMapAdmin.remove(id);
         subOnlineCount();           //在線數(shù)減1
         System.out.println("有一連接關(guān)閉!當(dāng)前在線人數(shù)為" + getOnlineCount());
     }
 
      /**
       * 收到客戶端消息后調(diào)用的方法
       *
       * @param message 客戶端發(fā)送過來的消息
       * @param session 可選的參數(shù)
       */
             @OnMessage
      public static void onMessage(String message, Session session) {
                 //群發(fā)消息
                 if (webSocketMapAdmin.size() > 0) {
                for (WebSocket item : webSocketMapAdmin.values()) {
                        try {
                                //System.out.println(item.session.getId());
                                item.session.getBasicRemote().sendText(message);
                            } catch (IOException e) {
                                logger.error("IO異常");
                                continue;
                            }
                            }
                    }
        
             }
     /**
        * 發(fā)生錯(cuò)誤時(shí)調(diào)用
        *
        * @param session
        * @param error
        */
             @OnError
      public void onError(Session session, Throwable error) {
                 //System.out.println("發(fā)生錯(cuò)誤");
                 logger.error("發(fā)生錯(cuò)誤");
             }
}

四,WebSocket在聊天室功能中的實(shí)現(xiàn)

3.1服務(wù)端代碼

這里以前后端分離的SpringBoot項(xiàng)目集成聊天功能為例,首先需要在SpringBoot的項(xiàng)目中導(dǎo)入WebSocket依賴。

 ? ? ? ?<dependency>
 ? ? ? ? ? ?<groupId>org.springframework.boot</groupId>
 ? ? ? ? ? ?<artifactId>spring-boot-starter-websocket</artifactId>
 ? ? ? ?</dependency>

然后需要?jiǎng)?chuàng)建ServerEndpointExporter并注入Spring容器,如果想在使用內(nèi)嵌容器的Spring Boot應(yīng)用中使用@ServerEndpoint,我們需要聲明一個(gè)單獨(dú)的ServerEndpointExporter的Bean

@Bean
public ServerEndpointExporter serverEndpointExporter() {
 ?  return new ServerEndpointExporter();
}

該bean將使用底層的WebSocket容器注冊任何被@ServerEndpoint注解的beans。

創(chuàng)建一個(gè)WebSocket服務(wù),并通過@ServerEndpoint指明該類作為服務(wù)端點(diǎn),直接構(gòu)建請求接口即可,并在其中完成消息處理的業(yè)務(wù)。

package com.yy.util;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author young
 * Date 2023/6/7 12:34
 * Description: WebSocket服務(wù)
 */
@Component
@ServerEndpoint("/chat/{nickname}")
public class WebSocketServer {

    Logger log = LoggerFactory.getLogger(WebSocketServer.class);
    protected static final Map<String, Session> SESSION_MAP = new ConcurrentHashMap<>(10);
    
    @OnOpen
    public void onOpen(Session session, @PathParam("nickname") String username) {
        SESSION_MAP.put(username, session);
        log.info("有新用戶加入---》{},當(dāng)前在線人數(shù)為:{}", username, SESSION_MAP.size());
        JSONObject result = new JSONObject();
        JSONArray array = new JSONArray();
        result.set("users", array);
        SESSION_MAP.keySet().forEach(s -> {
            JSONObject object = new JSONObject();
            object.set("nickname", s);
            array.add(object);
        });
        log.info("存入的對象集合信息{}", Arrays.toString(result.values().toArray()));
        // 后臺發(fā)送消息給所有的客戶端
        sendAllMessage(JSONUtil.toJsonStr(result));  
    }

    /**     
     * 收到客戶端消息后調(diào)用的方法   
     * 后臺收到客戶端發(fā)送過來的消息    
     * onMessage 是一個(gè)消息的中轉(zhuǎn)站  
     * 接受 瀏覽器端 socket.send 發(fā)送過來的 json數(shù)據(jù)  
     * @param message 客戶端發(fā)送過來的消息   
     */
    @OnMessage
    public void onMessage(String message, Session session, @PathParam("nickname") String username) {
        log.info("服務(wù)端收到用戶{}的消息{}", username, message);
        JSONObject obj = JSONUtil.parseObj(message);
        // to表示發(fā)送給哪個(gè)用戶
        String toUser = obj.getStr("to");
        // 發(fā)送的消息文本  hello  
        String text = obj.getStr("text");
        // 根據(jù) to用戶名來獲取 session,再通過session發(fā)送消息文本
        Session toSession = SESSION_MAP.get(toUser);
        if (ObjectUtil.isNotEmpty(toSession)) {
            JSONObject object = new JSONObject();
            object.set("from", username);
            object.set("text", text);
            this.sendMsg(object.toString(), toSession);
            log.info("發(fā)送給{}的消息---》{}", toUser, object);
        } else {
            log.info("消息發(fā)送失敗,未找到用戶{}", toUser);
        }
    }
    @OnClose
    public void onClose(Session session, @PathParam("username") String username) {
        SESSION_MAP.remove(username);
        log.info("有一個(gè)鏈接關(guān)閉,移除{}的用戶session,當(dāng)前在線人數(shù)為:{}", username, SESSION_MAP.size());
    }
    @OnError
    public void onError(Session session, Throwable throwable) {
        log.info("消息發(fā)生錯(cuò)誤");
        throwable.printStackTrace();
    }

    private void sendMsg(String message, Session session) {
        try {
            session.getBasicRemote().sendText(message);
        } catch (Exception e) {
            log.error("服務(wù)端發(fā)送消息失敗", e);
        }
    }

    private void sendAllMessage(String message) {
        SESSION_MAP.values().forEach(s -> {
            try {
                s.getBasicRemote().sendText(message);
            } catch (IOException e) {
                log.error("服務(wù)端發(fā)送消息失敗", e);
            }
        });
    }
}

此時(shí)一個(gè)簡易的服務(wù)端就完成了??蛻舳税l(fā)送消息時(shí)綁定該接口就能完成消息交互了。

3.2客戶端代碼

客戶端這塊主要在前端處理當(dāng)前用戶與遠(yuǎn)程用戶聊天時(shí),通過創(chuàng)建WebSocekt實(shí)例與服務(wù)端交互實(shí)現(xiàn)消息的組裝傳遞與轉(zhuǎn)發(fā),從而達(dá)到用戶相互聊天的功能。這里頁面主要通過Vue和ElementPlus完成。大致前端代碼如下:

<template>
  <div class="ml20" style="padding: 10px; margin-bottom: 50px;">
    <el-row>
      <el-col :span="4">
        <el-card style="width: 300px; height: 300px; color: #333">
          <div style="padding-bottom: 10px; border-bottom: 1px solid #ccc">
            在線用戶<span style="font-size: 12px">(點(diǎn)擊聊天氣泡開始聊天)</span>
          </div>
          <div style="padding: 10px 0" v-for="user in users" :key="user.username">
            <span style="padding-bottom: 10px; border-bottom: 1px solid #48a6f3">{{ user.nickname }}</span>
            <el-icon color="#409EFC" class="no-inherit" style="margin-left: 10px;text-align: center; font-size: 16px; cursor: pointer"
                     @click="chatUser = user.nickname">
              <ChatLineRound />
            </el-icon>
            <span style="font-size: 12px;color: limegreen; margin-left: 5px" v-if="user.nickname === chatUser">聊天中...</span>
          </div>
        </el-card>
      </el-col>
      <el-col :span="20">
        <div
            style="width: 800px; margin: 0 auto; background-color: white;                    border-radius: 5px; box-shadow: 0 0 10px #ccc">
          <div style="text-align: center; line-height: 50px;">
            Web聊天室({{ chatUser }})
          </div>
          <div style="height: 300px; 
          overflow:auto;
          border-top: 1px solid #ccc" 
          v-html="content">
          </div>
          <div style="height: 200px">
            <textarea v-model="text"
                      placeholder="在此輸入信息……"
                      style="height: 120px;
                      width: -webkit-fill-available; 
                      padding: 20px; 
                      border: none; 
                      background-color: #f7f7fa;
                      border-top: 1px solid #ccc;   
                      border-bottom: 1px solid #ccc;
                      outline: none"
                      @keyup.enter="send"
                       >
            </textarea>
            <div style="text-align: right; padding-right: 10px">
              <el-button type="primary" size="small" @click="send">發(fā)送</el-button>
            </div>
          </div>
        </div>
      </el-col>
    </el-row>
    <div class="fixed3">
      <a href="#"><img src="../../assets/kefu.png" style="border:5px solid #0f99e9;border-radius: 20%;" alt=""/></a>
    </div>
  </div>
</template>

<script>
import {mixin} from "../../mixins/index";
import { ChatLineRound} from "@element-plus/icons-vue";
let socket;
export default {
  name: "ChatHome",
  mixins:[mixin],
  components:{
    ChatLineRound
  },
  data() {
    return {
      circleUrl: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
      user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {},
      isCollapse: false,
      users: [],
      chatUser: '',
      text: "",
      messages: [],
      content: ''
    }
  },
  created() {
    this.init()
  },
  methods: {
    send() {
      if (!this.chatUser) {
        this.$message({type: 'warning', message: "請選擇聊天對象"})
        return;
      }
      if (!this.text) {
        this.$message({type: 'warning', message: "請輸入內(nèi)容"})
      } else {
        if (typeof (WebSocket) == "undefined") {
          console.log("您的瀏覽器不支持WebSocket");
        } else {
          console.log("您的瀏覽器支持WebSocket");
          // 組裝待發(fā)送的消息 json          
          // {"from": "張三", "to": "李四", "text": "聊天文本"}          
          let message = {from: this.user.nickname, to: this.chatUser, text: this.text}
          socket.send(JSON.stringify(message));
          // 將組裝好的json發(fā)送給服務(wù)端,由服務(wù)端進(jìn)行轉(zhuǎn)發(fā)          
          this.messages.push({user: this.user.nickname, text: this.text})
          // 構(gòu)建消息內(nèi)容,本人消息       
          this.createContent(null, this.user.nickname, this.text)
          this.text = '';
        }
      }
    },
    createContent(remoteUser, nowUser, text) {
      // 這個(gè)方法是用來將 json的聊天消息數(shù)據(jù)轉(zhuǎn)換成 html的。   
      let html
// 當(dāng)前用戶消息     
      if (nowUser) {
        // nowUser 表示是否顯示當(dāng)前用戶發(fā)送的聊天消息,綠色氣泡   
        html = "<div class=\"el-row\" style=\"padding: 5px 0;\">\n" +
            "  <div class=\"el-col el-col-22\" style=\"text-align: right;margin-top: auto;margin-bottom: auto; padding-right: 10px\">\n" 
            + "    <div class=\"tip left\" style=\" width: auto;\n" +
            "  height: 30px;\n" +
            "  background: #48a6f3;\n" +
            "  padding: 5px 20px;\n" +
            "  margin: 4px;\n" +
            "  line-height: 30px;\n" +
            "  font-size: 14px;\n" +
            "  border-radius: 10px;\n" +
            "  margin-left: 10px;\n" +
            "  position: relative;\n" +
            "  float: right;\">"
            + text 
            + "</div>\n" 
            + "  </div>\n" 
            + "  <div class=\"el-col el-col-2\" style=\"text-align: left;margin-top: auto;padding-left: 10px;\">\n" 
            + "  <span class=\"el-avatar el-avatar--circle\" style=\"height: 40px; width: 40px; line-height: 40px;\">\n" 
            + "    <img :src=\"http://img.mp.itc.cn/upload/20161123/2fb03a6584f24901acc5e02d19ece787_th.jpeg\" style=\"object-fit: cover;\">\n" 
            + "  </span>\n" + "  </div>\n" + "</div>";
      } else if (remoteUser) {
        // remoteUser表示遠(yuǎn)程用戶聊天消息,藍(lán)色的氣泡 
        html = "<div class=\"el-row\" style=\"padding: 5px 0;\">\n" 
            + "  <div class=\"el-col el-col-2\" style=\"text-align: right;margin-top: auto;\">\n" 
            + "  <span class=\"el-avatar el-avatar--circle\" style=\"height: 40px; width: 40px; line-height: 40px;\">\n" 
            + "    <img src=\"http://img.mp.itc.cn/upload/20161123/2fb03a6584f24901acc5e02d19ece787_th.jpeg\" style=\"object-fit: cover;\">\n" 
            + "  </span>\n"
            + "  </div>\n" 
            + "  <div class=\"el-col el-col-22\" style=\"text-align: left; padding-left: 10px;margin-top: auto;margin-bottom: auto;\">\n"
            + "    <div class=\"tip right\" style=\"width: auto;\n" +
            "  height: 30px;\n" +
            "  background: #eeeeee;\n" +
            "  padding: 5px 20px;\n" +
            "  margin: 4px;\n" +
            "  line-height: 30px;\n" +
            "  font-size: 14px;\n" +
            "  border-radius: 10px;\n" +
            "  margin-left: 10px;\n" +
            "  position: relative;\n" +
            "  float: left;\">"
            + text 
            + "</div>\n" 
            + "  </div>\n"
            + "</div>";
      }
      console.log(html)
      this.content += html;
    },
    init() {
      let nickname = this.user.nickname;
      let _this = this;
      if (typeof (WebSocket) == "undefined") {
        console.log("您的瀏覽器不支持WebSocket");
      } else {
        console.log("您的瀏覽器支持WebSocket");
        let socketUrl = "ws://localhost:8084/chat/" + nickname;
        if (socket != null) {
          socket.close();
          socket = null;
        }
        // 開啟一個(gè)websocket服務(wù)   
        socket = new WebSocket(socketUrl);
        //打開事件      
        socket.onopen = function () {
          console.log("websocket已打開");
        };
        //  瀏覽器端收消息,獲得從服務(wù)端發(fā)送過來的文本消息 
        socket.onmessage = function (message) {
          console.log("收到數(shù)據(jù)====" + message.data)
          let chatData = JSON.parse(message.data)
         // 對收到的json數(shù)據(jù)進(jìn)行解析, 類似這樣的: {"users": [{"username": "張三"},{ "username": "李四"}]}  
          if (chatData.users) {
            // 獲取在線人員信息,并且排除自身,自己不會出現(xiàn)在自己的聊天列表里
            _this.users = chatData.users.filter(user => user.nickname !== nickname)
          } else {
            // 如果服務(wù)器端發(fā)送過來的json數(shù)據(jù) 不包含 users 這個(gè)key,那么發(fā)送過來的就是聊天文本json數(shù)據(jù)            
            // {"from": "張三", "text": "hello"}     
            if (chatData.from === _this.chatUser) {
              _this.messages.push(chatData)
            // 構(gòu)建消息內(nèi)容          
              _this.createContent(chatData.from, null, chatData.text)
            }
          }
        };
        //關(guān)閉事件    
        socket.onclose = function () {
          console.log("websocket已關(guān)閉");
        };
        //發(fā)生了錯(cuò)誤事件    
        socket.onerror = function () {
          console.log("websocket發(fā)生了錯(cuò)誤");
        }
      }
    }
  }
}
</script>

<style scoped>
.tip {
  color: white;
  text-align: center;
  border-radius: 10px;
  font-family: sans-serif;
  padding: 10px;
  width: auto;
  display: inline-block !important;
  display: inline;
}
.fixed3{
  position: absolute;
  right: 14px;
  top: 400px;
}
.right {
  background-color: deepskyblue;
}

.left {
  background-color: forestgreen;
}
</style>

然后就可以進(jìn)行簡單測試了。

3.3聊天功能測試

打開兩個(gè)不同的瀏覽器,并登錄兩個(gè)用戶進(jìn)入到聊天室界面。這里需要注意的是:因?yàn)楣P者這里用戶使用的是相同的WebSocket連接,因此在同一個(gè)瀏覽器中登錄多個(gè)用戶,可能會出現(xiàn)信息被覆蓋的問題。

因?yàn)閃ebSocket是基于長連接的通信協(xié)議,而同一個(gè)瀏覽器內(nèi)的WebSocket連接是共享的,多個(gè)用戶在同一個(gè)瀏覽器中使用相同的WebSocket連接進(jìn)行通信,導(dǎo)致消息會被混淆。所以需要在兩個(gè)不同瀏覽器中才看得到實(shí)現(xiàn)效果。

如果你的用戶采用了不同的連接方式,或者建立不同的獨(dú)立的WebSocket連接,就沒這回事了。

測試效果如下:A用戶聊天界面:

基于WebSocket的簡易聊天室的基本實(shí)現(xiàn)梳理

?B用戶聊天界面如下:

基于WebSocket的簡易聊天室的基本實(shí)現(xiàn)梳理

?至此一個(gè)簡單聊天室就實(shí)現(xiàn)了,但是這里面的消息記錄都是與原生html拼接的,因此頭像,用戶名并沒有與用戶真實(shí)信息連同展示,并且還存在許多不足之處:

首先,因?yàn)橛玫亩际且粋€(gè)WebSocket連接,因此用戶只有在不同瀏覽器中登錄才能實(shí)現(xiàn)該功能,否則就會出現(xiàn)信息覆蓋。

其次,當(dāng)用戶數(shù)>2時(shí),聊天區(qū)消息記錄并沒有及時(shí)清除,也就是說,該模式更像一個(gè)群聊。一對一與一對多的聊天功能實(shí)現(xiàn)可參考如下方式:

實(shí)現(xiàn)一對一聊天功能:

  • 當(dāng)一個(gè)客戶端建立連接時(shí),服務(wù)器為每個(gè)客戶端分配一個(gè)唯一的標(biāo)識符。

  • 當(dāng)客戶端發(fā)送消息時(shí),服務(wù)器將消息標(biāo)識符和消息內(nèi)容保存下來,并通過WebSocket服務(wù)器將消息發(fā)送給特定的目標(biāo)客戶端。

實(shí)現(xiàn)一對多聊天功能:

  • 當(dāng)一個(gè)客戶端建立連接時(shí),服務(wù)器為每個(gè)客戶端分配一個(gè)唯一的標(biāo)識符。

  • 當(dāng)客戶端發(fā)送消息時(shí),服務(wù)器將消息標(biāo)識符和消息內(nèi)容保存下來,并通過WebSocket服務(wù)器將消息發(fā)送給所有連接的客戶端。

另外,聊天消息并未持久化,也就是說,當(dāng)頁面重新打開時(shí)消息記錄就被清空了,因?yàn)閃ebSockset斷連了,相應(yīng)的記錄也就沒有了。如果需要消息記錄保存的話,仍然需要數(shù)據(jù)庫參與其中。

最后,消息記錄采用html拼接形式展示出來的,因此無論是聊天用戶頭像還是昵稱都并未與用戶本身信息綁定,比較呆板。

留給大家一些思考~

五,總結(jié)

WebSocket 是為了在 web 應(yīng)用上進(jìn)行雙通道通信而產(chǎn)生的協(xié)議,相比于輪詢HTTP請求的方式,WebSocket 有節(jié)省服務(wù)器資源,效率高等優(yōu)點(diǎn)。

WebSocket 中 Sec-WebSocket-Key 的生成算法是拼接服務(wù)端和客戶端生成的字符串,進(jìn)行SHA1哈希算法,再用base64編碼。

WebSocket 協(xié)議握手是依靠 HTTP 協(xié)議的,依靠于 HTTP 響應(yīng)101進(jìn)行協(xié)議升級轉(zhuǎn)換。

它的優(yōu)缺點(diǎn)在于:

  • 優(yōu)點(diǎn):WebSocket協(xié)議一旦建議后,互相溝通所消耗的請求頭是很小的服務(wù)器可以向客戶端推送消息了

  • 缺點(diǎn):少部分瀏覽器不支持,瀏覽器支持的程度與方式有區(qū)別(IE10)

應(yīng)用場景如下:

  • 即時(shí)聊天通信

  • 多玩家游戲

  • 在線協(xié)同編輯/編輯

  • 實(shí)時(shí)數(shù)據(jù)流的拉取與推送

  • 體育/游戲?qū)崨r

  • 實(shí)時(shí)地圖位置

  • 即時(shí)Web應(yīng)用程序:即時(shí)Web應(yīng)用程序使用一個(gè)Web套接字在客戶端顯示數(shù)據(jù),這些數(shù)據(jù)由后端服務(wù)器連續(xù)發(fā)送。在WebSocket中,數(shù)據(jù)被連續(xù)推送/傳輸?shù)揭呀?jīng)打開的同一連接中,這就是為什么WebSocket更快并提高了應(yīng)用程序性能的原因。

  • 游戲應(yīng)用程序:在游戲應(yīng)用程序中,你可能會注意到,服務(wù)器會持續(xù)接收數(shù)據(jù),而不會刷新用戶界面。屏幕上的用戶界面會自動(dòng)刷新,而且不需要建立新的連接,因此在WebSocket游戲應(yīng)用程序中非常有幫助。

  • 聊天應(yīng)用程序:聊天應(yīng)用程序僅使用WebSocket建立一次連接,便能在訂閱戶之間交換,發(fā)布和廣播消息。它重復(fù)使用相同的WebSocket連接,用于發(fā)送和接收消息以及一對一的消息傳輸。文章來源地址http://www.zghlxwxcb.cn/news/detail-510218.html

到了這里,關(guān)于基于WebSocket的簡易聊天室的基本實(shí)現(xiàn)梳理的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【W(wǎng)ebSocket&IndexedDB】node+WebSocket&IndexedDB開發(fā)簡易聊天室

    【W(wǎng)ebSocket&IndexedDB】node+WebSocket&IndexedDB開發(fā)簡易聊天室

    序幕介紹: WebSocket 是 HTML5 開始提供的一種在單個(gè) TCP 連接上進(jìn)行全雙工通訊的協(xié)議。 講人話就是說: WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,在 WebSocket API 中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)

    2024年02月10日
    瀏覽(47)
  • Vue+Nodejs 使用WebSocket創(chuàng)建一個(gè)簡易聊天室

    Vue+Nodejs 使用WebSocket創(chuàng)建一個(gè)簡易聊天室

    使用vue編寫前端頁面,nodejs處理服務(wù)端消息,WebSocket進(jìn)行實(shí)時(shí)通信 1.客戶端 2. 服務(wù)端 使用的是nodejs

    2024年02月16日
    瀏覽(25)
  • 基于 SpringBoot+WebSocket 無DB實(shí)現(xiàn)在線聊天室(附源碼)

    基于 SpringBoot+WebSocket 無DB實(shí)現(xiàn)在線聊天室(附源碼)

    0.1 樣例展示 0.2 源碼地址 GitHub:https://github.com/ShiJieCloud/web-chat Gitee:https://gitee.com/suitbaby/web-chat GitCode:I’m Jie / web-chat · GitCode 1.1 HTTP 常用的 HTTP 協(xié)議是一種無狀態(tài)的、無連接的、單向的應(yīng)用層協(xié)議。它采用了請求/響應(yīng)模型。通信請求只能由客戶端發(fā)起,服務(wù)端對請求做出

    2024年02月05日
    瀏覽(17)
  • Qt實(shí)現(xiàn)簡易聊天室

    Qt實(shí)現(xiàn)簡易聊天室

    目錄 一、界面展示(界面用ui 設(shè)計(jì)) ?群成員展示界面( denglu) ? ?聊天界面展示( widget ) 二、代碼展示 (所有代碼非原創(chuàng)) ?denglu.h和widget.h ?denglu.cpp、main.cpp、widget.cpp 三、軟件制作 ?

    2024年02月08日
    瀏覽(26)
  • 用Java實(shí)現(xiàn)簡易聊天室

    用Java實(shí)現(xiàn)簡易聊天室

    ? 說明:如果一個(gè)?類,需要有界面的顯示,那么該類就需要繼承自JFrame,此時(shí),該類就可以被稱為一個(gè)“窗體類\\\"。 ? 服務(wù)端代碼: 客戶端代碼: ?啟動(dòng)時(shí),必須先啟動(dòng)服務(wù)端,再啟動(dòng)客戶端。 最終效果: ? ? ?

    2024年02月11日
    瀏覽(19)
  • 基于WebSocket的在線文字聊天室

    基于WebSocket的在線文字聊天室

    與Ajax不同,WebSocket可以使服務(wù)端主動(dòng)向客戶發(fā)送響應(yīng),本案例就是基于WebSocket的一個(gè)在線聊天室,不過功能比較簡單,只能滿足文字交流。演示如下。 案例學(xué)習(xí)于b站up主,鏈接?。這位up主講的非常清楚,值得去學(xué)習(xí)。本文屬于記錄自我學(xué)習(xí)過程的文章。 項(xiàng)目目錄下app.js 項(xiàng)

    2024年02月13日
    瀏覽(34)
  • LinuxC TCP實(shí)現(xiàn)簡易聊天室

    LinuxC TCP實(shí)現(xiàn)簡易聊天室

    目錄 1.概述 1.1聊天室設(shè)計(jì)內(nèi)容 2.系統(tǒng)設(shè)計(jì) 2.1系統(tǒng)功能設(shè)計(jì) 2.1.1用戶管理 2.1.2聊天室管理 2.1.3聊天管理 2.2系統(tǒng)數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì) 2.3系統(tǒng)主要函數(shù)設(shè)計(jì) 3.系統(tǒng)實(shí)現(xiàn) 3.2功能模塊的程序流程圖及運(yùn)行界面 3.2.1功能模塊流程圖 ?3.2.2運(yùn)行界面 4.源代碼 4.1客戶端 4.2服務(wù)器 注:存在問題 1

    2024年02月09日
    瀏覽(19)
  • 【項(xiàng)目設(shè)計(jì)】基于WebSocket的Web聊天室

    【項(xiàng)目設(shè)計(jì)】基于WebSocket的Web聊天室

    本項(xiàng)目的名稱為Web聊天室,即類QQ群組聊天,多個(gè)用戶可以在同一個(gè)群組收發(fā)消息進(jìn)行聊天 項(xiàng)目實(shí)現(xiàn)的業(yè)務(wù) 注冊功能:用戶輸入賬號,密碼,昵稱,圖像點(diǎn)擊即可注冊用戶(賬號和昵稱不能重復(fù)) 登陸功能:用戶輸入賬號,密碼即可進(jìn)行登陸(如果登陸的賬號已在別處登陸

    2023年04月18日
    瀏覽(100)
  • Django實(shí)現(xiàn)websocket聊天室

    WebSocket協(xié)議是基于TCP的一種新的網(wǎng)絡(luò)協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器雙向通信,即允許服務(wù)器主動(dòng)發(fā)送信息給客戶端。因此,在WebSocket中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸,客戶端和服務(wù)器之間的數(shù)據(jù)交換變

    2023年04月23日
    瀏覽(92)
  • 基于SpringBoot+Vue+WebSocket的在線聊天室

    基于SpringBoot+Vue+WebSocket的在線聊天室

    WebSocket 是一種在 Web 應(yīng)用程序中實(shí)現(xiàn)雙向通信的協(xié)議。它提供了一種持久連接,允許客戶端和服務(wù)器之間進(jìn)行實(shí)時(shí)數(shù)據(jù)傳輸,而無需進(jìn)行頻繁的請求和響應(yīng)。 相對于傳統(tǒng)的 HTTP 請求-響應(yīng)模式,WebSocket 在客戶端和服務(wù)器之間建立起一條長久的雙向通信通道。這意味著服務(wù)器可

    2024年01月16日
    瀏覽(41)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包