整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          RabbitMQ實現即時通訊居然如此簡單

          時候我們的項目中會用到即時通訊功能,比如電商系統中的客服聊天功能,還有在支付過程中,當用戶支付成功后,第三方支付服務會回調我們的回調接口,此時我們需要通知前端支付成功。最近發現RabbitMQ可以很方便的實現即時通訊功能,如果你沒有特殊的業務需求,甚至可以不寫后端代碼,今天給大家講講如何使用RabbitMQ來實現即時通訊!

          SpringBoot實戰電商項目mall(40k+star)地址:https://github.com/macrozheng/mall

          轉自:https://www.jianshu.com/p/e132261456b5

          參考:go語言中文文檔:www.topgoer.com

          MQTT協議

          MQTT(Message Queuing Telemetry Transport,消息隊列遙測傳輸協議),是一種基于發布/訂閱(publish/subscribe)模式的輕量級通訊協議,該協議構建于TCP/IP協議上。MQTT最大優點在于,可以以極少的代碼和有限的帶寬,為連接遠程設備提供實時可靠的消息服務。

          MQTT相關概念

          • Publisher(發布者):消息的發出者,負責發送消息。
          • Subscriber(訂閱者):消息的訂閱者,負責接收并處理消息。
          • Broker(代理):消息代理,位于消息發布者和訂閱者之間,各類支持MQTT協議的消息中間件都可以充當。
          • Topic(主題):可以理解為消息隊列中的路由,訂閱者訂閱了主題之后,就可以收到發送到該主題的消息。
          • Payload(負載);可以理解為發送消息的內容。
          • QoS(消息質量):全稱Quality of Service,即消息的發送質量,主要有QoS 0、QoS 1、QoS 2三個等級,下面分別介紹下: QoS 0(Almost Once):至多一次,只發送一次,會發生消息丟失或重復; QoS 1(Atleast Once):至少一次,確保消息到達,但消息重復可能會發生; QoS 2(Exactly Once):只有一次,確保消息只到達一次。

          RabbitMQ啟用MQTT功能

          RabbitMQ啟用MQTT功能,需要先安裝然RabbitMQ然后再啟用MQTT插件。

          • 首先我們需要安裝并啟動RabbitMQ,對RabbitMQ不了解的朋友可以參考《花了3天總結的RabbitMQ實用技巧,有點東西!》;
          • 接下來就是啟用RabbitMQ的MQTT插件了,默認是不啟用的,使用如下命令開啟即可;
          rabbitmq-plugins enable rabbitmq_mqtt
          
          • 開啟成功后,查看管理控制臺,我們可以發現MQTT服務運行在1883端口上了。

          MQTT客戶端

          我們可以使用MQTT客戶端來測試MQTT的即時通訊功能,這里使用的是MQTTBox這個客戶端工具。

          • 首先下載并安裝好MQTTBox,下載地址:http://workswithweb.com/mqttbox.html

          • 點擊Create MQTT Client按鈕來創建一個MQTT客戶端;

          • 接下來對MQTT客戶端進行配置,主要是配置好協議端口、連接用戶名密碼和QoS即可;

          • 再配置一個訂閱者,訂閱者訂閱testTopicA這個主題,我們會向這個主題發送消息;

          • 發布者向主題中發布消息,訂閱者可以實時接收到。

          前端直接實現即時通訊

          既然MQTTBox客戶端可以直接通過RabbitMQ實現即時通訊,那我們是不是直接使用前端技術也可以實現即時通訊?答案是肯定的!下面我們將通過html+javascript實現一個簡單的聊天功能,真正不寫一行后端代碼實現即時通訊!

          • 由于RabbitMQ與Web端交互底層使用的是WebSocket,所以我們需要開啟RabbitMQ的MQTT WEB支持,使用如下命令開啟即可;
          rabbitmq-plugins enable rabbitmq_web_mqtt
          
          • 開啟成功后,查看管理控制臺,我們可以發現MQTT的WEB服務運行在15675端口上了;

          • WEB端與MQTT服務進行通訊需要使用一個叫MQTT.js的庫,項目地址:https://github.com/mqttjs/MQTT.js

          • 實現的功能非常簡單,一個單聊功能,需要注意的是配置好MQTT服務的訪問地址為:ws://localhost:15675/ws
          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
          </head>
          <body>
          <div>
              <label>目標Topic:<input id="targetTopicInput" type="text"></label><br>
              <label>發送消息:<input id="messageInput" type="text"></label><br>
              <button onclick="sendMessage()">發送</button>
              <button onclick="clearMessage()">清空</button>
              <div id="messageDiv"></div>
          </div>
          </body>
          <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
          <script>
              //RabbitMQ的web-mqtt連接地址
              const url = 'ws://localhost:15675/ws';
              //獲取訂閱的topic
              const topic = getQueryString("topic");
              //連接到消息隊列
              let client = mqtt.connect(url);
              client.on('connect', function () {
                  //連接成功后訂閱topic
                  client.subscribe(topic, function (err) {
                      if (!err) {
                          showMessage("訂閱topic:" + topic + "成功!");
                      }
                  });
              });
              //獲取訂閱topic中的消息
              client.on('message', function (topic, message) {
                  showMessage("收到消息:" + message.toString());
              });
          
              //發送消息
              function sendMessage() {
                  let targetTopic = document.getElementById("targetTopicInput").value;
                  let message = document.getElementById("messageInput").value;
                  //向目標topic中發送消息
                  client.publish(targetTopic, message);
                  showMessage("發送消息給" + targetTopic + "的消息:" + message);
              }
          
              //從URL中獲取參數
              function getQueryString(name) {
                  let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
                  let r = window.location.search.substr(1).match(reg);
                  if (r != null) {
                      return decodeURIComponent(r[2]);
                  }
                  return null;
              }
          
              //在消息列表中展示消息
              function showMessage(message) {
                  let messageDiv = document.getElementById("messageDiv");
                  let messageEle = document.createElement("div");
                  messageEle.innerText = message;
                  messageDiv.appendChild(messageEle);
              }
          
              //清空消息列表
              function clearMessage() {
                  let messageDiv = document.getElementById("messageDiv");
                  messageDiv.innerHTML = "";
              }
          </script>
          </html>
          
          • 接下來我們訂閱不同的主題開啟兩個頁面測試下功能(頁面放在了SpringBoot應用的resource目錄下了,需要先啟動應用再訪問): 第一個訂閱主題testTopicA,訪問地址:http://localhost:8088/page/index?topic=testTopicA 第二個訂閱主題testTopicB,訪問地址:http://localhost:8088/page/index?topic=testTopicB
          • 之后互相發送消息,讓我們來看看效果吧!

          在SpringBoot中使用

          沒有特殊業務需求的時候,前端可以直接和RabbitMQ對接實現即時通訊。但是有時候我們需要通過服務端去通知前端,此時就需要在應用中集成MQTT了,接下來我們來講講如何在SpringBoot應用中使用MQTT。

          • 首先我們需要在pom.xml中添加MQTT相關依賴;
          <!--Spring集成MQTT-->
          <dependency>
              <groupId>org.springframework.integration</groupId>
              <artifactId>spring-integration-mqtt</artifactId>
          </dependency>
          
          • 在application.yml中添加MQTT相關配置,主要是訪問地址、用戶名密碼、默認主題信息;
          rabbitmq:
            mqtt:
              url: tcp://localhost:1883
              username: guest
              password: guest
              defaultTopic: testTopic
          
          • 編寫一個Java配置類從配置文件中讀取配置便于使用;
          /**
           * MQTT相關配置
           * Created by macro on 2020/9/15.
           */
          @Data
          @EqualsAndHashCode(callSuper = false)
          @Component
          @ConfigurationProperties(prefix = "rabbitmq.mqtt")
          public class MqttConfig {
              /**
               * RabbitMQ連接用戶名
               */
              private String username;
              /**
               * RabbitMQ連接密碼
               */
              private String password;
              /**
               * RabbitMQ的MQTT默認topic
               */
              private String defaultTopic;
              /**
               * RabbitMQ的MQTT連接地址
               */
              private String url;
          }
          
          • 添加MQTT消息訂閱者相關配置,使用@ServiceActivator注解聲明一個服務激活器,通過MessageHandler來處理訂閱消息;
          /**
           * MQTT消息訂閱者相關配置
           * Created by macro on 2020/9/15.
           */
          @Slf4j
          @Configuration
          public class MqttInboundConfig {
              @Autowired
              private MqttConfig mqttConfig;
          
              @Bean
              public MessageChannel mqttInputChannel() {
                  return new DirectChannel();
              }
          
              @Bean
              public MessageProducer inbound() {
                  MqttPahoMessageDrivenChannelAdapter adapter =
                          new MqttPahoMessageDrivenChannelAdapter(mqttConfig.getUrl(), "subscriberClient",
                                  mqttConfig.getDefaultTopic());
                  adapter.setCompletionTimeout(5000);
                  adapter.setConverter(new DefaultPahoMessageConverter());
                  //設置消息質量:0->至多一次;1->至少一次;2->只有一次
                  adapter.setQos(1);
                  adapter.setOutputChannel(mqttInputChannel());
                  return adapter;
              }
          
              @Bean
              @ServiceActivator(inputChannel = "mqttInputChannel")
              public MessageHandler handler() {
                  return new MessageHandler() {
          
                      @Override
                      public void handleMessage(Message<?> message) throws MessagingException {
                          //處理訂閱消息
                          log.info("handleMessage : {}",message.getPayload());
                      }
          
                  };
              }
          }
          
          • 添加MQTT消息發布者相關配置;
          /**
           * MQTT消息發布者相關配置
           * Created by macro on 2020/9/15.
           */
          @Configuration
          public class MqttOutboundConfig {
          
              @Autowired
              private MqttConfig mqttConfig;
          
              @Bean
              public MqttPahoClientFactory mqttClientFactory() {
                  DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
                  MqttConnectOptions options = new MqttConnectOptions();
                  options.setServerURIs(new String[] { mqttConfig.getUrl()});
                  options.setUserName(mqttConfig.getUsername());
                  options.setPassword(mqttConfig.getPassword().toCharArray());
                  factory.setConnectionOptions(options);
                  return factory;
              }
          
              @Bean
              @ServiceActivator(inputChannel = "mqttOutboundChannel")
              public MessageHandler mqttOutbound() {
                  MqttPahoMessageHandler messageHandler =
                          new MqttPahoMessageHandler("publisherClient", mqttClientFactory());
                  messageHandler.setAsync(true);
                  messageHandler.setDefaultTopic(mqttConfig.getDefaultTopic());
                  return messageHandler;
              }
          
              @Bean
              public MessageChannel mqttOutboundChannel() {
                  return new DirectChannel();
              }
          }
          
          • 添加MQTT網關,用于向主題中發送消息;
          /**
           * MQTT網關,通過接口將數據傳遞到集成流
           * Created by macro on 2020/9/15.
           */
          @Component
          @MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
          public interface MqttGateway {
              /**
               * 發送消息到默認topic
               */
              void sendToMqtt(String payload);
          
              /**
               * 發送消息到指定topic
               */
              void sendToMqtt(String payload, @Header(MqttHeaders.TOPIC) String topic);
          
              /**
               * 發送消息到指定topic并設置QOS
               */
              void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
          }
          
          • 添加MQTT測試接口,使用MQTT網關向特定主題中發送消息;
          /**
           * MQTT測試接口
           * Created by macro on 2020/9/15.
           */
          @Api(tags = "MqttController", description = "MQTT測試接口")
          @RestController
          @RequestMapping("/mqtt")
          public class MqttController {
          
              @Autowired
              private MqttGateway mqttGateway;
          
              @PostMapping("/sendToDefaultTopic")
              @ApiOperation("向默認主題發送消息")
              public CommonResult sendToDefaultTopic(String payload) {
                  mqttGateway.sendToMqtt(payload);
                  return CommonResult.success(null);
              }
          
              @PostMapping("/sendToTopic")
              @ApiOperation("向指定主題發送消息")
              public CommonResult sendToTopic(String payload, String topic) {
                  mqttGateway.sendToMqtt(payload, topic);
                  return CommonResult.success(null);
              }
          }
          
          • 調用接口向主題中發送消息進行測試;

          • 后臺成功接收到消息并進行打印。
          2020-09-17 14:29:01.689  INFO 11192 --- [ubscriberClient] c.m.mall.tiny.config.MqttInboundConfig   : handleMessage : 來自網頁上的消息
          2020-09-17 14:29:06.101  INFO 11192 --- [ubscriberClient] c.m.mall.tiny.config.MqttInboundConfig   : handleMessage : 來自網頁上的消息
          2020-09-17 14:29:07.384  INFO 11192 --- [ubscriberClient] c.m.mall.tiny.config.MqttInboundConfig   : handleMessage : 來自網頁上的消息
          

          總結

          消息中間件應用越來越廣泛,不僅可以實現可靠的異步通信,還可以實現即時通訊,掌握一個消息中間件還是很有必要的。如果沒有特殊業務需求,客戶端或者前端直接使用MQTT對接消息中間件即可實現即時通訊,有特殊需求的時候也可以使用SpringBoot集成MQTT的方式來實現,總之消息中間件是實現即時通訊的一個好選擇!

          項目源碼地址

          https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-mqtt

          用php+swoole+redis 簡單實現網頁即時聊天,需要瀏覽器支持html5的websocket,

          websocket是不同于http的另外一種網絡通信協議,能夠進行雙向通信,基于此,可開發出各種實時通信產品,簡單做了個聊天demo,順便分享一下

          效果圖如下:

          環境:

          • 系統 centos7.5
          • php7.2.9
          • redis5.0.0
          • swoole4.2.2
          • nginx 1.8

          參考文檔:

          • redis官網 https://redis.io
          • 教程 http://www.runoob.com/redis/
          • swoole 官網 https://swoole.com
          • swoole 的webSocket手冊:https://wiki.swoole.com/wiki/page/397.html
          • php擴展庫地址 http://pecl.php.net/

          IP與端口:

          • 虛擬機的IP: 192.168.1.100
          • webSocket服務端口是 9520
          • redis服務端口是 6379

          服務器端代碼 websocket.php

          <?php
          class Server
          {
          private $serv;
          private $conn = null;
          private static $fd = null;
          public function __construct()
          {
          $this->redis_connect();
          $this->serv = new swoole_websocket_server("0.0.0.0", 9502);
          $this->serv->set(array(
          'worker_num' => 8,
          'daemonize' => false,
          'max_request' => 10000,
          'dispatch_mode' => 2,
          'debug_mode' => 1
          ));
          echo "start \n";
          $this->serv->on('Open', array($this, 'onOpen'));
          $this->serv->on('Message', array($this, 'onMessage'));
          $this->serv->on('Close', array($this, 'onClose'));
          $this->serv->start();
          }
          function onOpen($server, $req)
          {
          echo "connection open: {$req->fd} \n";
          // $server->push($req->fd, json_encode(33));
          }
          public function onMessage($server, $frame)
          {
          //echo "received data $frame->data \n";
          //$server->push($frame->fd, json_encode(["hello", "world"]));
          $pData = json_decode($frame->data,true);
          $fd=$frame->fd;
          if(empty($pData)){
          echo "received data null \n";
          return;
          }
          echo "received fd=>{$fd} message: {$frame->data}\n";
          $data = [];
          if (isset($pData['content'])) {
          $f_fd = $this->getFd($pData['fid']); //獲取綁定的fd
          $data = $this->add($pData['uid'], $pData['fid'], $pData['content']); //保存消息
          $server->push($f_fd, json_encode($data)); //推送到接收者
          $json_data=json_encode($data);
          echo "推送到接收者 fd=>{$f_fd} message: {$json_data}\n";
          } else {
          $this->unBind($pData['uid']); //首次接入,清除綁定數據
          if ($this->bind($pData['uid'], $fd)) { //綁定fd
          $data = $this->loadHistory($pData['uid'], $pData['fid']); //加載歷史記錄
          } else {
          $data = array("content" => "無法綁定fd");
          }
          }
          $json_data=json_encode($data);
          echo "推送到發送者 fd=>{$fd} message: {$json_data}\n";
          $server->push($fd, json_encode($data)); //推送到發送者
          }
          public function onClose($server, $fd)
          {
          //$this->unBind($fd);
          echo "connection close: {$fd}\n";
          }
          /*******************/
          /**
          * redis
          * @param string $host
          * @param string $port
          * @return bool
          */
          function redis_connect($host='127.0.0.1',$port='6379')
          {
          $this->conn = new Redis();
          try{
          $this->conn->connect($host, $port);
          }catch (\Exception $e){
          user_error(print_r($e));
          }
          return true;
          }
          /**
          * 保存消息
          * @param $uid 發送者uid
          * @param $fid 接收者uid
          * @param $content 內容
          * @return array
          */
          public function add($uid, $fid, $content)
          {
          $msg_data=[];
          $msg_data['uid']=$uid;
          $msg_data['fid']=$fid;
          $msg_data['content']=$content;
          $msg_data['time']=time();
          $key=K::KEY_MSG;
          $data=$this->conn->get($key);
          if(!empty($data)){
          $data=json_decode($data,true);
          }else{
          $data=[];
          }
          $data[]=$msg_data;
          $this->conn->set($key,json_encode($data));
          $return_msg[]=$msg_data;
          return $return_msg;
          }
          /**
          * 綁定FD
          * @param $uid
          * @param $fd
          * @return bool
          */
          public function bind($uid, $fd)
          {
          $key=K::KEY_UID."{$uid}";
          $ret=$this->conn->set($key,$fd);
          if(!$ret){
          echo "bind fail \n";
          return false;
          }
          return true;
          }
          /**
          * 獲取FD
          * @param $uid
          * @return mixed
          */
          public function getFd($uid)
          {
          $key=K::KEY_UID."{$uid}";
          $fd=$this->conn->get($key);
          return $fd;
          }
          /**
          * 清除綁定
          * @param $uid
          * @return bool
          */
          public function unBind($uid)
          {
          $key=K::KEY_UID."{$uid}";
          $ret=$this->conn->delete($key);
          if(!$ret){
          return false;
          }
          return true;
          }
          /**
          * 歷史記錄
          * @param $uid
          * @param $fid
          * @param null $id
          * @return array
          */
          public function loadHistory($uid, $fid)
          {
          $msg_data=[];
          $key=K::KEY_MSG;
          $this->conn->delete($key);
          $data=$this->conn->get($key);
          if($data){
          echo $data;
          $json_data=json_decode($data,true);
          foreach ($json_data as $k=>$info){
          if(($info['uid']==$uid&&$info['fid']==$fid)||($info['uid']==$fid&&$info['fid']==$uid)){
          $msg_data[] = $info;
          }
          }
          }
          return $msg_data;
          }
          }
          //Key 定義
          class K{
          const KEY_MSG = 'msg_data';
          const KEY_FD = 'fd_data';
          const KEY_UID = 'uid';
          }
          //啟動服務器
          $server = new Server();
          
          

          客戶端代碼 chat.html

          <!DOCTYPE html>
          <html lang="en">
          <html>
          <head>
           <title>CHAT A</title>
           <meta charset="UTF-8">
           <meta name="viewport" content="width=device-width, initial-scale=1.0">
           <meta http-equiv="X-UA-Compatible" content="ie=edge">
           <script src="jquery.min.js"></script>
           <script src="jquery.json.min.js"></script>
           <style type="text/css">
           .talk_con{
           width:600px;
           height:500px;
           border:1px solid #666;
           margin:50px auto 0;
           background:#f9f9f9;
           }
           .talk_show{
           width:580px;
           height:420px;
           border:1px solid #666;
           background:#fff;
           margin:10px auto 0;
           overflow:auto;
           }
           .talk_input{
           width:580px;
           margin:10px auto 0;
           }
           .whotalk{
           width:80px;
           height:30px;
           float:left;
           outline:none;
           }
           .talk_word{
           width:420px;
           height:26px;
           padding:0px;
           float:left;
           margin-left:10px;
           outline:none;
           text-indent:10px;
           }
           .talk_sub{
           width:56px;
           height:30px;
           float:left;
           margin-left:10px;
           }
           .close{
           width:56px;
           height:30px;
           float:left;
           margin-left:10px;
           }
           .atalk{
           margin:10px;
           }
           .atalk span{
           display:inline-block;
           background:#0181cc;
           border-radius:10px;
           color:#fff;
           padding:5px 10px;
           }
           .btalk{
           margin:10px;
           text-align:right;
           }
           .btalk span{
           display:inline-block;
           background:#ef8201;
           border-radius:10px;
           color:#fff;
           padding:5px 10px;
           }
           </style>
           <script type="text/javascript">
           var uid = 'A'; //發送者uid
           var fid = 'B'; //接收者uid
           var wsUrl = 'ws://192.168.1.100:9502';
           var webSocket = new WebSocket(wsUrl);
           //創建Socket
           webSocket.onopen = function (event) {
           console.log('onOpen=' + event.data);
           //webSocket.send("hello webSocket");
           initData(); //初始化數據,加載歷史記錄
           };
           //接收數據事件
           webSocket.onmessage = function (event) {
           console.log('onMessage=' + event.data);
           loadData($.parseJSON(event.data)); //導入消息記錄,加載新的消息
           }
           //關閉socket
           webSocket.onclose = function (event) {
           console.log('close');
           };
           //socket連接錯誤
           webSocket.onerror = function (event) {
           console.log('error-data:' + event.data);
           }
           //========================================================
           //向服務器發送數據
           function sendMsg() {
           var pData = {
           content: document.getElementById('content').value,
           uid: uid,
           fid: fid,
           }
           if (pData.content == '') {
           alert("消息不能為空");
           return;
           }
           webSocket.send($.toJSON(pData)); //發送消息
           }
           function initData() {
           //var Who = document.getElementById("who").value;
           console.log('initData uid:' + uid + ' fid:'+fid);
           var pData = {
           uid: uid,
           fid: fid,
           }
           webSocket.send($.toJSON(pData)); //獲取消息記錄,綁定fd
           var html = '<div class="atalk"><span id="asay">' + 'WebSocket連接成功' + '</div>';
           $("#words").append(html);
           }
           function loadData(data) {
           for (var i = 0; i < data.length; i++) {
           if(data[i].uid=='A'){
           var html = '<div class="atalk"><span id="asay">' + data[i].uid + '說: ' + data[i].content + '</div>';
           }else{
           var html = '<div class="btalk"><span id="asay">' + data[i].uid + '說: ' + data[i].content + '</div>';
           }
           $("#words").append(html);
           }
           }
           //關閉連接
           function closeWebSocket() {
           console.log('close');
           webSocket.close();
           var html = '<div class="atalk"><span id="asay">' + '已和服務器斷開連接' + '</div>';
           $("#words").append(html);
           }
           </script>
          </head>
          <body>
          <div class="talk_con">
           <div class="talk_show" id="words">
           <!--<div class="atalk"><span id="asay">A說:吃飯了嗎?</span></div>-->
           <!--<div class="btalk"><span id="bsay">B說:還沒呢,你呢?</span></div>-->
           </div>
           <div class="talk_input">
           <!--<select class="whotalk" id="who">-->
           <!--<option value="A" selected="selected">A說:</option>-->
           <!--<option value="B">B說:</option>-->
           <!--</select>-->
          		<button class="close" onclick="closeWebSocket()">斷開</button>
           <input type="text" class="talk_word" id="content">
           <input type="button" onclick="sendMsg()" value="發送" class="talk_sub" id="talksub"> 
           </div>
          </div>
          </body>
          </html>
          

          文件詳情

          • 再復制一份客戶端,修改一下發送者與接收者的uid,即可進行模擬實時聊天。
          • 此代碼已經實現了加載歷史記錄的功能

          使用方法:

          安裝完php、redis和swoole擴展之后,直接執行:

          并可以觀察下輸出,看看websocket服務器是否正常

          最近想做一個Web版的即時聊天為后面開發的各項功能做輔助,就需要瀏覽器與服務器能夠實時通訊。而WebSocket這種雙向通信協議,就很合適用來實現這種需求。

          本篇文章主要解決C#如何實現WebSocket服務端和Javascript客戶端基于wss協議的安全通信問題。

          本文代碼已開源至Github:https://github.com/hxsfx/WebSocketServerTest

          環境

          • 編程語言:C#
          • Websocket開源庫:fleck
          • SSL域名證書:騰訊云IIS版本域名證書

          最終效果

          代碼實現

          前端

          1、HTML

          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="utf-8" />
              <title></title>
              <link href="Content/index.css" rel="stylesheet" />
          </head>
          <body>
              <div id="ChatContainer">
                  <div class="tip "></div>
                  <div class="msgList"></div>
                  <div class="msgInput">
                      <textarea id="SendMsgContent"></textarea>
                      <button id="SendMsgButton">發送</button>
                  </div>
              </div>
              <script src="Scripts/index.js"></script>
          </body>
          </html>

          2、JavaScript

          window.onload = function () {
              var TipElement = document.querySelector("#ChatContainer > div.tip");
              var MsgListElement = document.querySelector("#ChatContainer > div.msgList");
              var SendMsgContentElement = document.getElementById("SendMsgContent");
              var SendMsgButton = document.getElementById("SendMsgButton");
              window.wss = new WebSocket("wss://xxx.hxsfx.com:xxx");
              //監聽消息狀態
              wss.onmessage = function (e) {
                  var dataJson = JSON.parse(e.data);
                  loadData(dataJson.nickName, dataJson.msg, dataJson.date, dataJson.time, true);
              }
              //監聽鏈接狀態
              wss.onopen = function () {
                  if (TipElement.className.indexOf("conn") < 0) {
                      TipElement.className = TipElement.className + " conn";
                  }
                  if (TipElement.className.indexOf("disConn") >= 0) {
                      TipElement.className = TipElement.className.replace("disConn", "");
                  }
              }
              //監聽關閉狀態
              wss.onclose = function () {
                  if (TipElement.className.indexOf("conn") >= 0) {
                      TipElement.className = TipElement.className.replace("conn", "");
                  }
                  if (TipElement.className.indexOf("disConn") < 0) {
                      TipElement.className = TipElement.className + " disConn";
                  }
              }
              //監控輸入框回車鍵(直接發送輸入內容)
              SendMsgContentElement.onkeydown = function () {
                  if (event.keyCode == 13 && SendMsgContentElement.value.trim() != "") {
                      if (SendMsgContentElement.value.trim() != "") {
                          SendMsgButton.click();
                          event.returnValue = false;
                      } else {
                          SendMsgContentElement.value = "";
                      }
                  }
              }
              //發送按鈕點擊事件
              SendMsgButton.onclick = function () {
                  var msgDataJson = {
                      msg: SendMsgContentElement.value,
                  };
                  SendMsgContentElement.value = "";
                  var today = new Date();
                  var date = today.getFullYear() + "年" + (today.getMonth() + 1) + "月" + today.getDate() + "日";
                  var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
                  loadData("自己", msgDataJson.msg, date, time, false);
                  let msgDataJsonStr = JSON.stringify(msgDataJson);
                  wss.send(msgDataJsonStr);
              }
              //把數據加載到對話框中
              function loadData(nickName, msg, date, time, isOther) {
                  let msgItemElement = document.createElement('div');
                  if (isOther) {
                      msgItemElement.className = "msgItem other";
                  } else {
                      msgItemElement.className = "msgItem self";
                  }
                  let chatHeadElement = document.createElement('div');
                  chatHeadElement.className = "chatHead";
                  chatHeadElement.innerHTML = "<svg viewBox=\"0 0 1024 1024\"><path d=\"M956.696128 512.75827c0 245.270123-199.054545 444.137403-444.615287 444.137403-245.538229 0-444.522166-198.868303-444.522166-444.137403 0-188.264804 117.181863-349.108073 282.675034-413.747255 50.002834-20.171412 104.631012-31.311123 161.858388-31.311123 57.297984 0 111.87909 11.128455 161.928996 31.311123C839.504032 163.650197 956.696128 324.494489 956.696128 512.75827L956.696128 512.75827M341.214289 419.091984c0 74.846662 38.349423 139.64855 94.097098 171.367973 23.119557 13.155624 49.151443 20.742417 76.769454 20.742417 26.64894 0 51.773154-7.096628 74.286913-19.355837 57.06467-31.113625 96.650247-96.707552 96.650247-172.742273 0-105.867166-76.664054-192.039781-170.936137-192.039781C417.867086 227.053226 341.214289 313.226864 341.214289 419.091984L341.214289 419.091984M513.886977 928.114163c129.883139 0 245.746984-59.732429 321.688583-153.211451-8.971325-73.739445-80.824817-136.51314-182.517917-167.825286-38.407752 34.55091-87.478354 55.340399-140.989081 55.340399-54.698786 0-104.770182-21.907962-143.55144-57.96211-98.921987 28.234041-171.379229 85.823668-188.368158 154.831344C255.507278 861.657588 376.965537 928.114163 513.886977 928.114163L513.886977 928.114163M513.886977 928.114163 513.886977 928.114163z\"></path></svg>";
                  let msgMainElement = document.createElement('div');
                  msgMainElement.className = "msgMain";
                  let nickNameElement = document.createElement('div');
                  nickNameElement.className = "nickName";
                  nickNameElement.innerText = nickName;
                  let msgElement = document.createElement('div');
                  msgElement.className = "msg";
                  msgElement.innerText = msg;
                  let timeElement = document.createElement('div');
                  timeElement.className = "time";
                  let time_date_Element = document.createElement('span');
                  time_date_Element.innerText = date;
                  let time_time_Element = document.createElement('span');
                  time_time_Element.innerText = time;
                  timeElement.append(time_date_Element);
                  timeElement.append(time_time_Element);
                  msgMainElement.append(nickNameElement);
                  msgMainElement.append(msgElement);
                  msgMainElement.append(timeElement);
                  msgItemElement.append(chatHeadElement);
                  msgItemElement.append(msgMainElement);
                  MsgListElement.append(msgItemElement);
                  MsgListElement.scrollTop = MsgListElement.scrollHeight - MsgListElement.clientHeight;
              }
          } 

          3、CSS

          * {
            padding: 0;
            margin: 0;
          }
          html,
          body {
            font-size: 14px;
            height: 100%;
          }
          body {
            padding: 2%;
            box-sizing: border-box;
            background-color: #a3aebc;
          }
          #ChatContainer {
            padding: 1% 25px 0 25px;
            width: 80%;
            max-width: 850px;
            height: 100%;
            background-color: #fefefe;
            border-radius: 10px;
            box-sizing: border-box;
            margin: auto;
          }
          #ChatContainer .tip {
            height: 30px;
            line-height: 30px;
            text-align: center;
            align-items: center;
            justify-content: center;
            color: #999999;
          }
          #ChatContainer .tip:before {
            content: "連接中";
          }
          #ChatContainer .tip.disConn {
            color: red;
          }
          #ChatContainer .tip.disConn:before {
            content: "× 連接已斷開";
          }
          #ChatContainer .tip.conn {
            color: green;
          }
          #ChatContainer .tip.conn:before {
            content: "√ 已連接";
          }
          #ChatContainer .msgList {
            display: flex;
            flex-direction: column;
            overflow-x: hidden;
            overflow-y: auto;
            height: calc(100% - 100px);
          }
          #ChatContainer .msgList .msgItem {
            display: flex;
            margin: 5px;
          }
          #ChatContainer .msgList .msgItem .chatHead {
            height: 36px;
            width: 36px;
            background-color: #ffffff;
            border-radius: 100%;
          }
          #ChatContainer .msgList .msgItem .msgMain {
            margin: 0 5px;
            display: flex;
            flex-direction: column;
          }
          #ChatContainer .msgList .msgItem .msgMain .nickName {
            color: #666666;
          }
          #ChatContainer .msgList .msgItem .msgMain .msg {
            padding: 10px;
            line-height: 30px;
            color: #333333;
          }
          #ChatContainer .msgList .msgItem .msgMain .time {
            color: #999999;
            font-size: 9px;
          }
          #ChatContainer .msgList .msgItem .msgMain .time span:first-child {
            margin-right: 3px;
          }
          #ChatContainer .msgList .self {
            flex-direction: row-reverse;
          }
          #ChatContainer .msgList .self .nickName {
            text-align: right;
          }
          #ChatContainer .msgList .self .msg {
            border-radius: 10px 0 10px 10px;
            background-color: #d6e5f6;
          }
          #ChatContainer .msgList .self .time {
            text-align: right;
          }
          #ChatContainer .msgList .other .msg {
            border-radius: 0 10px 10px 10px;
            background-color: #e8eaed;
          }
          #ChatContainer .msgInput {
            margin: 15px 0;
            display: flex;
          }
          #ChatContainer .msgInput textarea {
            font-size: 16px;
            padding: 0 5px;
            width: 80%;
            box-sizing: border-box;
            height: 40px;
            line-height: 40px;
            overflow: hidden;
            color: #333333;
            border-radius: 10px 0 0 10px;
            border: none;
            outline: none;
            border: 1px solid #eee;
            resize: none;
          }
          #ChatContainer .msgInput button {
            width: 20%;
            text-align: center;
            height: 40px;
            line-height: 40px;
            color: #fefefe;
            background-color: #2a6bf2;
            border-radius: 0 10px 10px 0;
            border: 1px solid #2a6bf2;
          }
          
          

          后端

          創建控制臺程序(通過cmd命令調用,可修改源碼為直接運行使用),然后進入Gnet安裝fleck,其中的主要代碼如下(完整源碼移步github獲取):

          //組合監聽地址
          var loaction = webSocketProtocol + "://" + ListenIP + ":" + ListenPort;
          var webSocketServer = new WebSocketServer(loaction);
          if (loaction.StartsWith("wss://"))
          {
              webSocketServer.Certificate = new X509Certificate2(pfxFilePath, pfxPassword
             , X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet
              );
              webSocketServer.EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12;
          }//當為安全鏈接時,將證書信息寫入鏈接
          //開始偵聽
          webSocketServer.Start(socket =>
          {
              var socketConnectionInfo = socket.ConnectionInfo;
              var clientId = socketConnectionInfo.ClientIpAddress + ":" + socketConnectionInfo.ClientPort;
              socket.OnOpen = () =>
              {
                  if (!ip_scoket_Dic.ContainsKey(clientId))
                  {
                      ip_scoket_Dic.Add(clientId, socket);
                  }
                  Console.WriteLine(CustomSend("服務端", $"[{clientId}]加入"));
              };
              socket.OnClose = () =>
              {
                  if (ip_scoket_Dic.ContainsKey(clientId))
                  {
                      ip_scoket_Dic.Remove(clientId);
                  }
                  Console.WriteLine(CustomSend("服務端", $"[{clientId}]離開"));
              };
              socket.OnMessage = message =>
              {
                  //將發送過來的json字符串進行解析
                  var msgModel = JsonConvert.DeserializeObject<MsgModel>(message);
                  Console.WriteLine(CustomSend(clientId, msgModel.msg, clientId));
              };
          });
          //出錯后進行重啟  
          webSocketServer.RestartAfterListenError = true;
          Console.WriteLine("【開始監聽】" + loaction);
          //服務端發送消息給客戶端
          do
          {
              Console.WriteLine(CustomSend("服務端", Console.ReadLine()));
          } while (true);

          問題及解決方法

          問題:WebSocket connection to 'wss://xxx.xxx.xxx.xxx:xxxx/' failed:

          解決方法:要建立WSS安全通道,必須要先申請域名SSL證書,同時在防火墻中開放指定端口,以及前端WSS請求域名要跟SSL證書域名相同。


          主站蜘蛛池模板: 久久久久人妻一区精品性色av| 精品福利一区二区三区| 精品国产一区在线观看| 精品国产一区二区三区久久蜜臀| 亚洲AV福利天堂一区二区三| 蜜桃传媒视频麻豆第一区| 综合久久久久久中文字幕亚洲国产国产综合一区首 | 欧美av色香蕉一区二区蜜桃小说| 亚洲Av永久无码精品一区二区| 一区二区三区内射美女毛片| 色视频综合无码一区二区三区| 国产一区二区电影在线观看| 国精产品一区二区三区糖心| 中文字幕VA一区二区三区| 美女视频免费看一区二区| 亚欧在线精品免费观看一区| 无码乱码av天堂一区二区| 亚洲欧洲日韩国产一区二区三区| 色一乱一伦一图一区二区精品| 九九久久99综合一区二区| 麻豆一区二区三区精品视频 | 波多野结衣高清一区二区三区| 免费人妻精品一区二区三区| 色综合视频一区二区三区 | 91久久精品无码一区二区毛片| 中文字幕一区二区免费| 精品一区二区三区在线观看 | 国产韩国精品一区二区三区久久 | 国产福利电影一区二区三区久久老子无码午夜伦不 | 国产伦精品一区二区三区免费下载| 亚洲AV无码一区二区二三区入口| 狠狠色综合一区二区| 日韩十八禁一区二区久久| 日韩国产精品无码一区二区三区| 美女AV一区二区三区| 国产精品久久亚洲一区二区| av一区二区三区人妻少妇| 亚洲AV网一区二区三区| 国产在线一区二区在线视频| 亚洲av综合av一区| 狠狠色综合一区二区|