Qt5.8《网络版够级游戏》编写日志之四:服务器端登陆响应功能实现(TCP多线程)

Qt5.8《网络版够级游戏》编写日志之四:服务器端登陆响应功能实现(TCP多线程)

 

    登陆功能实现,需要服务器与客户端的交互才能实现,这里先实现服务器端的登陆响应功能,这样客户端就可以进行响应开发了,不然客户端的任何操作都无法得到响应。客户端和服务器端采用TCP协议实现,同时为了响应多客户端同时响应,这里服务器采用多线程模式,确保每一个客户端都有一个线程与之对应,不过多线程也会带来很多麻烦,主要在线程通信上。

1 服务器响应客户端信息处理线程类实现(SInfoProThread)

1.1 概述

    为了实现服务器多线程响应客户端的连接和数据交互,那就必须先写一个线程类,这个线程类用来与客户端进行TCP通信,同时也表明是一个个客户端的连接,因此这个线程类需要保存各个客户端用户的连接信息,同时这个线程类需要将收到的信息与服务器主界面(我的命名是ServerFrame)进行交互,另外也需要对客户端的信息进行回应。这个Tcp通信线程类,我取名叫SInfoProThread,总结来说SInfoProThread主要完成以下几个功能:

1)    继承子QThread类;

2)    接收来自客户端的信息,并对信息进行响应;

3)    与服务器主界面类进行信息交互;

4)    保存客户端连接用户的相关信息,如登录名、登陆Ip、头像等。

1.2 代码实现

1.2.1 成员

     qint32 ThreadID;//线程编号,自定义的线程编号,用以对所有连接线程进行区分

     QString LoginName;//客户端登陆者用户名,通过登陆者用户名对各个连接线程进行区分

     QString LoginIP;//客户端登陆者的IP

     qint32 LoginHeadIcon;//客户端登陆者的头像索引

     int socketDescriptor;//客户端登陆者连接套接字描述符

     QTcpSocket *AnswerClient;//与客户端连接通信所用的Tcp套接字

1.2.2 信号

     SInfoProThread类中,定义了两个自定义信号,分别是:

     void sendMsg(qint32 Direct,QString Msg);

     void sendClientExchMsg(ORDERTYPEnType,QString Msg);

    信号sendMsg主要用于发送需要显示的信息给主界面显示,Direct表示在哪个地方显示,目前想到可显示信息的区域,一是服务器信息列表,二是在房间中显示。Msg表示需要显示的信息。

    信号sendClientExchMsg主要用于发送与主界面类(ServerFrame类)进行交互的信息类,ORDERTYPE是我自定义的一个枚举类型,涵盖了可能用到的一些命令名称,我将其定义放在Global类中,Msg为编码后的信息内容,ORDERTYPE的定义如下:

enum ORDERTYPE

{   SERVERSTOP,ONLINE,OFFLINE,CHATTOALL,

CHATTOONE,CHATP2P,CHATREQUEST

};

    我暂时能想到的命令信息大概有这么多,后续还将进一步完善,意思解释如下:

SERVERSTOP:服务器关闭

ONLINE:客户端上线

OFFLINE:客户端离线

CHATTOALL:聊天室中与所有聊天

CHATTOONE:聊天室中与某个人聊天

CHATP2P:点对点两人私下聊天

CHATREQUEST:点对点聊天应答

1.2.3 槽函数

1.2.3.1 answerClientDataSlot()

    在SInfoThread线程类中定义了一个QTcpSocket *AnswerClient,用于接收从客户端送来的各类信息和数据,而answerClientDataSlot()就是为了接收和处理来自AnswerClient的信息和数据。目前,仅实现登陆功能,因此在该操函数中,先实现了登陆信息的处理和响应。

void SInfoProThread::answerClientDataSlot()

{

   QByteArray Buffer;

   QString Temp,Name,Password,str;

   qint32 HeadIcon;

   Temp = "";

   Name = "";

   Password = "";

   HeadIcon = 0;

   //读取缓冲区数据,每次读一行

   Buffer =AnswerClient->readLine();

   //响应一次,循环读完,之前我没有采用循环读,然后就会出现同时来的数据被一同读出来的情况

   while(!Buffer.isEmpty())

   {

       //进行将信息进行本地8位字符串转换,这是为了支持中文的信息

       Temp = QString::fromLocal8Bit(Buffer);

       //登陆信息客户端发送过来的信息格式是LOGIN:Name:Password,也就是以LOGIN开头,后面跟姓名和密码,中间以:间隔

       //因此,这里使用Qstring的split函数将各个信息区分出来

       QStringList StrList = Temp.split(":");

       //登陆信息处理

       if(StrList.value(0) == "LOGIN")

       {

           //登陆信息客户端发送过来的信息格式是LOGIN:Name:Password,也就是以LOGIN开头,后面跟姓名和密码,中间以:间隔

           Name = StrList.value(1);

           Password = StrList.value(2);

           //将客户端用户名和密码作为参数,调用全局配置类实例中数据库实例,然后调用其登陆方法,并据此得到返回值

           qint32 ret = ServerConfig->ServerDataBase.loginServer(Name,Password);

           //在《网络版够级》编写日志之三的文章中说了,为了取巧,我将用户的头像索引号放在返回值中,因此这时候需要进行处理得到头像索引,以及登陆成功与否的信息

          //这里取整可得到登陆成功与否的返回值,取余则得到头像索引

           switch (ret/100)

           {

                case 0:

                                                //登陆值是0,标识用户名或者密码错误

                        Temp.clear();

//登陆不成功,因此加工好用户名或密码错误的信息发送至客户端

                        Temp ="LOGIN:LOGIN_USERPWDERROR";

                                                 //将SInfoThread,也就是自己本身加入到OnLineUser一个QMap中OnLineUser是Qmap <Qstring,QSInfoThread*>的一个实例,全局的,用于记

//录已经连接的用户和登陆的用户,因为没有登陆成功,所以用连接线程的线程ID来作为键值,录入该登陆线程

                        str.sprintf("NotLogin%d",ThreadID);

                        if(!OnLineUser.contains(str))

                           OnLineUser.insert(str,this);

                                                   //登陆不成功信息通过AnswerClient送回客户端

                         AnswerClient->write(Temp.toLocal8Bit());

                        AnswerClient->flush();

                        break;

                case 1:

//返回值是1,表示该用户已经登陆了;将该信息传回客户端即可方法、流程与用户名、密码错误是一样的

                        Temp.clear();

                         Temp = "LOGIN:LOGIN_RELOGIN";

                        str.sprintf("NotLogin%d",ThreadID);

                        if(!OnLineUser.contains(str))

                            OnLineUser.insert(str,this);

                        AnswerClient->write(Temp.toLocal8Bit());

                               AnswerClient->flush();

                               break;

                case 2:

                                                 //返回值是2,表示登陆成功

                        Temp.clear();

                                                 //登陆成功,这时给SInfoThread的成员变量LoginName(客户端登陆名)、头像索引进行赋值,这样SInfoThread就能同LoginName与其他客户端

 //的连接线程区分出来了

                        LoginName = Name;

                        LoginHeadIcon = ret %100;

                        str.sprintf("%d",LoginHeadIcon);

                        Temp ="LOGIN:LOGIN_OK:"+Name+":"+LoginIP+":"+str;

                                 //对于登陆成功的用户,以其登陆名为键值,将其线程的指针存入OnLineUser中,用户对所有连接和登陆玩家进行管理

                        if(!OnLineUser.contains(Name))

                            OnLineUser.insert(Name,this);

                                                 //客户登陆成功,服务器端需要更新玩家在线列表,因此通过sendClientExchMsg信号,将登陆用户的用户名和头像索引,转发给服务器主界面,由

//主界面相应的槽函数进行处理。

                        emit sendClientExchMsg(ONLINE,Name+":"+str+":"+LoginIP);//

                        //将登陆成功的信息,格式LOGIN:LOGIN_OK:Name:LoginIP+头像索引发送给连接的客户端

AnswerClient->write(Temp.toLocal8Bit());

                        AnswerClient->flush();

                        break;

                default:

                          break;

           }

       }

}

    后续有新的信息交互,仍然在此处增加处理代码。

1.2.3.2 initClientConnect()

    客户端连接初始化操函数,这个槽函数以SInfoThread线程的start信号,作为启动信号,也就是SInfoThread启动时,该槽函数就随即运行。该槽函数主要是在线程启动后,对前面的Tcp套接字AnswerClient进行创建和设置,同时,在客户端连接时,将其IP记录到SinfoThread的成员变量LoginIP中,当用户正常登陆后,LoginIP也要显示到好友列表中。

void SInfoProThread::initClientConnect()

{

   //对AnswerClient进行实例化

   AnswerClient = new QTcpSocket;

   AnswerClient->setSocketDescriptor(socketDescriptor);

    //将客户端的IP记录下来

   LoginIP = AnswerClient->peerAddress().toString();

   QList <QString> IPTemp = LoginIP.split(":");

   LoginIP = IPTemp.value(3);

   //绑定槽函数,一个是AnswerClientreadyRead()信号与answerClientDataSlot()的槽的绑定,完成来自客//户端送来的信息处理和响应

   connect(AnswerClient,SIGNAL(readyRead()),this,SLOT(answerClientDataSlot()),Qt::DirectConnection);

   //这个是当客户端用户断开连接发生,促发disconnected()信号时,SinfoThread则以threadEndSlot()槽//来响应,完成用户退出连接时的一些操作,后面会细说

   connect(AnswerClient,SIGNAL(disconnected()),this,SLOT(threadEndSlot()),Qt::DirectConnection);

}

1.2.3.2 threadEndSlot ()

    客户端断开连接时信号响应槽函数,主要是对客户端断开连接后,进行响应,这里进行连接的客户端有两种情况,一个是成功登陆的,一种是只是连接上了,而没有成功登陆的,需要区分处理。

void SInfoProThread::threadEndSlot()

{

   QString Temp;

  //客户端离开服务器,要将该用户的登陆状态置为OffLine,这里没有区分成功登陆还是没登录的用户

  //因为没登陆的用户其LoginName是空,也查不到,所这里统一都置OffLine状态,这样就没事了

  ServerConfig->ServerDataBase.offLine(LoginName);

  //向服务器主界面发送用户离开服务器的显示信息

  emit sendMsg(0,LoginIP + ":" + LoginName + ":离开服务器!");

   //根据LoginName是否是空来判断用户是否登陆

   if(LoginName == "") //只是连接了,没登陆,因此LoginName为空

  {

       //没有登陆,就用ThreadID作为键值,把连接者从OnLineUser中移除

       Temp = "";

       Temp.sprintf("NotLogin%d",ThreadID);

       if(OnLineUser.contains(Temp))

           OnLineUser.remove(Temp);

    }

   else

   {

      //成功登陆的用户,则以LoginName作为键值,从OnLineUser中移除

       if(OnLineUser.contains(LoginName))

       {

           //向主界面通报该用户退出,并从主界面树中清除,清除是不管在不在房间中,直接清除下线消息转发给主界面服务器,信息包含了下线者名称和其IP,主界面会对该信

           //号响应

           sendClientExchMsg(OFFLINE,LoginName + ":" + LoginIP);

           OnLineUser.remove(LoginName);

       }

}

   //线程退出

   deleteLater();

}

1.2.3.3 serverMsgProSlot (ORDERTYPEnType,QString Msg)

    这个槽函数是用来响应服务器主界面送来的信息处理。在这儿我也把的信息传送思路整理了一下:

1)       SInfoThread是一个个具体的客户端通信连接线程,它们负责从客户端接收信息,同时将服务器的响应回馈给客户端;

2)       SInfoThread从客户端接收来的信息,需要转发给服务器端主界面,由主界面进行显示和其他处理;主界面需要向每个一个客户端发送消息或进行一些操作时,就发给一些指令+信息给每一个SInfoThread线程,由线程中的槽函数也就是serverMsgProSlot来进行响应,实现数据信息的执行解释和传输给客户端;

3)       总的来说,信息从客户端送到SInfoThread,SInfoThread再把信息转给主界面ServerFrame,然后主界面ServerFrame再对信息进行处理加工后再将信息回转给各个SInfoThread,再由每个SInfoThread进行适当的加工处理,然后将信息回馈给各个客户端;

    大家可能觉得这样是不是绕了呢,为什么SInfoThread不直接回馈客户端信息,而需要通过主界面ServerFrame进行中转呢?一是因为这些信息首先要传到主界面ServerFrame进行显示,比如玩家列表信息;二是如果直接SInfoThread处理,也不能满足所有要求,比如一个玩家登陆后,服务器要告诉所有连接线程,向他们通知有一个用户登陆了,这样也需要进行信号槽设计,我这儿用主界面ServerFrame进行中转,在信号槽函数设计上,比较清晰,只是信息转了几次,不是太好跟踪。

    serverMsgProSlot槽函数,目前只考虑到三种情况,一个上线、下线还有服务器关闭三个信息,后有的信息仍需在此处添加处理代码。见代码实现:

voidSInfoProThread::serverMsgProSlot(ORDERTYPE nType, QString Msg)

{

   QString Temp,str;

   QMap <QString,SInfoProThread*>::iterator it;

   QList<SInfoProThread *> OnLineUserList;

   //以147852为分隔符,分割每种信息,选择147852作为分隔符,是随便选的,是为了防止用现有的符号分割,后续如果玩家聊天信息中也输入了分割符号,这样信息就被分

  //段了,用147852,毕竟概率要小的多,这个算是个隐患。

   QList <QString> ChatList = Msg.split("147852");

   switch (nType)

    {

       case SERVERSTOP:

        //服务器停止信息,这里每个具体的SInfoProThread,都会给各自的客户端发送//一个服务器停止消息

                        Temp ="SERVERSTOP";

                        AnswerClient->write(Temp.toLocal8Bit());

                         AnswerClient->flush();

                        break;

 

       case ONLINE:

                        Temp = "";

                        str = "";

                        //如果本线程维护的就是刚上线玩家,需要将所有已上线玩家信息都给他转一遍,这样它才能把玩家在线列表创建起来,后续还要将房间信息也 //转过去,//这里因为还有考虑到房间信息的事,所以只做了玩家信息的转发

                        if(LoginName == Msg)

                        {

                                                          //客户端收到的信息格式是:ONLINEYOU|Name:HeadIcon:IP|Name:HeadIcon:IP|……

                            Temp ="ONLINEYOU|";//刚上线玩家为ONLINEYOU,已上线玩家为ONLINE

                            OnLineUserList =OnLineUser.values();

                                                               //对在线玩家逐一按照格式加工转发信息

                            for(inti=0;i<OnLineUserList.size();i++)

                            {

                                Temp +=OnLineUserList.at(i)->LoginName + ":";

                                str.sprintf("%d",OnLineUserList.at(i)->LoginHeadIcon);

                                Temp +=  str + ":";

                                Temp +=OnLineUserList.at(i)->LoginIP + "|";

                             }

                        }

                        else //不是刚上线,那就只转发刚上线玩家信息

                        {

                            if(OnLineUser.contains(Msg))

                            {

                                                                      //信息转发格式是:ONLINEYOU|Name:HeadIcon:IP| Name:HeadIcon:IP|……

                                                                      //以Name找到刚上线玩家的具体信息

                                      it =OnLineUser.find(Msg);

                                                                      //按格式加工好要发送的信息

                                     Temp ="ONLINE:";

                                Temp +=it.value()->LoginName + ":";

                                str.sprintf("%d",it.value()->LoginHeadIcon);

                                Temp +=  str + ":";

                                       Temp +=it.value()->LoginIP;

                            }

                              }

                                                      //向客户端回送登陆成功的信息,主要都是已上线用户的信息

                             AnswerClient->write(Temp.toLocal8Bit());

                             AnswerClient->flush();

                             break;

       case OFFLINE:

                            //下线信息为OFFLINE:Name\nIP,由ServerFrame加工好,直接回发给客户端即//可,无论谁下线,都需要告诉所有人

                           AnswerClient->write(Msg.toLocal8Bit());

                           AnswerClient->flush();

                           break;

       default:

               break;

    }

}

    至此,客户端通信连接线程类已经完成,实现从客户端接收信息,向服务器主界面转发信息,同时也完成服务器主界面给其发送信息的处理实现。

2服务器端多线程TCP服务器类实现

    为了实现多线程响应客户端,因此需要在QTcpServer类基础上,编写一个自己的类(我命名为STcpServer),也就是说STcpServer的基类是QTcpServer,STcpServer主要是重载incomingConnection(qintptrsocketDescriptor)函数,并在这其中完成响应客户端的线程建立,也就是实例化一个个SInfoThread。实现每个客户端都有一个线程与它对应,为它服务。

    STcpServer继承自QTcpServer,没有大的变化,只是重载了void incomingConnection(qintptr socketDescriptor)槽函数,在incomingConnection槽函数中完成客户端响应线程的建立,以及其他信息交互信号槽机制的绑定。代码如下:

void STcpServer::incomingConnection(qintptrsocketDescriptor)

{

  static qint32ThreadID=100;

  //有一个连接申请,即实例化一个SInfoProThread,并将套接字描述字传给它,这样SInfoProThread就可以创建每个客户端的套接字了,实现通信

   SInfoProThread *thread = new SInfoProThread(socketDescriptor,0);

   thread->ThreadID = ThreadID;

   ThreadID++;

   //每个用户的数据通信信息与主界面信息解释槽绑定

   //此处的连接必须使用Qt::QueuedConnection队列方式,因为两个不同线程,在后面的槽中调用Socket

   //不然会出现跨线程调用套接字情况,因为不用队列方式,默认为一个线程

  qRegisterMetaType<ORDERTYPE>("ORDERTYPE");

  //绑定主界面线程向客户连接线程信号槽

   connect((ServerFrame *)(this->parent()),SIGNAL(sendSeverExchMsg(ORDERTYPE,QString)),      

   thread,SLOT(serverMsgProSlot(ORDERTYPE,QString)),Qt::QueuedConnection);

   qRegisterMetaType<ORDERTYPE>("ORDERTYPE");

   //用户上线后,给主界面发送信息,然后服务器在界面中添加玩家到列表,服务器再把该信息转发给所有已登陆用线程,也就是给所有在线用户发送该用户上线消息

   connect(thread, SIGNAL(sendClientExchMsg(ORDERTYPE,QString)),(ServerFrame *)(this->parent()),    

    SLOT(clientMsgProSlot(ORDERTYPE,QString)),Qt::QueuedConnection);

   //每个用户的数据通信信息与主界面信息解释槽绑定-需要在主界面线程显示的信息

   connect(thread, SIGNAL(sendMsg(qint32 ,QString)), (ServerFrame*)(this->parent()), SLOT(showMsgSlot(qint32,QString)),Qt::QueuedConnection);

   //线程启动后,进行初始化槽绑定

   connect(thread, SIGNAL(started()), thread, SLOT(initClientConnect()));

   //线程结束消息绑定

   connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));

   thread->start();

}

    至此,重载的STcpServer类实现完毕,在这个类中,主要就是重载incomingConnection函数,通过在这个函数,实现每个客户端连接线程SInfoThread的实例化,并为每个SInfoThread实例化线程,绑定与服务器主界面线程的信号-槽,后续一些槽这里先给出了,后续在主界面槽编写中,会逐步将处理连接线程SInfoThread送来的各类信号的槽实现完毕。

3 主界面线程类ServerFrame登陆功能配套实现

    首先,说一下我为什么在服务器程序的主界面类上面加了线程二字,把它叫成主界面线程类,因为在刚开始使用Qt做多线程开发时,发现了Qt的线程机制与MFC不一样,对于主界面类中的界面元素的刷新只能在主界面这个类中开展,如果在别的地方调用主界面线程刷新,就会出现跨线程调用的问题,原因就是Qt中主界面类也是一个线程,而Qt不允许跨线程间调用,因此我自己都习惯把主界面类叫程主界面线程类,或者叫主线程。

    有了SInfoTread和重载后的STcpServer类之后,多线程响应客户端的基本工作已经开展到位,下面就是将服务器端TcpServer服务启动了。刚才在前面也说了,STcpServer响应SInfoThread信号的一些槽要写好,另外,服务器自身启动服务也需要添加一些代码,下面开始实施。

3.1 主界面线程ServerFrame相关槽实现

3.1.1 clientMsgProSlot(ORDERTYPE nType,QString OrderInfo)

    这个槽函数,主要是依据SInfoThread送来的各种信息并进行相应的处理。目前,我只做到登陆功能这一块,所以先把登陆这部分的功能进行实现,涉及到的信息有这么几个,一个上线信息ONLINE和下线信息OFFLINE。关于SInfoThread的信号格式在SInfoThread类中已经进行了说明,这里就把主界面线程收到每类信息所作的处理,在这里实现,代码如下:

void ServerFrame::clientMsgProSlot(ORDERTYPEnType, QString OrderInfo)

{

   QString Temp;

   QString Name;

   QString IP;

   qint32 HeadIcon;

   QTreeWidgetItem * TempPlayer;

   QList <QString> InfoList = OrderInfo.split(":");

   QList <QTreeWidgetItem *> PlayListTemp;

   switch (nType)

    {

       case SERVERSTOP:

                        break;

 

       case ONLINE:

                                                     //是上线信息,从信息中提取用户名、头像索引以及用户IP

                             Name =InfoList.value(0);

                             HeadIcon =InfoList.value(1).toInt();

                              IP = InfoList.value(2);

                                                     //这里将上线用户添加到上线用户列表QTreeWidget中添加树控件子项

                         TempPlayer = newQTreeWidgetItem;

                       Temp.sprintf(":/HeadIcon/image/headIcon/%02d.png",HeadIcon+1);

                                                     //根据用户头像索引,设置其图标,因为想做成qq那样

                       TempPlayer->setIcon(0,QIcon(Temp));

                        Temp = Name +"\n" + IP;

                       TempPlayer->setText(0,Temp);

                                                     //添加到PlayerTree下面,PlayerTree是一个树节点,用户列表节点

                       PlayerTree->addChild(TempPlayer);

                                                     //因为需要将该用户登陆的信息告诉其他登陆者,所有这里需要把

                                                     //这个用户登陆的信息再转回给SInfoThread,由SInfoThread来转发

                     emit sendSeverExchMsg(ONLINE,Name);

                        break;

       case OFFLINE:

                                                     //下线信息处理,获得用户名、用户IP

                            Name = InfoList.value(0);

                       IP = InfoList.value(1);

                        Temp = Name +":" + IP;

                                                     //向SInfoThread转发玩家下线信息

                        emit sendSeverExchMsg(OFFLINE,"OFFLINE:"+Temp);

                         Temp = Name +"\n" + IP;

                         //首先玩家树中查找到离开的玩家,找到后,将其中服务器玩家列表树中删除

                            //Qt::MatchRecursive表示搜索整个树,如果不加则只找一级

                            PlayListTemp = ui->PlayerTreeView->findItems(Temp,Qt::MatchExactly| Qt::MatchRecursive);

                            if(PlayListTemp.size()!=0)

                           {

                            //如果没有父节点,则直接删除

                            if(PlayListTemp.at(0)->parent() == Q_NULLPTR)

                            {

                                 ui->PlayerTreeView->setCurrentItem(PlayListTemp.at(0));

                                deleteui->PlayerTreeView->takeTopLevelItem(ui->PlayerTreeView->currentIndex().row());

                            }

                            else //如果有父节点,则需要用父节点来takechild

                            {

                                //由于takechild的输入是该节点的行号,所以先将该节点设定为选中,这样就知道该节点的行号了

                                ui->PlayerTreeView->setCurrentItem(PlayListTemp.at(0));

                                delete PlayListTemp.at(0)->parent()->takeChild(ui->PlayerTreeView->currentIndex().row());

                            }

                        }

                        break;

       default :

                        break;

    }

}

3.1.2 showMsgSlot (qint32 Direct,QString Msg)

    这个槽函数主要是完成信息的显示,之前SInfoThread、SDataBase、SconfigSet等类中都有一些信息需要在该槽函数中进行显示。Direct表示在那个控件中显示。目前,只做了服务器信息显示,Direct=0,就显示到服务器信息栏中,后续根据需要再添加。代码如下

void ServerFrame::showMsgSlot(qint32Direct,QString Msg)

{

   QDateTime currentTime;

   currentTime = QDateTime::currentDateTime();

   switch (Direct)

    {

       case 0:     //表示显示在服务器信息栏中

                   ui->EdtMessage->append(currentTime.toString() +":" +Msg);

                    break;

       case 1:     //表示显示在第一个房间信息栏中

       default:

                    break;

    }

}

3.2 主界面ServerFrame初始化及操作响应

3.2.1 主界面初始化

    初始化主要在构造函数中进行,完成对一些成员的初始化以及对界面控件的一个初始化。代码比较简单,直接看代码即可。

ServerFrame::ServerFrame(QWidget *parent):    QWidget(parent),    ui(new Ui::ServerFrame)

{

   ui->setupUi(this);

   //初始化窗口1024*768

   this->resize(1024,768);

   //设置窗体名称

   this->setWindowTitle("够级-服务器V1.0");

   //加载配置文件

  ServerConfig =new SConfigSet ;

  //绑定ServerConfig信息显示信号到ServerFrame的显示槽上

 connect(ServerConfig,SIGNAL(sendMsg(qint32,QString)),this,SLOT(showMsgSlot(qint32,QString)));

  //绑定ServerDataBase信息显示信号到ServerFrame的显示槽上

  connect(&(ServerConfig->ServerDataBase),SIGNAL(sendMsg(qint32,QString)),this,

  SLOT(showMsgSlot(qint32,QString)));

   //加载配置文件

   ServerConfig->loadConfig();

   //初始化界面,这里主要就是对玩家列表树控件进行了初始化,具体代码在后面

   initUI();

   //实例化STcpServer

   MainServer = new STcpServer(this);

}

3.2.2 玩家列表树控件初始化

    代码也比较简单,就是对玩家列表树控件,进行的简单的设置,增加了玩家节点和房间节点。代码如下:

void ServerFrame::initPlayerTree()

{

         //隐藏表头

ui->PlayerTreeView->setHeaderHidden(true);

//设置节点图标大小

    ui->PlayerTreeView->setIconSize(QSize(48,48));

         //增加玩家列表节点

    PlayerTree = new QTreeWidgetItem;

    PlayerTree->setText(0,"玩家列表");

    ui->PlayerTreeView->addTopLevelItem(PlayerTree);

         //增加房间列表节点

    RoomTree = new QTreeWidgetItem;

    RoomTree->setText(0,"房间列表");

    ui->PlayerTreeView->addTopLevelItem(RoomTree);

         //设置树控件为展开

   ui->PlayerTreeView->expandAll();

}

3.2.3 界面初始化initUI()

    这里没什么具体代码,就是把界面初始化的东西都放在了这里,现在只是对玩家列表树控件,进行了初始化,也就是调用initPlayerTree。代码如下:

void ServerFrame::initUI()

{

   ui->PsbStopServer->setEnabled(false);

    initPlayerTree();

}

3.2.4 服务器启动按钮响应槽

    启动按钮响应槽,主要完成服务器侦听的打开,以及“启动服务器”、“停止服务器”按钮的使能控制。代码如下:

voidServerFrame::on_PsbStartServer_clicked()

{

   QDateTime currentTime;

   currentTime = QDateTime::currentDateTime();

   //先将所有用户都清空为未登陆,防止数据库中存储的玩家登陆状态有ONLINE的状态,因为有ONLINE的状态是不能登陆的,在启动服务器之前,统一全部清为未登陆

   ServerConfig->ServerDataBase.offLine();

   //打开Tcp侦听

   bool flag = MainServer->listen(QHostAddress::Any,ServerConfig->ServerPort);

   if(flag)

   {

         //将信息显示到信息显示栏中

       ui->EdtMessage->append(currentTime.toString() +":" +"服务器启动正常!");

                  //进行“启动服务器”、“停止服务器”按钮的使能控制,防止重复操作

       ui->PsbStartServer->setEnabled(false);

       ui->PsbStopServer->setEnabled(true);

    }

   else

   {

         //将信息显示到信息显示栏中

       ui->EdtMessage->append(currentTime.toString() +":" +"服务器启动异常!");

                  //进行“启动服务器”、“停止服务器”按钮的使能控制,防止重复操作

       ui->PsbStartServer->setEnabled(true);

        ui->PsbStopServer->setEnabled(false);

    }

}

3.2.5 服务器停止按钮响应槽

    停止按钮响应槽,主要完成服务器关闭,已连接用户线程关闭等工作,同时也完成“启动服务器”、“停止服务器”按钮的使能控制。代码如下:

voidServerFrame::on_PsbStopServer_clicked()

{

   //将服务器关闭的消息通知给各个连接线程

   emit sendSeverExchMsg(SERVERSTOP,"");

   //将所有用户的登陆状态设为下线,也是为了防止出现有ONLINE的情况

   ServerConfig->ServerDataBase.offLine();

   //将所有打开的线程关闭,就是依据全局的玩家登陆进行维护

   QList <QString> OnLineUserName = OnLineUser.keys();

   QMap <QString,SInfoProThread*>::iterator it;

    //依据键值,将响应的连接线程全部关闭

    for(int i=0;i<OnLineUserName.size();i++)

    {

       it = OnLineUser.find(OnLineUserName.at(i));

       it.value()->quit();

       it.value()->wait();

    }

   //清空在线用户列表

   OnLineUser.clear();

   //清空在线用户列表

   ui->PlayerTreeView->clear();

   initPlayerTree();

   //关闭服务器,服务器的作用只是打开端口,建立与客户端的连接

   MainServer->close();

   showMsgSlot(0,"服务器关闭成功!");

   ui->PsbStartServer->setEnabled(true);

   ui->PsbStopServer->setEnabled(false);

}

4 实现后的效果图展示

    至此,服务器端登陆功能基本实现完毕。下面把效果展示一下,由于客户端还没弄,实际上也没有多少变化。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值