整合營銷服務商

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

          免費咨詢熱線:

          如何將WinCC報警消息通過語音進行播報

          如何將WinCC報警消息通過語音進行播報

          者:胡世川 - 西門子數字化工業集團自動化部



          客戶經常問到:出現嚴重故障時,能不能自動語音播報消息文本?因為做不到時時刻刻盯著監控畫面。

          So easy!

          有視頻有真相



          ,時長00:14

          實驗環境:

          • WinCC 7.5 SP2
          • Windows10 及 Windows Server 2016/2019


          實現思路:

          • 實時捕捉WinCC的報警文本
          • 調用windows自帶的SAPI語音技術接口,播報文本


          • 開發步驟
          • windows鍵+R,輸入services.msc,打開windows服務界面


          • 啟動Windows的音頻服務


          • 在WinCC的“報警記錄”中,對需要語音播報的消息變量,勾選“觸發動作”,此報警消息觸發后,會執行GMsgFunction函數。


          • 在全局C腳本處的GMsgFunction函數里添加自定義的腳本(如下藍顏色框),捕捉報警消息文本,傳遞給內部變量(如下紅顏色框)。修改完后,此函數會自動從左側目錄樹的“Alarm”進入“alarm”下:

          .......


          MSG_RTDATA_STRUCT mRT;

          MSG_CSDATA_STRUCT sM; // holds alarm info

          MSG_TEXT_STRUCT tMeld; // holds message text info

          CMN_ERROR pError;

          memset( &mRT, 0, sizeof( MSG_RTDATA_STRUCT ) );

          .......

          if(mRT.dwMsgState==MSG_STATE_COME)

          {

          MSRTGetMsgCSData(mRT.dwMsgNr, &sM, &pError);

          MSRTGetMsgText(0, sM.dwTextID[0], &tMeld, &pError);

          SetTagBit("alarmComing",TRUE); //置位VBS腳本觸發器

          SetTagChar("alarmText",tMeld.szText); //報警消息文本

          }


          • VBS全局腳本中調用SAPI接口播報消息文本,此腳本采用變量觸發(內部變量alarmComing)。

          Dim speaker, alarmText

          Dim alarmComing

          alarmComing=HMIRuntime.Tags("alarmComing").Read

          alarmText=HMIRuntime.Tags("alarmText").Read

          If alarmComing=1 Then

          Set speaker=CreateObject("SAPI.SpVoice")

          speaker.rate=0 '語速

          speaker.volume=100 ‘音量

          speaker.Speak alarmText

          HMIRuntime.Tags("alarmComing").write 0

          End If

          End Function


          • 完成組態過程


          若采用PC蜂鳴器提醒報警到來,可參考下面鏈接:

          www.ad.siemens.com.cn/service/elearning/course/1791.html

          來源:人機常情 WinCC(微信公眾號)

          情不算好盯盤容易困,那就讓小姐姐甜美的聲音播報一下當前上證指數吧。實現電腦語音播報只需短短三行代碼就能實現,代碼如下:

          import win32com.client
          speaker=win32com.client.Dispatch("SAPI.SpVoice")
          speaker.Speak("當前上證指數:3259.86")

          就這么簡單!別忘了安裝pywin32模塊。

          當然要有點實用價值還是得費點工夫,接下來我們做一個能實時的動態的播報上證指數的小程序。制作過程中會運用到“tkinter”,Tkinter模塊("Tk 接口")是Python的標準Tk GUI工具包的接口.Tk和Tkinter可以在大多數的Unix平臺下使用,同樣可以應用在Windows和Macintosh系統里.Tk8.0的后續版本可以實現本地窗口風格,并良好地運行在絕大多數平臺中.。還有“requests”,沒錯那個“讓 HTTP 服務人類”的家伙,大名鼎鼎的自動化測試(爬蟲)工具。

          第一步:導入所需模塊

          import re
          import requests
          import tkinter as tk
          import win32com.client

          第二步:定義獲取數據鏈接

          url="https://xueqiu.com/service/v5/stock/batch/quote?symbol=SH000001"
          heads={
              "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,"
                        "application/signed-exchange;v=b3;q=0.9",
              "Accept-Language": "zh-CN,zh;q=0.9",
              "Host": "xueqiu.com",
              "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
                            "Chrome/99.0.4844.51 Safari/537.36 "
                  }

          第三步:制作一個windwos窗口

          class bobao(tk.Tk):
              def __init__(self):
                  super(bobao, self).__init__()
          
                  self.title("上證指數語音播報")
                  self.zs_text=""
                  self.lal=tk.Label(self,
                                      text=self.zs_text,
                                      font=("DS-Digital", 40),
                                      padx=10,
                                      pady=10,
                                      background="black",
                                      foreground="red"
                                      )

          第四步:定義一個請求數據的方法

          def update_re(self):
              r=requests.get(url, headers=heads)
              data=r.text
              current=re.findall('"current":(\d+\.\d+)', data)
              speaker=win32com.client.Dispatch("SAPI.SpVoice")
              speaker.Speak("當前上證指數")
              speaker.Speak(current)
              self.zs_text=current
              self.lal.config(text=self.zs_text)
              self.after(360000, self.update_re)

          這里要注意self.after(360000, self.update_re)的時間單位是毫秒,不要設置得太小,經免把人家的服務器搞宕機。

          完整代碼:

          、開通阿里云TTS服務

          登錄阿里云,選擇菜單:產品->人工智能->語音合成

          點擊“申請開通”,然后在“管理控制臺”創建一個項目

          復制 appkey

          注意,token只有1天有效,所以需要通過接口去定時獲取

          二、對接語音合成api接口

          查看接口文檔

          由于sdk需要引入很多第三方jar包,所以建議對接RESTful API

          copy接口文檔里的demo代碼,把申請到token和appkey粘貼進去,可以直接運行,demo會生成一個syAudio.wav文件,使用語言播放器直接播放就可以。

          根據文檔提示需要在工程中引入三個jar包:

          <dependency>
              <groupId>com.squareup.okhttp3</groupId>
              <artifactId>okhttp</artifactId>
              <version>3.9.1</version>
          </dependency>
          <!-- http://mvnrepository.com/artifact/com.alibaba/fastjson -->
          <dependency>
              <groupId>com.alibaba</groupId>
              <artifactId>fastjson</artifactId>
              <version>1.2.42</version>
          </dependency>
          <!-- 獲取token使用 -->
          <dependency>
              <groupId>com.aliyun</groupId>
              <artifactId>aliyun-java-sdk-core</artifactId>
              <version>3.7.1</version>
          </dependency>

          語音生成工具類:

          package com.hsoft.web.util;
          import java.io.UnsupportedEncodingException;
          import java.net.URLEncoder;
          
          import org.apache.commons.lang3.StringUtils;
          import org.slf4j.Logger;
          import org.slf4j.LoggerFactory;
          
          import com.alibaba.fastjson.JSONObject;
          import com.hsoft.commutil.props.PropertiesUtil;
          
          import okhttp3.MediaType;
          import okhttp3.OkHttpClient;
          import okhttp3.Request;
          import okhttp3.RequestBody;
          import okhttp3.Response;
          public class SpeechRestfulUtil {
              private static Logger logger=LoggerFactory.getLogger(SpeechRestfulUtil.class);
              private String accessToken;
              private String appkey;
          
              private static SpeechRestfulUtil getInstance() {
                  String appkey=PropertiesUtil.getProperty("aliyun.voice.appkey");
                  String token=AliTokenUtil.getToken();
                  return new SpeechRestfulUtil(appkey, token);
              }
          
              private SpeechRestfulUtil(String appkey, String token) {
                  this.appkey=appkey;
                  this.accessToken=token;
              }
              /**
               * HTTPS GET 請求
               */
              private byte[] processGETRequet(String text, String format, int sampleRate) {
                  /**
                   * 設置HTTPS GET請求
                   * 1.使用HTTPS協議
                   * 2.語音識別服務域名:nls-gateway.cn-shanghai.aliyuncs.com
                   * 3.語音識別接口請求路徑:/stream/v1/tts
                   * 4.設置必須請求參數:appkey、token、text、format、sample_rate
                   * 5.設置可選請求參數:voice、volume、speech_rate、pitch_rate
                   */
                  String url="https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts";
                  url=url + "?appkey=" + appkey;
                  url=url + "&token=" + accessToken;
                  url=url + "&text=" + text;
                  url=url + "&format=" + format;
                  url=url + "&sample_rate=" + String.valueOf(sampleRate);
                  // voice 發音人,可選,默認是xiaoyun
                  // url=url + "&voice=" + "xiaoyun";
                  // volume 音量,范圍是0~100,可選,默認50
                  // url=url + "&volume=" + String.valueOf(50);
                  // speech_rate 語速,范圍是-500~500,可選,默認是0
                   url=url + "&speech_rate=" + String.valueOf(100);
                  // pitch_rate 語調,范圍是-500~500,可選,默認是0
                  // url=url + "&pitch_rate=" + String.valueOf(0);
          //        System.out.println("URL: " + url);
                  /**
                   * 發送HTTPS GET請求,處理服務端的響應
                   */
                  Request request=new Request.Builder()
                          .url(url)
                          .get()
                          .build();
                  byte[] bytes=null;
                  try {
                      OkHttpClient client=new OkHttpClient();
                      Response response=client.newCall(request).execute();
                      String contentType=response.header("Content-Type");
                      if ("audio/mpeg".equals(contentType)) {
                          bytes=response.body().bytes();
          //                File f=new File(audioSaveFile);
          //                FileOutputStream fout=new FileOutputStream(f);
          //                fout.write(response.body().bytes());
          //                fout.close();
          //                System.out.println(f.getAbsolutePath());
                          logger.info("The GET SpeechRestful succeed!");
                      }
                      else {
                          // ContentType 為 null 或者為 "application/json"
                          String errorMessage=response.body().string();
                          logger.info("The GET SpeechRestful failed: " + errorMessage);
                      }
                      response.close();
                  } catch (Exception e) {
                      logger.error("processGETRequet",e);
                  }
                  return bytes;
              }
              /**
               * HTTPS POST 請求
               */
              private byte[] processPOSTRequest(String text, String audioSaveFile, String format, int sampleRate) {
                  /**
                   * 設置HTTPS POST請求
                   * 1.使用HTTPS協議
                   * 2.語音合成服務域名:nls-gateway.cn-shanghai.aliyuncs.com
                   * 3.語音合成接口請求路徑:/stream/v1/tts
                   * 4.設置必須請求參數:appkey、token、text、format、sample_rate
                   * 5.設置可選請求參數:voice、volume、speech_rate、pitch_rate
                   */
                  String url="https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts";
                  JSONObject taskObject=new JSONObject();
                  taskObject.put("appkey", appkey);
                  taskObject.put("token", accessToken);
                  taskObject.put("text", text);
                  taskObject.put("format", format);
                  taskObject.put("sample_rate", sampleRate);
                  // voice 發音人,可選,默認是xiaoyun
                  // taskObject.put("voice", "xiaoyun");
                  // volume 音量,范圍是0~100,可選,默認50
                  // taskObject.put("volume", 50);
                  // speech_rate 語速,范圍是-500~500,可選,默認是0
                  // taskObject.put("speech_rate", 0);
                  // pitch_rate 語調,范圍是-500~500,可選,默認是0
                  // taskObject.put("pitch_rate", 0);
                  String bodyContent=taskObject.toJSONString();
          //        System.out.println("POST Body Content: " + bodyContent);
                  RequestBody reqBody=RequestBody.create(MediaType.parse("application/json"), bodyContent);
                  Request request=new Request.Builder()
                          .url(url)
                          .header("Content-Type", "application/json")
                          .post(reqBody)
                          .build();
          
                  byte[] bytes=null;
                  try {
                      OkHttpClient client=new OkHttpClient();
                      Response response=client.newCall(request).execute();
                      String contentType=response.header("Content-Type");
                      if ("audio/mpeg".equals(contentType)) {
                          bytes=response.body().bytes();
                          logger.info("The POST SpeechRestful succeed!");
                      }
                      else {
                          // ContentType 為 null 或者為 "application/json"
                          String errorMessage=response.body().string();
                          logger.info("The POST SpeechRestful failed: " + errorMessage);
                      }
                      response.close();
                  } catch (Exception e) {
                      logger.error("processPOSTRequest",e);
                  }
                  return bytes;
              }
          
              public static byte[] text2voice(String text) {
                  if (StringUtils.isBlank(text)) {
                      return null;
                  }
                  SpeechRestfulUtil demo=SpeechRestfulUtil.getInstance();
          //        String text="會員收款87.12元";
                  // 采用RFC 3986規范進行urlencode編碼
                  String textUrlEncode=text;
                  try {
                      textUrlEncode=URLEncoder.encode(textUrlEncode, "UTF-8")
                              .replace("+", "%20")
                              .replace("*", "%2A")
                              .replace("%7E", "~");
                  } catch (UnsupportedEncodingException e) {
                      logger.error("encode",e);
                  }
          //        String audioSaveFile="syAudio.wav";
                  String format="wav";
                  int sampleRate=16000;
                 return demo.processGETRequet(textUrlEncode, format, sampleRate);
              }
          
          }
          

          獲取Token工具類

          import org.apache.commons.lang3.StringUtils;
          import org.slf4j.Logger;
          import org.slf4j.LoggerFactory;
          
          import com.alibaba.fastjson.JSON;
          import com.alibaba.fastjson.JSONObject;
          import com.aliyuncs.CommonRequest;
          import com.aliyuncs.CommonResponse;
          import com.aliyuncs.DefaultAcsClient;
          import com.aliyuncs.IAcsClient;
          import com.aliyuncs.http.MethodType;
          import com.aliyuncs.http.ProtocolType;
          import com.aliyuncs.profile.DefaultProfile;
          import com.hsoft.commutil.props.PropertiesUtil;
          
          public class AliTokenUtil {
              private static Logger logger=LoggerFactory.getLogger(AliTokenUtil.class);
              // 您的地域ID
              private static final String REGIONID="cn-shanghai";
              // 獲取Token服務域名
              private static final String DOMAIN="nls-meta.cn-shanghai.aliyuncs.com";
              // API 版本
              private static final String API_VERSION="2019-02-28";
              // API名稱
              private static final String REQUEST_ACTION="CreateToken";
              // 響應參數
              private static final String KEY_TOKEN="Token";
              private static final String KEY_ID="Id";
              private static final String KEY_EXPIRETIME="ExpireTime";
          
              private static volatile String TOKEN="";
              private static volatile long EXPIRETIME=0L;
          
              public static String getToken() {
                  if (StringUtils.isNotBlank(TOKEN)) {
                      if (EXPIRETIME - System.currentTimeMillis() / 1000 > 3600) {
                          return TOKEN;
                      }
                  }
                  try {
                      String accessKeyId=PropertiesUtil.getProperty("aliyun.accessId");;
                      String accessKeySecret=PropertiesUtil.getProperty("aliyun.accessKey");;
                      // 創建DefaultAcsClient實例并初始化
                      DefaultProfile profile=DefaultProfile.getProfile(REGIONID, accessKeyId, accessKeySecret);
                      IAcsClient client=new DefaultAcsClient(profile);
                      CommonRequest request=new CommonRequest();
                      request.setDomain(DOMAIN);
                      request.setVersion(API_VERSION);
                      request.setAction(REQUEST_ACTION);
                      request.setMethod(MethodType.POST);
                      request.setProtocol(ProtocolType.HTTPS);
                      CommonResponse response=client.getCommonResponse(request);
                      logger.info(response.getData());
                      if (response.getHttpStatus()==200) {
                          JSONObject result=JSON.parseObject(response.getData());
                          TOKEN=result.getJSONObject(KEY_TOKEN).getString(KEY_ID);
                          EXPIRETIME=result.getJSONObject(KEY_TOKEN).getLongValue(KEY_EXPIRETIME);
                          logger.info("獲取到的Token: " + TOKEN + ",有效期時間戳(單位:秒): " + EXPIRETIME);
                      } else {
                          logger.info("獲取Token失?。?#34;);
                      }
                  } catch (Exception e) {
                      logger.error("getToken error!", e);
                  }
          
                  return TOKEN;
              }
          
          }
          

          三、集成websocket

          當然,我們的目的不是得到一個音頻文件,而是在web站點上可以直接聽見聲音。

          為此,需要引入Websocket,將得到的音頻資源直接推送到web頁面上,然后使用FileReader對象直接播放

          1、引入jar包

          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-websocket</artifactId>
              <exclusions>
                  <exclusion>
                      <groupId>org.slf4j</groupId>
                      <artifactId>log4j-over-slf4j</artifactId>
                  </exclusion>
                  <exclusion>
                      <groupId>org.hibernate</groupId>
                      <artifactId>hibernate-validator</artifactId>
                  </exclusion>
              </exclusions>
          </dependency>
          

          2、創建Websocket處理類

          public class VoiceHandler extends AbstractWebSocketHandler {
              private static final Logger logger=LoggerFactory.getLogger(VoiceHandler.class);
          
              @Override
              public void afterConnectionEstablished(WebSocketSession session) throws Exception {
                  VoicePool.add(session);
              }
          
              @Override
              public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
                  VoicePool.remove(session);
              }
          
          
              @Override
              protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
                  logger.debug("receive Msg :" + message.getPayload());
                  TextMessage msg=new TextMessage(message.getPayload());
                  session.sendMessage(msg);
              }
          
          }
          

          3、創建websocket連接池管理類

          public class VoicePool {
              private static final Logger logger=LoggerFactory.getLogger(VoicePool.class);
              private static Map<String, WebSocketSession> pool=new ConcurrentHashMap<String, WebSocketSession>();
              private static Map<Long, List<String>> userMap=new ConcurrentHashMap<Long, List<String>>();
              private static final ExecutorService threadPool=Executors.newFixedThreadPool(50);
          
              public static void add(WebSocketSession inbound) {
                  pool.put(inbound.getId(), inbound);
                  Map<String, String> map=ParamUtil.parser(inbound.getUri().getQuery());
                  Long companyId=Long.valueOf(map.get("companyId"));
                  logger.info("add companyId:{}", companyId);
                  List<String> lstInBound=null;
                  if (companyId !=null) {
                      lstInBound=userMap.get(companyId);
                      if (lstInBound==null) {
                          lstInBound=new ArrayList<String>();
                          userMap.put(companyId, lstInBound);
                      }
                      lstInBound.add(inbound.getId());
                  }
                  logger.info("add connetion {},total size {}", inbound.getId(), pool.size());
              }
          
              public static void remove(WebSocketSession socket) {
                  String sessionId=socket.getId();
                  List<String> lstInBound=null;
                  Map<String, String> map=ParamUtil.parser(socket.getUri().getQuery());
                  Long companyId=Long.valueOf(map.get("companyId"));
                  logger.info("remove companyId:{}", companyId);
                  if (StringUtils.isNotBlank(sessionId)) {
                      if (companyId !=null) {
                          lstInBound=userMap.get(companyId);
                          if (lstInBound !=null) {
                              lstInBound.remove(sessionId);
                              if (lstInBound.isEmpty()) {
                                  userMap.remove(companyId);
                              }
                          }
                      }
                  }
          
                  pool.remove(sessionId);
                  logger.info("remove connetion {},total size {}", sessionId, pool.size());
              }
          
              /** 推送信息 */
              public static void broadcast(VoiceMsgVo vo) {
                  Long companyId=vo.getCompanyId();
                  if (companyId==null || companyId==0L) {
                      return;
                  }
                  List<String> lstInBoundId=userMap.get(companyId);
                  if (lstInBoundId==null || lstInBoundId.isEmpty()) {
                      return;
                  }
                  byte[] bytes=SpeechRestfulUtil.text2voice(vo.getText());
                  if (bytes==null) {
                      return;
                  }
                  threadPool.execute(() -> {
                      try {
                          for (String id : lstInBoundId) {
                              // 發送給指定用戶
                              WebSocketSession connection=pool.get(id);
                              if (connection !=null) {
                                  synchronized (connection) {
                                      BinaryMessage msg=new BinaryMessage(bytes);
                                      connection.sendMessage(msg);
                                  }
                              }
                          }
                      } catch (Exception e) {
                          logger.error("broadcast error: companyId:{}", companyId, e);
                      }
                  });
              }
          
          }
          

          消息對象bean

          public class VoiceMsgVo {
              private String text;
              private Long companyId;
          }
          

          4、Websocket配置

          @Configuration
          @EnableWebSocket
          public class WebSocketConfig implements WebSocketConfigurer {
          
              @Override
              public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
                  registry.addHandler(voiceHandler(), "/ws/voice").setAllowedOrigins("*");
              }
          
              @Bean
              public VoiceHandler voiceHandler() {
                  return new VoiceHandler();
              }
          
          }
          

          5、前端js處理

          隨便創建頁面,引入下面的js

          var audioContext=new (window.AudioContext || window.webkitAudioContext)();
          var Chat={};
          Chat.socket=null;
          Chat.connect=(function(host) {
              if ("WebSocket" in window) {
                  Chat.socket=new WebSocket(host);
              } else if ("MozWebSocket" in window) {
                  Chat.socket=new MozWebSocket(host);
              } else {
                  Console.log("Error: WebSocket is not supported by this browser.");
                  return;
              }
              Chat.socket.onopen=function() {
                  Console.log("Info: 語音播報已啟動.");
                  // 心跳檢測重置
                  heartCheck.reset().start(Chat.socket);
              };
              Chat.socket.onclose=function() {
                  Console.log("Info: 語音播報已關閉.");
              };
              Chat.socket.onmessage=function(message) {
          
                  heartCheck.reset().start(Chat.socket);
                  if (message.data==null || message.data=='' || "HeartBeat"==message.data){
                      //心跳消息
                      return;
                  }
          
          
                  var reader=new FileReader();
                  reader.onload=function(evt) {
                      if (evt.target.readyState==FileReader.DONE) {
                          audioContext.decodeAudioData(evt.target.result,
                                  function(buffer) {
                                      // 解碼成pcm流
                                      var audioBufferSouceNode=audioContext
                                              .createBufferSource();
                                      audioBufferSouceNode.buffer=buffer;
                                      audioBufferSouceNode
                                              .connect(audioContext.destination);
                                      audioBufferSouceNode.start(0);
                                  }, function(e) {
                                      console.log(e);
                                  });
                      }
                  };
                  reader.readAsArrayBuffer(message.data);
              };
          });
          Chat.initialize=function() {
              Chat.companyId=_currCompanyId;
              if (window.location.protocol=="http:") {
                  Chat.connect("ws://" + window.location.host + "/ws/voice?companyId="+Chat.companyId);
              } else {
                  Chat.connect("wss://" + window.location.host + "/ws/voice?companyId="+Chat.companyId);
              }
          };
          Chat.sendMessage=(function() {
              var message=document.getElementById("chat").value;
              if (message !="") {
                  Chat.socket.send(message);
                  document.getElementById("chat").value="";
              }
          });
          var Console={};
          Console.log=(function(message) {
          
              var _console=document.getElementById("console");
              if (_console==null || _console==undefined){
                  console.log(message);
                  return;
              }
              var p=document.createElement("p");
              p.style.wordWrap="break-word";
              p.innerHTML=message;
              _console.appendChild(p);
              while(_console.childNodes.length>25) 
              {
                  _console.removeChild(_console.firstChild);
              }
              _console.scrollTop=_console.scrollHeight;
          });
          Chat.initialize();
          
          
          //心跳檢測
          var heartCheck={
              timeout : 60000,// 60秒
              timeoutObj : null,
              serverTimeoutObj : null,
              reset : function() {
                  clearTimeout(this.timeoutObj);
                  clearTimeout(this.serverTimeoutObj);
                  return this;
              },
              start : function(ws) {
                  var self=this;
                  this.timeoutObj=setTimeout(function() {
                      // 這里發送一個心跳,后端收到后,返回一個心跳消息,
                      // onmessage拿到返回的心跳就說明連接正常
          //            console.log('start heartCheck');
                      ws.send("HeartBeat");
                      self.serverTimeoutObj=setTimeout(function() {// 如果超過一定時間還沒重置,說明后端主動斷開了
                          ws.close();// 如果onclose會執行reconnect,我們執行ws.close()就行了.如果直接執行reconnect
                                      // 會觸發onclose導致重連兩次
                      }, self.timeout)
                  }, this.timeout)
              }
          }
          

          四、啟動工程測試

          啟動工程,從后臺發送一段消息


          主站蜘蛛池模板: 亚洲精品一区二区三区四区乱码| 日本一区二区三区不卡视频中文字幕| 亚洲熟妇av一区二区三区| 精品国产精品久久一区免费式| 无码夜色一区二区三区| 国产乱码精品一区二区三区四川| 琪琪see色原网一区二区| 少妇人妻偷人精品一区二区| 久久精品国产一区二区三区肥胖| 日韩免费无码一区二区三区| 国产成人久久一区二区不卡三区| 一区二区三区中文字幕| 无码人妻视频一区二区三区| 精品国产AV无码一区二区三区 | 国产精品无圣光一区二区| 91在线一区二区| 亚洲午夜精品第一区二区8050| 高清一区二区三区免费视频| 精品国产一区二区三区免费看| 中文字幕亚洲乱码熟女一区二区| 韩国福利一区二区美女视频| 久久精品一区二区免费看| 成人毛片无码一区二区| 国产在线精品一区二区不卡| 无码aⅴ精品一区二区三区| 国产成人无码一区二区在线观看| 成人精品一区二区激情| 无码一区二区三区| 亚洲国产成人久久一区WWW | 国产精品一区二区香蕉| 久久精品一区二区免费看| 国产丝袜一区二区三区在线观看| 日本内射精品一区二区视频 | 日本视频一区在线观看免费| 成人午夜视频精品一区| 最新中文字幕一区二区乱码| 99精品一区二区三区| 亚洲熟妇av一区二区三区| 一区二区三区免费视频播放器| 国产一区二区草草影院| 久久无码人妻一区二区三区午夜|