Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 国产激情视频在线观看首页,亚洲欧美中文字幕,欧美经典成人在观看线视频

          整合營(yíng)銷(xiāo)服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢(xún)熱線(xiàn):

          RabbitMQ實(shí)現(xiàn)即時(shí)通訊居然如此簡(jiǎn)單

          時(shí)候我們的項(xiàng)目中會(huì)用到即時(shí)通訊功能,比如電商系統(tǒng)中的客服聊天功能,還有在支付過(guò)程中,當(dāng)用戶(hù)支付成功后,第三方支付服務(wù)會(huì)回調(diào)我們的回調(diào)接口,此時(shí)我們需要通知前端支付成功。最近發(fā)現(xiàn)RabbitMQ可以很方便的實(shí)現(xiàn)即時(shí)通訊功能,如果你沒(méi)有特殊的業(yè)務(wù)需求,甚至可以不寫(xiě)后端代碼,今天給大家講講如何使用RabbitMQ來(lái)實(shí)現(xiàn)即時(shí)通訊!

          SpringBoot實(shí)戰(zhàn)電商項(xiàng)目mall(40k+star)地址:https://github.com/macrozheng/mall

          轉(zhuǎn)自:https://www.jianshu.com/p/e132261456b5

          參考:go語(yǔ)言中文文檔:www.topgoer.com

          MQTT協(xié)議

          MQTT(Message Queuing Telemetry Transport,消息隊(duì)列遙測(cè)傳輸協(xié)議),是一種基于發(fā)布/訂閱(publish/subscribe)模式的輕量級(jí)通訊協(xié)議,該協(xié)議構(gòu)建于TCP/IP協(xié)議上。MQTT最大優(yōu)點(diǎn)在于,可以以極少的代碼和有限的帶寬,為連接遠(yuǎn)程設(shè)備提供實(shí)時(shí)可靠的消息服務(wù)。

          MQTT相關(guān)概念

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

          RabbitMQ啟用MQTT功能

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

          • 首先我們需要安裝并啟動(dòng)RabbitMQ,對(duì)RabbitMQ不了解的朋友可以參考《花了3天總結(jié)的RabbitMQ實(shí)用技巧,有點(diǎn)東西!》;
          • 接下來(lái)就是啟用RabbitMQ的MQTT插件了,默認(rèn)是不啟用的,使用如下命令開(kāi)啟即可;
          rabbitmq-plugins enable rabbitmq_mqtt
          
          • 開(kāi)啟成功后,查看管理控制臺(tái),我們可以發(fā)現(xiàn)MQTT服務(wù)運(yùn)行在1883端口上了。

          MQTT客戶(hù)端

          我們可以使用MQTT客戶(hù)端來(lái)測(cè)試MQTT的即時(shí)通訊功能,這里使用的是MQTTBox這個(gè)客戶(hù)端工具。

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

          • 點(diǎn)擊Create MQTT Client按鈕來(lái)創(chuàng)建一個(gè)MQTT客戶(hù)端;

          • 接下來(lái)對(duì)MQTT客戶(hù)端進(jìn)行配置,主要是配置好協(xié)議端口、連接用戶(hù)名密碼和QoS即可;

          • 再配置一個(gè)訂閱者,訂閱者訂閱testTopicA這個(gè)主題,我們會(huì)向這個(gè)主題發(fā)送消息;

          • 發(fā)布者向主題中發(fā)布消息,訂閱者可以實(shí)時(shí)接收到。

          前端直接實(shí)現(xiàn)即時(shí)通訊

          既然MQTTBox客戶(hù)端可以直接通過(guò)RabbitMQ實(shí)現(xiàn)即時(shí)通訊,那我們是不是直接使用前端技術(shù)也可以實(shí)現(xiàn)即時(shí)通訊?答案是肯定的!下面我們將通過(guò)html+javascript實(shí)現(xiàn)一個(gè)簡(jiǎn)單的聊天功能,真正不寫(xiě)一行后端代碼實(shí)現(xiàn)即時(shí)通訊!

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

          • WEB端與MQTT服務(wù)進(jìn)行通訊需要使用一個(gè)叫MQTT.js的庫(kù),項(xiàng)目地址:https://github.com/mqttjs/MQTT.js

          • 實(shí)現(xiàn)的功能非常簡(jiǎn)單,一個(gè)單聊功能,需要注意的是配置好MQTT服務(wù)的訪(fǎng)問(wèn)地址為:ws://localhost:15675/ws
          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
          </head>
          <body>
          <div>
              <label>目標(biāo)Topic:<input id="targetTopicInput" type="text"></label><br>
              <label>發(fā)送消息:<input id="messageInput" type="text"></label><br>
              <button onclick="sendMessage()">發(fā)送</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");
              //連接到消息隊(duì)列
              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());
              });
          
              //發(fā)送消息
              function sendMessage() {
                  let targetTopic = document.getElementById("targetTopicInput").value;
                  let message = document.getElementById("messageInput").value;
                  //向目標(biāo)topic中發(fā)送消息
                  client.publish(targetTopic, message);
                  showMessage("發(fā)送消息給" + targetTopic + "的消息:" + message);
              }
          
              //從URL中獲取參數(shù)
              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>
          
          • 接下來(lái)我們訂閱不同的主題開(kāi)啟兩個(gè)頁(yè)面測(cè)試下功能(頁(yè)面放在了SpringBoot應(yīng)用的resource目錄下了,需要先啟動(dòng)應(yīng)用再訪(fǎng)問(wèn)): 第一個(gè)訂閱主題testTopicA,訪(fǎng)問(wèn)地址:http://localhost:8088/page/index?topic=testTopicA 第二個(gè)訂閱主題testTopicB,訪(fǎng)問(wèn)地址:http://localhost:8088/page/index?topic=testTopicB
          • 之后互相發(fā)送消息,讓我們來(lái)看看效果吧!

          在SpringBoot中使用

          沒(méi)有特殊業(yè)務(wù)需求的時(shí)候,前端可以直接和RabbitMQ對(duì)接實(shí)現(xiàn)即時(shí)通訊。但是有時(shí)候我們需要通過(guò)服務(wù)端去通知前端,此時(shí)就需要在應(yīng)用中集成MQTT了,接下來(lái)我們來(lái)講講如何在SpringBoot應(yīng)用中使用MQTT。

          • 首先我們需要在pom.xml中添加MQTT相關(guān)依賴(lài);
          <!--Spring集成MQTT-->
          <dependency>
              <groupId>org.springframework.integration</groupId>
              <artifactId>spring-integration-mqtt</artifactId>
          </dependency>
          
          • 在application.yml中添加MQTT相關(guān)配置,主要是訪(fǎng)問(wèn)地址、用戶(hù)名密碼、默認(rèn)主題信息;
          rabbitmq:
            mqtt:
              url: tcp://localhost:1883
              username: guest
              password: guest
              defaultTopic: testTopic
          
          • 編寫(xiě)一個(gè)Java配置類(lèi)從配置文件中讀取配置便于使用;
          /**
           * MQTT相關(guān)配置
           * Created by macro on 2020/9/15.
           */
          @Data
          @EqualsAndHashCode(callSuper = false)
          @Component
          @ConfigurationProperties(prefix = "rabbitmq.mqtt")
          public class MqttConfig {
              /**
               * RabbitMQ連接用戶(hù)名
               */
              private String username;
              /**
               * RabbitMQ連接密碼
               */
              private String password;
              /**
               * RabbitMQ的MQTT默認(rèn)topic
               */
              private String defaultTopic;
              /**
               * RabbitMQ的MQTT連接地址
               */
              private String url;
          }
          
          • 添加MQTT消息訂閱者相關(guān)配置,使用@ServiceActivator注解聲明一個(gè)服務(wù)激活器,通過(guò)MessageHandler來(lái)處理訂閱消息;
          /**
           * MQTT消息訂閱者相關(guān)配置
           * 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());
                  //設(shè)置消息質(zhì)量: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消息發(fā)布者相關(guān)配置;
          /**
           * MQTT消息發(fā)布者相關(guān)配置
           * 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網(wǎng)關(guān),用于向主題中發(fā)送消息;
          /**
           * MQTT網(wǎng)關(guān),通過(guò)接口將數(shù)據(jù)傳遞到集成流
           * Created by macro on 2020/9/15.
           */
          @Component
          @MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
          public interface MqttGateway {
              /**
               * 發(fā)送消息到默認(rèn)topic
               */
              void sendToMqtt(String payload);
          
              /**
               * 發(fā)送消息到指定topic
               */
              void sendToMqtt(String payload, @Header(MqttHeaders.TOPIC) String topic);
          
              /**
               * 發(fā)送消息到指定topic并設(shè)置QOS
               */
              void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
          }
          
          • 添加MQTT測(cè)試接口,使用MQTT網(wǎng)關(guān)向特定主題中發(fā)送消息;
          /**
           * MQTT測(cè)試接口
           * Created by macro on 2020/9/15.
           */
          @Api(tags = "MqttController", description = "MQTT測(cè)試接口")
          @RestController
          @RequestMapping("/mqtt")
          public class MqttController {
          
              @Autowired
              private MqttGateway mqttGateway;
          
              @PostMapping("/sendToDefaultTopic")
              @ApiOperation("向默認(rèn)主題發(fā)送消息")
              public CommonResult sendToDefaultTopic(String payload) {
                  mqttGateway.sendToMqtt(payload);
                  return CommonResult.success(null);
              }
          
              @PostMapping("/sendToTopic")
              @ApiOperation("向指定主題發(fā)送消息")
              public CommonResult sendToTopic(String payload, String topic) {
                  mqttGateway.sendToMqtt(payload, topic);
                  return CommonResult.success(null);
              }
          }
          
          • 調(diào)用接口向主題中發(fā)送消息進(jìn)行測(cè)試;

          • 后臺(tái)成功接收到消息并進(jìn)行打印。
          2020-09-17 14:29:01.689  INFO 11192 --- [ubscriberClient] c.m.mall.tiny.config.MqttInboundConfig   : handleMessage : 來(lái)自網(wǎng)頁(yè)上的消息
          2020-09-17 14:29:06.101  INFO 11192 --- [ubscriberClient] c.m.mall.tiny.config.MqttInboundConfig   : handleMessage : 來(lái)自網(wǎng)頁(yè)上的消息
          2020-09-17 14:29:07.384  INFO 11192 --- [ubscriberClient] c.m.mall.tiny.config.MqttInboundConfig   : handleMessage : 來(lái)自網(wǎng)頁(yè)上的消息
          

          總結(jié)

          消息中間件應(yīng)用越來(lái)越廣泛,不僅可以實(shí)現(xiàn)可靠的異步通信,還可以實(shí)現(xiàn)即時(shí)通訊,掌握一個(gè)消息中間件還是很有必要的。如果沒(méi)有特殊業(yè)務(wù)需求,客戶(hù)端或者前端直接使用MQTT對(duì)接消息中間件即可實(shí)現(xiàn)即時(shí)通訊,有特殊需求的時(shí)候也可以使用SpringBoot集成MQTT的方式來(lái)實(shí)現(xiàn),總之消息中間件是實(shí)現(xiàn)即時(shí)通訊的一個(gè)好選擇!

          項(xiàng)目源碼地址

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

          用php+swoole+redis 簡(jiǎn)單實(shí)現(xiàn)網(wǎng)頁(yè)即時(shí)聊天,需要瀏覽器支持html5的websocket,

          websocket是不同于http的另外一種網(wǎng)絡(luò)通信協(xié)議,能夠進(jìn)行雙向通信,基于此,可開(kāi)發(fā)出各種實(shí)時(shí)通信產(chǎn)品,簡(jiǎn)單做了個(gè)聊天demo,順便分享一下

          效果圖如下:

          環(huán)境:

          • 系統(tǒng) centos7.5
          • php7.2.9
          • redis5.0.0
          • swoole4.2.2
          • nginx 1.8

          參考文檔:

          • redis官網(wǎng) https://redis.io
          • 教程 http://www.runoob.com/redis/
          • swoole 官網(wǎng) https://swoole.com
          • swoole 的webSocket手冊(cè):https://wiki.swoole.com/wiki/page/397.html
          • php擴(kuò)展庫(kù)地址 http://pecl.php.net/

          IP與端口:

          • 虛擬機(jī)的IP: 192.168.1.100
          • webSocket服務(wù)端口是 9520
          • redis服務(wù)端口是 6379

          服務(wù)器端代碼 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']); //首次接入,清除綁定數(shù)據(jù)
          if ($this->bind($pData['uid'], $fd)) { //綁定fd
          $data = $this->loadHistory($pData['uid'], $pData['fid']); //加載歷史記錄
          } else {
          $data = array("content" => "無(wú)法綁定fd");
          }
          }
          $json_data=json_encode($data);
          echo "推送到發(fā)送者 fd=>{$fd} message: {$json_data}\n";
          $server->push($fd, json_encode($data)); //推送到發(fā)送者
          }
          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 發(fā)送者uid
          * @param $fid 接收者uid
          * @param $content 內(nèi)容
          * @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';
          }
          //啟動(dòng)服務(wù)器
          $server = new Server();
          
          

          客戶(hù)端代碼 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'; //發(fā)送者uid
           var fid = 'B'; //接收者uid
           var wsUrl = 'ws://192.168.1.100:9502';
           var webSocket = new WebSocket(wsUrl);
           //創(chuàng)建Socket
           webSocket.onopen = function (event) {
           console.log('onOpen=' + event.data);
           //webSocket.send("hello webSocket");
           initData(); //初始化數(shù)據(jù),加載歷史記錄
           };
           //接收數(shù)據(jù)事件
           webSocket.onmessage = function (event) {
           console.log('onMessage=' + event.data);
           loadData($.parseJSON(event.data)); //導(dǎo)入消息記錄,加載新的消息
           }
           //關(guān)閉socket
           webSocket.onclose = function (event) {
           console.log('close');
           };
           //socket連接錯(cuò)誤
           webSocket.onerror = function (event) {
           console.log('error-data:' + event.data);
           }
           //========================================================
           //向服務(wù)器發(fā)送數(shù)據(jù)
           function sendMsg() {
           var pData = {
           content: document.getElementById('content').value,
           uid: uid,
           fid: fid,
           }
           if (pData.content == '') {
           alert("消息不能為空");
           return;
           }
           webSocket.send($.toJSON(pData)); //發(fā)送消息
           }
           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 + '說(shuō): ' + data[i].content + '</div>';
           }else{
           var html = '<div class="btalk"><span id="asay">' + data[i].uid + '說(shuō): ' + data[i].content + '</div>';
           }
           $("#words").append(html);
           }
           }
           //關(guān)閉連接
           function closeWebSocket() {
           console.log('close');
           webSocket.close();
           var html = '<div class="atalk"><span id="asay">' + '已和服務(wù)器斷開(kāi)連接' + '</div>';
           $("#words").append(html);
           }
           </script>
          </head>
          <body>
          <div class="talk_con">
           <div class="talk_show" id="words">
           <!--<div class="atalk"><span id="asay">A說(shuō):吃飯了嗎?</span></div>-->
           <!--<div class="btalk"><span id="bsay">B說(shuō):還沒(méi)呢,你呢?</span></div>-->
           </div>
           <div class="talk_input">
           <!--<select class="whotalk" id="who">-->
           <!--<option value="A" selected="selected">A說(shuō):</option>-->
           <!--<option value="B">B說(shuō):</option>-->
           <!--</select>-->
          		<button class="close" onclick="closeWebSocket()">斷開(kāi)</button>
           <input type="text" class="talk_word" id="content">
           <input type="button" onclick="sendMsg()" value="發(fā)送" class="talk_sub" id="talksub"> 
           </div>
          </div>
          </body>
          </html>
          

          文件詳情

          • 再?gòu)?fù)制一份客戶(hù)端,修改一下發(fā)送者與接收者的uid,即可進(jìn)行模擬實(shí)時(shí)聊天。
          • 此代碼已經(jīng)實(shí)現(xiàn)了加載歷史記錄的功能

          使用方法:

          安裝完php、redis和swoole擴(kuò)展之后,直接執(zhí)行:

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

          最近想做一個(gè)Web版的即時(shí)聊天為后面開(kāi)發(fā)的各項(xiàng)功能做輔助,就需要瀏覽器與服務(wù)器能夠?qū)崟r(shí)通訊。而WebSocket這種雙向通信協(xié)議,就很合適用來(lái)實(shí)現(xiàn)這種需求。

          本篇文章主要解決C#如何實(shí)現(xiàn)WebSocket服務(wù)端和Javascript客戶(hù)端基于wss協(xié)議的安全通信問(wèn)題。

          本文代碼已開(kāi)源至Github:https://github.com/hxsfx/WebSocketServerTest

          環(huán)境

          • 編程語(yǔ)言:C#
          • Websocket開(kāi)源庫(kù):fleck
          • SSL域名證書(shū):騰訊云IIS版本域名證書(shū)

          最終效果

          代碼實(shí)現(xiàn)

          前端

          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">發(fā)送</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");
              //監(jiān)聽(tīng)消息狀態(tài)
              wss.onmessage = function (e) {
                  var dataJson = JSON.parse(e.data);
                  loadData(dataJson.nickName, dataJson.msg, dataJson.date, dataJson.time, true);
              }
              //監(jiān)聽(tīng)鏈接狀態(tài)
              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", "");
                  }
              }
              //監(jiān)聽(tīng)關(guān)閉狀態(tài)
              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";
                  }
              }
              //監(jiān)控輸入框回車(chē)鍵(直接發(fā)送輸入內(nèi)容)
              SendMsgContentElement.onkeydown = function () {
                  if (event.keyCode == 13 && SendMsgContentElement.value.trim() != "") {
                      if (SendMsgContentElement.value.trim() != "") {
                          SendMsgButton.click();
                          event.returnValue = false;
                      } else {
                          SendMsgContentElement.value = "";
                      }
                  }
              }
              //發(fā)送按鈕點(diǎn)擊事件
              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);
              }
              //把數(shù)據(jù)加載到對(duì)話(huà)框中
              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: "× 連接已斷開(kāi)";
          }
          #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;
          }
          
          

          后端

          創(chuàng)建控制臺(tái)程序(通過(guò)cmd命令調(diào)用,可修改源碼為直接運(yùn)行使用),然后進(jìn)入Gnet安裝fleck,其中的主要代碼如下(完整源碼移步github獲取):

          //組合監(jiān)聽(tīng)地址
          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;
          }//當(dāng)為安全鏈接時(shí),將證書(shū)信息寫(xiě)入鏈接
          //開(kāi)始偵聽(tīng)
          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("服務(wù)端", $"[{clientId}]加入"));
              };
              socket.OnClose = () =>
              {
                  if (ip_scoket_Dic.ContainsKey(clientId))
                  {
                      ip_scoket_Dic.Remove(clientId);
                  }
                  Console.WriteLine(CustomSend("服務(wù)端", $"[{clientId}]離開(kāi)"));
              };
              socket.OnMessage = message =>
              {
                  //將發(fā)送過(guò)來(lái)的json字符串進(jìn)行解析
                  var msgModel = JsonConvert.DeserializeObject<MsgModel>(message);
                  Console.WriteLine(CustomSend(clientId, msgModel.msg, clientId));
              };
          });
          //出錯(cuò)后進(jìn)行重啟  
          webSocketServer.RestartAfterListenError = true;
          Console.WriteLine("【開(kāi)始監(jiān)聽(tīng)】" + loaction);
          //服務(wù)端發(fā)送消息給客戶(hù)端
          do
          {
              Console.WriteLine(CustomSend("服務(wù)端", Console.ReadLine()));
          } while (true);

          問(wèn)題及解決方法

          問(wèn)題:WebSocket connection to 'wss://xxx.xxx.xxx.xxx:xxxx/' failed:

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


          主站蜘蛛池模板: 人妻夜夜爽天天爽爽一区| 波多野结衣一区在线| 国产精品无码一区二区在线观| 亚洲AV无码一区二区三区性色| 日韩精品久久一区二区三区| 麻豆精品久久久一区二区| 一区二区高清视频在线观看| 亚洲色无码专区一区| 精品国产AV一区二区三区| 亲子乱av一区二区三区| 成人精品一区二区三区中文字幕| 99国产精品欧美一区二区三区| 亚洲一区在线视频观看| 无码一区二区三区老色鬼| 成人精品视频一区二区三区 | 国产一区二区好的精华液| 日韩精品一区二区三区中文| 国模少妇一区二区三区| 国产成人无码AV一区二区在线观看 | 秋霞午夜一区二区| 国模无码人体一区二区| 色噜噜AV亚洲色一区二区| 久久免费国产精品一区二区| 一区二区三区人妻无码| 亚洲制服中文字幕第一区| 久久久久人妻精品一区| 精品福利一区二区三区免费视频| 99精品一区二区三区| 蜜桃AV抽搐高潮一区二区| 国产av一区最新精品| 精品一区狼人国产在线| 国产aⅴ一区二区| 国产精品被窝福利一区| 国产AV午夜精品一区二区三| 福利一区二区三区视频在线观看| 射精专区一区二区朝鲜| 糖心vlog精品一区二区三区| 精品一区二区高清在线观看| 精品国产亚洲一区二区在线观看| 国产成人精品无码一区二区| 精品少妇人妻AV一区二区三区 |