QT creater联机版斗地主斗地主(服务器与客户端两部分)

代码下载:
https://download.csdn.net/download/qq_42660681/11826538

程序简介

个人的暑假实训是学习QT编辑器的使用方法。帮助文档中使用了来自老师的帮助文档的内容。
编辑器:Qt Creator 5.6.3版本
编译器:MinGW 4.9.2 32位
调试环境:Windows 10 企业版

程序运行样例

运行样例

程序截图

服务器部分
客户端部分

需要用到的相关理论

1.基于Tcp-ip协议的网络连接相关知识。利用QT自带的TcpSocket和TcpServer实现网络连接。
2.使用QT的QDatabase库实现在服务器端用数据库保存所有用户信息
3.利用QT的QDatastream库和网络连接实现数据流的发送。
4.利用枚举产生一系列操作码,实现通过特定操作码调用不同功能。
5.利用QT的widget(包括QTablewidget,QPushbutton,QLabel)实现对用户信息的录入、控制用户的操作顺序、实现服务器的信息显示。通过指针传递来实现非主窗口类控制主窗口的ui控件。
6.使用QMessageBox库实现多窗口的信息提示。
7.QT的信号与槽机制实现从单一Socket到Server中全体Socket的信息交互。实现定向发包,减少对服务器的消耗。
8.重载TcpServer,QDatastream等的内部函数(例如incommingconnection函数,重载输入输出流运算符)实现对信息的控制。
9.通过动态内存分配的方式创建游戏房间,减少对服务器的消耗。

功能需求

总体部分:

1.服务器的建立和客户端的连接
2.数据流实现数据交互
3.通过map绑定与操作数对应的函数指针来实现对数据流的反应。

服务器部分:

4.能接受客户端的请求,返回相应的数据。
服务器接受用户的注册请求,在数据库中添加一条新用户,并返回添加后的用户信息。
5.服务器接受用户的登录请求,将用户信息绑定到一个连接,可以根据用户信息查找连接并向指定的客户端发送消息。
6.根据不同的请求消息,服务器会找到对应的游戏房间,对房间的游戏数据进行操作,并将操作后的数据返回给其他用户的客户端,客户端根据这些数据进行界面调整。

客户端部分:

7.实现四个页面:注册界面、登录界面、游戏大厅(房间列表)、游戏主界面。
8.根据不同页面实现不同的I/O方式。

功能模块

包含以下大致功能模块:不过多阐述内部实现原理,简单描述,有需求请自行分析代码。写有注释
ps:rcv结尾的文件都是解析方法类。

1.网络功能

ServerManager,ClientManager为程序运行主类。
统一一套解析方法,下方会给出操作码。
TcpServer继承自QTcpServer。
TcpSocket只复制传送DataStream。
TcpSocket接包的时候只接受包大小,操作码,ReceiverService类初始化了所有解析方法(通过函数指针)根据操作码可以调用不同的解析功能。

2.注册功能和登录功能

服务器保存用户名和密码是通过Qsql数据库。
User保存用户名和密码。
其他User开头类包含解析方法实现功能

3.房间功能

动态内存分配新房间。
房间列表内置在服务器,通过RoomManager类管理一个个Room。
每当接收到新建房间的消息就new一个房间出来。

4.打牌功能

本功能来自帮助文档
Card包含了牌的基本属性。
CardRand是随机发牌函数。
CardPic组合了卡和卡面。
Game开头的解析方法完成各项功能。

5.音乐功能

QSound实现。

下面给出操作数对照表:

每一操作数对应一个功能。

enum DATA_TYPE{
    USER_REGISTER =1,     //用户注册
    USER_LOGIN =2,            //用户登录
    ROOM_CREATE = 3,       //创建房间
    ROOM_JOIN = 4,             //加入房间
    ROOM_EXIT = 5,             //离开房间
    ROOM_DELETE = 6,       //删除房间 房主权限
    ROOM_UPDATE = 7,      //服务器主动发出,跟新房间列表数据包
    ROOM_USERUPDATE = 8,      //跟新房间成员
    GAME_BEGIN = 9,                    //开始游戏
    GAME_BASICCARD= 10,         //发基础牌17张
    GAME_LANDOWNER= 11,       //发送地主消息
    GAME_SENDCARD= 12,          //出牌
    GAME_END= 13,                       //有人出光, 游戏结束**
};

程序流程

连接部分:

server:打开服务器点击create,开始监听。
client:用户a连接服务器。
server:有用户连接通过incommingconnection函数初始化。将socket加入列表。

注册和登录部分:

client:连接成功后打开注册和登录按钮。在用户名和密码框(lineedit)输入0-20位的用户名和密码然后点击注册。
server:接收注册消息,与本地数据库内容比对,若没有相同的用户名则注册成功,发送注册成功消息。
client:接受到注册成功消息后可以点击登录。
server:接收到了用户的用户名和密码。与本地数据库比对,正确则发送登录成功消息,并在map中将用户的注册Id和socket绑定,方便以后的定向发包。再发送一个当前已存在的房间列表。
client:接收到登录成功消息后更新label的控件显示注册id,房间列表自动更新。

房间部分:

client:可以创建房间,或者加入一个已经有的房间。
server:创建房间则动态内存分配一个新房间,加入房间则将用户a的注册id添加到已经有的room的数据成员中。发送更新所有客户端的房间列表的信息。
client:加入房间后程序下方的房间状态将会更新。如果是房主可以删除房间,是房间成员可以退出房间。
server:房主删除房间将给通过绑定的id给房间其他成员发送房主删除房间消息,使其全部退出。用户退出将初始化room的某一数据成员。然后发送更新房间列表的消息

游戏部分:

client:当房间人数到达3人,房主可以点击发牌按钮开始游戏。
server:接受到开始游戏消息,洗牌,并向三个用户发送不同的三组牌(17张,用户可见),相同的地主牌(用户不可见)。更新房间状态free到false。
client:接受到房间开始游戏后显示17基础牌和3张地主牌的背面。可以开始0-3分抢地主。涉及到积分机制。
server:接受到抢地主消息后,按规则设置地主并保存。发送地主信息
client:地主先开始出牌。
——server:传递牌型。
——client:内置的打牌逻辑会判断当前出牌是否合理。也可以选择过牌。
server:重复操作上2条直到某一用户手牌为0。发送游戏结束标记,判定胜负,积分变化,数据保存,初始化
client:接受游戏结束消息,初始化,显示积分变化。

程序中遇到的一些问题

(1)怎么在其他类中控制ui界面?QT的ui界面并不能debug,该怎么控制程序的单步运行?

解:有的时候socket接受数据包,解析后最好是能直接控制ui变化。通过查找资料我找到了最简单的办法:需要控制对应ui的类包含对应的库,声明一个对应ui的指针,如何在构建对象的时候用ui界面的指针给其赋值即可。
由于在ui界面运行时出现debug语句将直接导致程序崩溃,我想到一种办法:可以在程序中加入QMessageBox来控制程序在某一点停止,这样就能通过“二分法”判断程序的出错位置。

(2)在各个解析方法中,有的时候需要tcpsocket不单单向数据包的来源发送消息,怎么实现把消息发向其他客户端。

答:这个方法是我查阅多方资料无果后,想到用信号槽机制解决的。由于每个解析方法都需要包含socket,可以在socket里面声明不同的信号,在server创建的时候就能把server中的sliot函数与socket的emit绑定,实现不同解析方法发出信号,server响应。

(3)服务器只有一个,而客户端有多个,如何判断服务器接受的数据流来自哪个客户端?

解:我在网上查阅了QTcpServer文档和各种资料,发现了在服务器建立socket会调用incommingconnection函数,虽然其会系统随机生成socketDescriptor,
这个虽然可以通过这个“身份证号”实现定向发包,但是不方便跟新本地的数据库。
于是我选择了另一种办法,在用户注册的时候,将用户名和密码在数据库中存储的id作为一个特征与socket绑定。首先用户登录,服务器的socket会发送信号,传递用户的id和socket,服务器将其绑定到map,就能通过id找到对应的socket。客户端发包的时候就能通过id来辨别身份。

(4)程序要求包含房间系统。那么如何实现动态的房间创建,又怎么定向的给房间内成员发送数据包?

解:其实房间系统可以简单化为房间号发送给客户端保存到用户本地,这样每次发送数据包的时候附上房间号就能判断数据该发给哪些用户。这样做虽然理论上可行,但是对服务器的要求非常巨大,因为这个发包数量等于当前在线的客户端数量,很多客户端接受到了无用的数据包。
Room类只储存房间信息,包括用户的Id,这样发包的时候需要将用户的id和socket绑定,于是游戏房间内只需要获得成员id就可以定向发包。为了节省空间,考虑到RoomManage类通过动态内存分配的方式创建Room,所有人退出的时候删除房间。难点在于怎么将socket绑定到用户id,前面已经提到解决方法。

(5)在新用户登录的时候,该怎么直接显示房间信息?

解:由于每次创建房间的时候,给所有在线用户发送房间更新消息,也就是说只有某一用户登陆后,其他用户再创建房间,此用户才能看见。
我将room用RoomManage管理,添加到了列表中,每次有成员加入就将列表中的数据全部发送,实现了登录后更新房间消息。

(6)数据流该怎么传递其他类型的数据?

解:数据流Database只能传递基本类型的数据,并不能发送Card类型。因此可以重载<<和>>运算符来实现直接传递Card类型。

特别感谢以下博客给予的帮助

csdn
https://blog.csdn.net/liang19890820/article/details/52273986 //Qt 之 QHostAddress
https://blog.csdn.net/cwj066/article/details/83344705 //QTableWidget详细用法
https://www.bbsmax.com/A/kmzL0EMAzG/ //tcp 服务端如何判断客户端断开连接
https://blog.csdn.net/tt1995cc/article/details/70770042 //QT TCP服务端如何判断客户端已断开连接
https://blog.csdn.net/xr469786706/article/details/78035514 //Qt中数据库查询方法
http://c.biancheng.net/view/429.html //C++ vector详解
https://blog.csdn.net/qq_42189368/article/details/80847563 //C/C++ vector 删除指定元素
https://blog.csdn.net/markely/article/details/8055961 //QT QMap介绍与使用

博客员:
https://www.cnblogs.com/fuqia/p/8904196.html // QTableWidget用法总结
https://www.cnblogs.com/techiel/p/7989908.html //QMap使用
https://www.cnblogs.com/wangcq/p/3520400.html //Socket通信原理
https://www.cnblogs.com/xudong-bupt/p/3522457.html //C++ vector 删除符合条件的元素
https://www.cnblogs.com/xiaoxi666/p/6843211.html //vector作为参数的三种传参方式


版权声明:本文为qq_42660681原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。