1. 程式人生 > 實用技巧 >Java後端使用socketio,實現小程式答題pk功能

Java後端使用socketio,實現小程式答題pk功能

在使用socket.io跟前端通訊過程中,出現了一系列問題,現做下記錄。

一、功能需求是,在小程式端,使用者可相互邀請,進入房間後進行答題PK。實現方法是,使用者點選邀請好友,建立連線,查詢當前是否有房間,有房間傳送訊息給兩人,匹配成功,開始pk。沒有房間新建房間返回,等待20秒,等待別人匹配。

程式碼如下,先看配置,在application.yml配置檔案中增加如下配置

# host在本地測試可以設定為localhost或者本機IP,在Linux伺服器跑可換成伺服器IP
socketio:
  host: 127.0.0.1    #監聽的ip
  port: 9999        #監聽埠
 # 設定最大每幀處理資料的長度,防止他人利用大資料來攻擊伺服器
  maxFramePayloadLength: 
1048576 # 設定http互動最大內容長度 maxHttpContentLength: 1048576 # socket連線數大小(如只監聽一個埠boss執行緒組為1即可) bossCount: 1 workCount: 100 allowCustomRequests: true # 協議升級超時時間(毫秒),預設10秒。HTTP握手升級為ws協議超時時間 upgradeTimeout: 1000000 # Ping訊息超時時間(毫秒),預設60秒,這個時間間隔內沒有接收到心跳訊息就會發送超時事件 pingTimeout: 6000000 # Ping訊息間隔(毫秒),預設25秒。客戶端向伺服器傳送一條心跳訊息間隔 pingInterval:
25000

配置類

package com.cwn.wethink.remy.handler;


import com.corundumstudio.socketio.SocketConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.corundumstudio.socketio.SocketIOServer;

/** * @description: * @author: m1575 * @create: 2020-11-11 **/ @Configuration class SocketIOConfig { @Value("${socketio.host}") private String host; @Value("${socketio.port}") private Integer port; @Value("${socketio.bossCount}") private int bossCount; @Value("${socketio.workCount}") private int workCount; @Value("${socketio.allowCustomRequests}") private boolean allowCustomRequests; @Value("${socketio.upgradeTimeout}") private int upgradeTimeout; @Value("${socketio.pingTimeout}") private int pingTimeout; @Value("${socketio.pingInterval}") private int pingInterval; /** * 以下配置在上面的application.properties中已經註明 * @return */ @Bean public SocketIOServer socketIOServer() { SocketConfig socketConfig = new SocketConfig(); socketConfig.setTcpNoDelay(true); socketConfig.setSoLinger(0); com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration(); config.setSocketConfig(socketConfig); config.setHostname(host); config.setPort(port); config.setBossThreads(bossCount); config.setWorkerThreads(workCount); config.setAllowCustomRequests(allowCustomRequests); config.setUpgradeTimeout(upgradeTimeout); config.setPingTimeout(pingTimeout); config.setPingInterval(pingInterval); return new SocketIOServer(config); } }

後臺實現

package com.cwn.wethink.remy.handler;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import com.alibaba.fastjson.JSONObject;
import com.corundumstudio.socketio.*;
import com.cwn.wethink.pojo.entity.Question;
import com.cwn.wethink.remy.entity.PkAgainGame;
import com.cwn.wethink.remy.entity.PkAnswerTime;
import com.cwn.wethink.remy.entity.PkGroup;
import com.cwn.wethink.remy.entity.WxUserInfo;
import com.cwn.wethink.remy.mapper.PkMapper;
import com.cwn.wethink.remy.mapper.WxUserInfoMapper;
import com.cwn.wethink.remy.service.RemyCourseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class MessageEventHandler {

    // 用來存已連線的客戶端
    private static Map<Long, SocketIOClient> clientMap = new ConcurrentHashMap<>();

    @Autowired
    private SocketIOServer socketIOServer;

    @Autowired
    private PkMapper pkMapper;

    @Autowired
    private WxUserInfoMapper wxUserInfoMapper;

    @Autowired
    private RemyCourseService remyCourseService;

    /**
     * Spring IoC容器建立之後,在載入SocketIOServiceImpl Bean之後啟動
     * @throws Exception
     */
    @PostConstruct
    private void autoStartup() throws Exception {
        start();
    }

    /**
     * Spring IoC容器在銷燬SocketIOServiceImpl Bean之前關閉,避免重啟專案服務端口占用問題
     * @throws Exception
     */
    @PreDestroy
    private void autoStop() throws Exception  {
        stop();
    }

    public void start() {
        // 監聽客戶端連線,同級挑戰比拼
        socketIOServer.addConnectListener(client -> {
            Long uid = Long.valueOf(getParamsByClient(client));
            log.info("connect come in,uid:{}",uid);
            //0為同級挑戰,1為邀請好友pk
            int type = 0;
            //房間號
            int pkId = 0;
            //從請求的連線中拿出引數
            Map<String, List<String>> params = client.getHandshakeData().getUrlParams();
            List<String> list = params.get("type");
            if (list != null && list.size() > 0) {
                type = Integer.valueOf(list.get(0));
            }
            List<String> list1 = params.get("pkId");
            if (list1 != null && list1.size() > 0) {
                pkId = Integer.valueOf(list1.get(0));
            }
            if (uid != null) {
                if (clientMap.containsKey(uid)) {
                    log.info(uid + "is reconnect");
                    clientMap.remove(uid);
                    clientMap.put(uid , client);
                }else{
                    clientMap.put(uid, client);
                    log.info("clientMap:"+clientMap);
                    joinSendMessage(uid , client , type , pkId);
                }
            }
        });

        // 監聽客戶端斷開連線
        socketIOServer.addDisconnectListener(client -> {
            Long uid = Long.valueOf(getParamsByClient(client));
            log.info("disconnect come in,uid:{}",uid);
            if (uid != null) {
                log.info("uid is not null come in,uid:{}",uid);
                clientMap.remove(uid);
                client.disconnect();
                //退出通知對手
                Long usrEntId = 0l;
                PkGroup pkGroup = pkMapper.getPkGroupUserNewRoom(uid);
                if(pkGroup != null){
                    log.info("pkGroup is not null come in,uid:{}",uid);
                    if(uid == pkGroup.getInviteUsrEntId()){
                        usrEntId = pkGroup.getAcceptUsrEntId();
                    }else if(uid == pkGroup.getAcceptUsrEntId()){
                        usrEntId = pkGroup.getInviteUsrEntId();
                    }
                }
                if(usrEntId != null && usrEntId != 0l){
                    log.info("usrEntId is not null come in,uid:{}",uid);
                    log.info("socketIOClient usrEntId:" + usrEntId);
                    JSONObject jsonObject = new JSONObject();
                    SocketIOClient socketIOClient = clientMap.get(usrEntId);
                    if(socketIOClient != null){
                        jsonObject.put("disconnect" , 1);
                        socketIOClient.sendEvent("ClientReceive" , jsonObject);
                    }
                }
                if(clientMap.get(usrEntId) == null || usrEntId == null){
                    if(pkGroup != null){
                        PkGroup updatePkGroup = pkMapper.getPkGroupById(pkGroup.getId());
                        updatePkGroup.setState(2);
                        pkMapper.updatePkGroup(updatePkGroup);
                        log.info("disconnect opponent is disconnect,uid:{}",uid);
                    }
                }
            }
            log.info("disconnect is success,uid:{}",uid);
        });

        // 處理自定義的事件,與連線監聽類似
        // 此示例中測試的json收發 所以接收引數為JSONObject 如果是字元型別可以用String.class或者Object.class
        socketIOServer.addEventListener("ServerReceive",JSONObject.class, (client, data, ackSender) -> {
            JSONObject jsonObject = data;
            if(data != null){
                String uid = jsonObject.getString("usrEntId");
                String action = jsonObject.getString("action");
                if("getAI".equals(action)){
                    log.info("getAI come in,uid:{}",uid);
                    //和人機pk返回
                    botSendMessage(uid , client);
                }else if("challenge".equals(action)){
                    log.info("challenge come in,uid:{}",uid);
                    //pk過程中每做一道題返回訊息給兩個人
                    int pkId = 0;
                    if(!"".equals(jsonObject.getString("pkId"))){
                        pkId = Integer.valueOf(jsonObject.getString("pkId"));
                    }
                    if(pkId == 0){
                        log.info("challenge pkId is 0");
                        return;
                    }
                    long usrEntId = -1;
                    if(!"".equals(jsonObject.getString("usrEntId"))){
                        usrEntId = Long.valueOf(jsonObject.getString("usrEntId"));
                    }
                    if(usrEntId == -1){
                        log.info("challenge usrEntId is -1");
                        return;
                    }
                    int answer = 0;
                    if(!"".equals(jsonObject.getString("answer"))){
                        answer = Integer.valueOf(jsonObject.getString("answer"));
                    }
                    int time = 0;
                    if(!"".equals(jsonObject.getString("time"))){
                        time = Integer.valueOf(jsonObject.getString("time"));
                    }
                    int queResId = 0;
                    if(!"".equals(jsonObject.getString("queResId"))){
                        queResId = Integer.valueOf(jsonObject.getString("queResId"));
                    }
                    int orderNum = 0;
                    if(!"".equals(jsonObject.getString("orderNum"))){
                        orderNum = Integer.valueOf(jsonObject.getString("orderNum"));
                    }
                    int option = 0;
                    if(!"".equals(jsonObject.getString("option"))){
                        option = Integer.valueOf(jsonObject.getString("option"));
                    }
                    PkAnswerTime  pkAnswerNow = new PkAnswerTime();
                    pkAnswerNow.setPkGroupId(pkId);
                    pkAnswerNow.setUsrEntId(usrEntId);
                    pkAnswerNow.setAnswer(answer);
                    pkAnswerNow.setTime(time);
                    pkAnswerNow.setQueResId(queResId);
                    pkAnswerNow.setOrderNum(orderNum);
                    pkAnswerNow.setOption(option);
                    pkMapper.savePkAnswerTime(pkAnswerNow);
                    PkGroup pkGroup = pkMapper.getPkGroupById(pkId);
                    if(usrEntId == pkGroup.getInviteUsrEntId()){
                        long acceptUsrEntId = pkGroup.getAcceptUsrEntId();
                        judgeWinner(acceptUsrEntId , pkAnswerNow ,client);
                    }else if(usrEntId == pkGroup.getAcceptUsrEntId()){
                        long inviteUsrEntId = pkGroup.getInviteUsrEntId();
                        judgeWinner(inviteUsrEntId , pkAnswerNow ,client);
                    }
                }else if("again".equals(action)){
                    log.info("again come in");
                    //再來一局
                    int pkId = Integer.valueOf(jsonObject.getString("pkId"));
                    log.info("pkId:"+pkId+"uid:"+uid);
                    againSendMessage(uid , pkId, client);
                }else if("skill".equals(action)){
                    //使用技能
                    int pkId = Integer.valueOf(jsonObject.getString("pkId"));
                    //技能id
                    int infoId = Integer.valueOf(jsonObject.getString("info"));
                    skillSendMessage(uid , pkId , infoId);
                }
            }
        });

        socketIOServer.start();
        log.info("socket.io初始化服務完成");
    }

    public void stop() {
        if (socketIOServer != null) {
            socketIOServer.stop();
            socketIOServer = null;
        }
        log.info("socket.io服務已關閉");
    }

    /**
     * 此方法為獲取client連線中的引數,可根據需求更改
     * @param client
     * @return
     */
    private String getParamsByClient(SocketIOClient client) {
        // 從請求的連線中拿出引數(這裡的usrEntId必須是唯一標識)
        Map<String, List<String>> params = client.getHandshakeData().getUrlParams();
        List<String> list = params.get("usrEntId");
        if (list != null && list.size() > 0) {
            return list.get(0);
        }
        return null;
    }

    private synchronized void joinSendMessage(long usrEntId , SocketIOClient client , int type , int pkId){
        // 給客戶端傳送一條資訊 傳送ConnectReceive事件 需要客戶端繫結此事件即可接收到訊息
        JSONObject jsonObject = new JSONObject();
        Date date = new Date(new Date().getTime() - 20000);
        PkGroup pkGroup = pkMapper.getPkGroupByState(usrEntId , type , date);
        if(type != 0 && pkId != 0){
            pkGroup = pkMapper.getPkGroupById(pkId);
        }
        if(type != 0 && pkId == 0){
            pkGroup = null;
        }
        if(pkGroup != null){
            pkGroup.setAcceptUsrEntId(usrEntId);
            pkGroup.setState(1);
            pkMapper.updatePkGroup(pkGroup);
            long inviteUsrEntId = pkGroup.getInviteUsrEntId();
            WxUserInfo invite = wxUserInfoMapper.queryWxUserInfoByUsrEntId(inviteUsrEntId);
            List<Question> questions = remyCourseService.listGetRandomTopic(0);
            jsonObject.put("state" , 1);
            jsonObject.put("wxUserInfo" , invite);
            jsonObject.put("questions" , questions);
            jsonObject.put("pkId" , pkGroup.getId());
            client.sendEvent("ConnectReceive",jsonObject);
            SocketIOClient socketIOClient = clientMap.get(inviteUsrEntId);
            WxUserInfo accept = wxUserInfoMapper.queryWxUserInfoByUsrEntId(usrEntId);
            JSONObject acceptJson = new JSONObject();
            acceptJson.put("state" , 1);
            acceptJson.put("questions" , questions);
            acceptJson.put("pkId" , pkGroup.getId());
            acceptJson.put("wxUserInfo" , accept);
            socketIOClient.sendEvent("ConnectReceive" , acceptJson);
        }else{
            PkGroup savePkGroup = new PkGroup();
            savePkGroup.setInviteUsrEntId(usrEntId);
            savePkGroup.setState(0);
            savePkGroup.setCreateTime(new Date());
            savePkGroup.setType(type);
            pkMapper.savePkGroup(savePkGroup);
            jsonObject.put("state" , 0);
            jsonObject.put("pkId" , savePkGroup.getId());
            client.sendEvent("ConnectReceive",jsonObject);
        }
    }

    private synchronized void botSendMessage(String uid , SocketIOClient client){
        JSONObject jsonObject = new JSONObject();
        PkGroup pkGroup = pkMapper.getPkGroupByUsrEntIdToAI(Long.valueOf(uid));
        if(pkGroup != null){
            log.info("getAI pkGroup is not null come in,uid:{}",uid);
            pkGroup.setAcceptUsrEntId(0l);
            pkGroup.setState(1);
            pkMapper.updatePkGroup(pkGroup);
            List<Question> questions = remyCourseService.listGetRandomTopic(0);
            jsonObject.put("state" , 1);
            jsonObject.put("questions" , questions);
            jsonObject.put("pkId" , pkGroup.getId());
            client.sendEvent("AIReceive",jsonObject);
        }
    }

    private synchronized void judgeWinner(long anotherEntId , PkAnswerTime  pkAnswerNow, SocketIOClient client){
        log.info("judgeWinner come in,anotherEntId:{}",anotherEntId);
        int pkId = pkAnswerNow.getPkGroupId();
        int orderNum = pkAnswerNow.getOrderNum();
        int answer = pkAnswerNow.getAnswer();
        int time = pkAnswerNow.getTime();
        long usrEntId = pkAnswerNow.getUsrEntId();
        int option = pkAnswerNow.getOption();
        JSONObject json = new JSONObject();
        PkAnswerTime pkAnswerTime = pkMapper.getPkAnswerTimeByParam(anotherEntId , pkId , orderNum);
        if(pkAnswerTime != null){
            log.info("judgeWinner pkAnswerTime is not null come in,pkAnswerTime:{}",pkAnswerTime);
            PkGroup pkGroup = pkMapper.getPkGroupById(pkId);
            if(orderNum == 5){
                pkGroup.setState(2);
                pkMapper.updatePkGroup(pkGroup);
            }
            long winUsrEntId = -1;
            if(pkAnswerTime.getAnswer() == 1 && answer == 1){
                if(time > pkAnswerTime.getTime()){
                    winUsrEntId = anotherEntId;
                }else if(time < pkAnswerTime.getTime()){
                    winUsrEntId = usrEntId;
                }else{
                    winUsrEntId = -1;
                }
            }else if(pkAnswerTime.getAnswer() == 1){
                winUsrEntId = anotherEntId;
            }else if(answer == 1){
                winUsrEntId = usrEntId;
            }else{
                winUsrEntId = -1;
            }
            json.put("winUsrEntId" , winUsrEntId);
            json.put("pkId" , pkId);
            json.put("usrEntId" , anotherEntId);
            json.put("answer" , pkAnswerTime.getAnswer());
            json.put("time" , pkAnswerTime.getTime());
            json.put("option" , pkAnswerTime.getOption());
            client.sendEvent("challengeReceive",json);
            if(anotherEntId != 0){
                SocketIOClient socketIOClient = clientMap.get(anotherEntId);
                JSONObject acceptJson = new JSONObject();
                acceptJson.put("pkId" , pkId);
                acceptJson.put("usrEntId" , usrEntId);
                acceptJson.put("answer", answer);
                acceptJson.put("time", time);
                acceptJson.put("option",option);
                acceptJson.put("winUsrEntId",winUsrEntId);
                socketIOClient.sendEvent("challengeReceive" , acceptJson);
            }
            if(pkGroup.getInviteUsrEntId() == winUsrEntId){
                if(pkGroup.getInviteNum() != null){
                    pkGroup.setInviteNum(pkGroup.getInviteNum() + 1);
                }else{
                    pkGroup.setInviteNum(1);
                }
            }else if(pkGroup.getAcceptUsrEntId() == winUsrEntId){
                if(pkGroup.getAcceptNum() != null){
                    pkGroup.setAcceptNum(pkGroup.getAcceptNum() + 1);
                }else{
                    pkGroup.setAcceptNum(1);
                }
            }
            pkMapper.updatePkNum(pkGroup);
        }
    }

    private synchronized void againSendMessage(String uid , int pkId , SocketIOClient client){
        JSONObject json = new JSONObject();
        long usrEntId = Long.valueOf(uid);
        PkGroup pkGroup = pkMapper.getPkGroupById(pkId);
        log.info("againSendMessage pkGroup:"+pkGroup);
        long opponentId = -1;
        if(pkGroup.getAcceptUsrEntId() != null){
            if(usrEntId == pkGroup.getAcceptUsrEntId()){
                opponentId = pkGroup.getInviteUsrEntId();
            }else{
                opponentId = pkGroup.getAcceptUsrEntId();
            }
        }
        PkAgainGame pkAgainGame = pkMapper.getPkAgainGame(opponentId , pkId);
        log.info("againSendMessage pkAgainGame:"+pkAgainGame);
        if(pkAgainGame == null){
            PkAgainGame againGame = new PkAgainGame();
            againGame.setCreateTime(new Date());
            againGame.setUsrEntId(usrEntId);
            againGame.setPkGroupId(pkId);
            pkMapper.savePkAgainGame(againGame);
            json.put("usrEntId" , usrEntId);
            json.put("state" , 0);
            SocketIOClient socketIOClient = clientMap.get(opponentId);
            log.info("againSendMessage socketIOClient:"+socketIOClient);
            socketIOClient.sendEvent("AgainReceive" , json);
        }else{
            pkAgainGame.setOpponentUsrEntId(usrEntId);
            pkMapper.updatePkAgainGame(pkAgainGame);
            //建立房間
            PkGroup savePkGroup = new PkGroup();
            savePkGroup.setAcceptUsrEntId(usrEntId);
            savePkGroup.setInviteUsrEntId(opponentId);
            savePkGroup.setState(1);
            savePkGroup.setCreateTime(new Date());
            savePkGroup.setType(pkGroup.getType());
            pkMapper.savePkGroup(savePkGroup);
            List<Question> questions = remyCourseService.listGetRandomTopic(0);
            log.info("againSendMessage questions:"+questions);
            json.put("state" , 1);
            json.put("questions" , questions);
            json.put("pkId" , savePkGroup.getId());
            if(opponentId == 0){
                json.put("wxUserInfo" , "");
            }else{
                WxUserInfo invite = wxUserInfoMapper.queryWxUserInfoByUsrEntId(opponentId);
                json.put("wxUserInfo" , invite);
            }
            client.sendEvent("AgainReceive",json);
            if(opponentId != 0 && opponentId != -1){
                SocketIOClient socketIOClient = clientMap.get(opponentId);
                JSONObject acceptJson = new JSONObject();
                acceptJson.put("state" , 1);
                acceptJson.put("questions" , questions);
                acceptJson.put("pkId" , savePkGroup.getId());
                WxUserInfo accept = wxUserInfoMapper.queryWxUserInfoByUsrEntId(usrEntId);
                acceptJson.put("wxUserInfo" , accept);
                log.info("againSendMessage socketIOClient:"+socketIOClient);
                socketIOClient.sendEvent("AgainReceive" , acceptJson);
            }
        }
    }

    private void skillSendMessage(String uid , int pkId , int infoId){
        JSONObject json = new JSONObject();
        long usrEntId = Long.valueOf(uid);
        PkGroup pkGroup = pkMapper.getPkGroupById(pkId);
        log.info("skillSendMessage pkGroup:"+pkGroup);
        long opponentId = -1;
        if(usrEntId == pkGroup.getAcceptUsrEntId()){
            opponentId = pkGroup.getInviteUsrEntId();
        }else{
            opponentId = pkGroup.getAcceptUsrEntId();
        }
        json.put("usrEntId" , usrEntId);
        json.put("skill" , 1);
        json.put("info" , infoId);
        SocketIOClient socketIOClient = clientMap.get(opponentId);
        log.info("skillSendMessage socketIOClient:"+socketIOClient);
        socketIOClient.sendEvent("SkillReceive" , json);
    }
}

  二、遇到的一些問題

    1、最初在傳送訊息給兩人時,有個人能收到訊息,有個人收不到

剛開始clientMap是這樣宣告的:

private static Map<String, SocketIOClient> clientMap = new ConcurrentHashMap<>();

但我在查詢使用者SocketIOClient時使用的是long型別,所以一直沒查到使用者的SocketIOClient,也就傳送訊息發不過去。

後面改為
private static Map<Long, SocketIOClient> clientMap = new ConcurrentHashMap<>();

這樣宣告還是獲取不到,繼而檢查程式碼,開始返回給兩個人的資訊物件JSONObject,使用的同一個物件,雖然重新set了值,但還是返回了相同的物件。
JSONObject jsonObject = new JSONObject();
jsonObject.put("state" , 1);
jsonObject.put("wxUserInfo" , invite);
jsonObject.put("questions" , questions);
jsonObject.put("pkId" , pkGroup.getId());
client.sendEvent("ConnectReceive",jsonObject);
SocketIOClient socketIOClient = clientMap.get(inviteUsrEntId);
WxUserInfo accept = wxUserInfoMapper.queryWxUserInfoByUsrEntId(usrEntId);
jsonObject.put("state" , 1);
jsonObject.put("questions" , questions);
jsonObject.put("pkId" , pkGroup.getId());
jsonObject.put("wxUserInfo" , accept);
socketIOClient.sendEvent("ConnectReceive" , jsonObject);

後改為重新new一個JSONObject物件,問題解決。

    2、本地測試沒問題,上了測試環境出現問題。連線時間超過1分鐘,會自動斷開連線。

後經查是伺服器使用了nginx,nginx預設連線60s會斷開連線。

需更改nginx配置,如下:

location /socket.io {
                proxy_pass http://172.17.0.2:9999;
                proxy_set_header Host $host;
                proxy_next_upstream off;
                proxy_buffering off;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_read_timeout 3600s;
        }
proxy_read_timeout預設為60s,若需要長時間連線,改大點。
   三、前端程式碼示例
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
    <script type="text/javascript">
        const connectEvent = "ConnectReceive";
        const aiEvent = "AIReceive";
        const challengeEvent = "challengeReceive";
        const sendEvent = "ServerReceive";
        const againEvent = "AgainReceive";
        const clientEvent = "ClientReceive";
        const skillEvent = "SkillReceive";
        var socket;
        function socketClick() {
            //連線伺服器,返回房間、題目資訊
            socket = io.connect('http://127.0.0.1:9999', {
                'force new connection': true,
                'query': 'usrEntId=' + 41 + '&type=' + 1
            })

            socket.on(connectEvent, function (data) {
                //data:{pkId: 31,state: 0} 房間號、狀態為0表示建立了個房間,為1表示有房間並已加入
                //data:{pkId: 31,questions:[{},{}...],state: 1,wxUserInfo:{openId:,unionId:,nickName:,headImg:,empiricalValue:,usrEntId:,createTime:,medal:}}
                console.log(data)
            })

            socket.on(clientEvent, function (data) {
                //data:{disconnect: 1} 對手退出了房間
                console.log(data)
            })

            socket.on(skillEvent, function (data) {
                //data:{usrEntId:42 , skill: 1 , info:1} 對手id、使用了技能,技能id
                console.log(data)
            })

            socket.on(aiEvent, function (data) {
                //data:{pkId: 31,questions:[{},{}...],state: 1} 房間號、題目集合、狀態
                console.log(data)
            })

            //每道題答完接收訊息
            socket.on(challengeEvent, function (data) {
                //返回 data:{pkId: 31,winUsrEntId: 41,usrEntId:42,answer:1,time:3} 房間號、贏家id、對手id、答案1對0錯、時間
                console.log('ServerReceive成功')
            })

            socket.on(againEvent, function (data) {
                //data:{state: 0,usrEntId: 41} state為0邀請資訊、邀請再來一局玩家id
                //data:{state: 1,wxUserInfo: {},questions:[{},{}...],pkId} state為1再來一局成功、對手資訊、題目、房間號
                console.log(data)
            })

            socket.on(clientEvent, function (data) {
                //data:{disconnect: 1} 對手退出了房間
                console.log(data)
            })

            //傳送和人機pk訊息到伺服器
            send({
                action: 'getAI',
                usrEntId: 41
            })

            //每道題答完傳送訊息到伺服器
            send({
                action: 'challenge',
                usrEntId: 41,
                answer: 1,
                time: 6,
                pkId: 1,
                orderNum: 1,
                queResId: 1,
                option: 3
            })
            //再來一局
            send({
                action: 'again',
                usrEntId: 41,
                pkId:1
            })

            //傳送技能
            send({
                action: 'skill',
                usrEntId: 41,
                pkId:1
            })

        }
        function send(data){
            socket.emit(sendEvent,data);
        }
        
        function quitClick() {
            var data={usrEntId:41};
            socket.disconnect();
        }
    </script>