文章目录
  1. 1. 说明
  2. 2. 系统功能模块图
  3. 3. 系统流程图
  4. 4. 系统数据流图
  5. 5. 负载均衡策略图
  6. 6. 概要设计
  7. 7. E-R 图
  8. 8. 数据库表设计
  9. 9. 详细设计之——通信协议的制订
    1. 9.1. 枚举所有要用到的消息标识
    2. 9.2. 各个消息的数据结构设计
  10. 10. 详细设计之—— Socket 发包形式统一
  11. 11. 详细设计之——服务器的设计
    1. 11.1. 配置文件的设计
    2. 11.2. ccache 库的应用
    3. 11.3. MySQL 的操作封装
    4. 11.4. 主程序设计
  12. 12. 详细设计之——客户端的设计
    1. 12.1. 大厅进程的设计
    2. 12.2. 五子棋进程
    3. 12.3. 客户端套接字的共用
    4. 12.4. 客户端的数据同步

说明

此篇也是遵从当时的文档,不做太多修正。

系统功能模块图

系统流程图

系统数据流图

负载均衡策略图

概要设计

  • 服务器端:
    • 主线程——网络监听线程;
    • 登录和注册线程池;
    • 维护游戏房间列表线程;
    • 游戏逻辑线程。
  • 客户端:
    • 大厅进程(接收通信包线程、主逻辑线程);
    • 五子棋进程(接收通信包线程、主逻辑线程)。
  • 通信协议:

    • 客户端到服务器的登录消息、
    • 客户端到服务器的注册消息、
    • 客户端到服务器的检测ID消息、
    • 客户端到服务器的索求房间列表消息、
    • 客户端到服务器的进某个房间消息;

    • 服务器到客户端的登录回应消息、

    • 服务器到客户端的注册回应消息、
    • 服务器到客户端的房间列表消息、
    • 服务器到客户端的进某个房间的回应消息;

    • 客户端到服务器的进某个桌子座位的消息、

    • 客户端到服务器的房间聊天消息;

    • 服务器到客户端的进某个桌子座位的回应消息、

    • 服务器到客户端的聊天转发消息;

    • 客户端到服务器的游戏数据消息、

    • 客户端到服务器的游戏状态消息、
    • 客户端到服务器的聊天消息;

    • 服务器到客户端的游戏数据转发消息、

    • 服务器到客户端的准备回复消息、
    • 服务器到客户端的聊天转发消息、
    • 服务器到客户端的桌子玩家列表消息;

    • 服务器到客户端的广播游戏状态消息。

E-R 图

数据库表设计

详细设计之——通信协议的制订

枚举所有要用到的消息标识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typedef enum
{
ClientToLogin,
ClientToRegister,
ClientToCheckUid, //检查 UID 是否被抢注
ClientToHall_RoomList,
ClientToHall_RoomNum,

LoginToClient,
RegisterToClient,
HallToClient_RoomList,
HallToClient_RoomNum, //check,tables and users of the room

ClientToRoom_TableNum,
ClientToRoom_Chat,

RoomToClient_TableNum,
RoomToClient_Chat,

ClientToTable_GameData,
ClientToTable_State,
ClientToTable_Chat,

TableToClient_GameData,
TableToClient_State,
TableToClient_Chat,
TableToClient_UserList,

Room_Mate_State //server to client
}MSGTYPE;

各个消息的数据结构设计

  • 登录、注册、大厅消息协议设计:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
typedef struct
{
unsigned int UserID;
char PWD[20];
}ClientToLoginMsg;

typedef struct
{
unsigned int UserID;
unsigned int RoomID;
int image;
char NickName[20];
}ClientToHall_RoomNumMsg;

typedef struct
{
unsigned int UserID;
char PWD[20];
char NickName[20];
int Image;
}ClientToRegisterMsg;

typedef struct
{
int game_rank;
int online;
int MaxPlayers;
int MaxTables;
int minLevel;
int maxLevel;
}shm_roominfo;

typedef struct
{
unsigned int RoomID;
shm_roominfo s_room;
}send_roominfo;
  • 大厅里需要的消息协议设计:
1
2
3
4
5
6
7
typedef struct
{
unsigned int UserID;
unsigned int RoomID;
int TableID;
int SeatID;
}ClientToHall_TableNumMsg;
  • 桌子上的个消息协议设计:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
typedef struct
{
unsigned int UserID;
unsigned int RoomID;
int state; //-2--leave room;-1--leave table normally;0--in table,no ready;1--ready;2--gaming;3--escape
}ClientToTable_StateMsg;

typedef struct
{
int turn;
int color;//1 -- black; 2 -- white
}TableToClient_StateMsg;

typedef struct
{
int x;
int y;
int is_win;
}TableToClient_GameDataMsg;

typedef struct
{
unsigned int UserID;
unsigned int RoomID;
int x;
int y;
}ClientToTable_GameDataMsg;

typedef struct
{
unsigned int UserID;
unsigned int RoomID;
char buffer[50];
}ClientToTable_ChatMsg;

详细设计之—— Socket 发包形式统一

基础发包(收包亦然)均为发送字节流:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
int ag_writen(SOCKET sock, const void *vptr, size_t n)
{

size_t nleft;
size_t nwritten;
const char *ptr;

ptr = (const char *)vptr;
nleft = n;
while (nleft > 0) {
nwritten = send(sock, ptr, nleft, 0);
if (nwritten <= 0) {
if (errno == EINTR)
nwritten = 0; /* and call write() again */
else
return (-1); /* error */
}
nleft -= nwritten;
ptr += nwritten;
}
return (n);
}

/* Read "n" bytes from a descriptor. */
int ag_readn(SOCKET sock, void *vptr, size_t n)
{

size_t nleft;
size_t nread;
char *ptr;

ptr = (char *)vptr;
nleft = n;
while (nleft > 0) {
nread = recv(sock, ptr, nleft, 0);
if (nread < 0) {
if (errno == EINTR)
nread = 0; /* and call read() again */
else if (errno == EAGAIN)//added by Ask u
break; //added by Ask u
else
return (-1);
} else if (nread == 0)
break; /* EOF */

nleft -= nread;
ptr += nread;
}
return (n - nleft); /* return >= 0 */
}

接收或发送错误时,服务器端直接关闭相应客户端的套接字,以保护服务器。

详细设计之——服务器的设计

配置文件的设计

格式:字段名 + “=” + 字段值,包括数据库连接配置文件、房间配置文件等。

ccache 库的应用

一个共享内存的开源库,修改一些自己系统需求不同的部分。

MySQL 的操作封装

将自己常用的几个操作所用的API封装到一起:

1
2
3
4
void Sql_connect(MYSQL *mysql, const char *host,const char *user, const char *pwd, const char *db);
void RunSelect(MYSQL *mysql, MYSQL_RES *res, const char *sqlstr, char *returned);
void RunSql(MYSQL *mysql, const char *sqlstr);
void Sql_close(MYSQL *mysql);

主程序设计

(1)主线程——分析配置及监听线程:

1
int main(int argc, char **argv)

将配置文件里的信息读到程序里,并开始监听连接。主线程建立两个 cache,用来分别存放在线玩家信息和房间列表及在线信息。

(2)登录和注册线程池:

1
2
3
4
5
6
7
8
9
void *thread_main(void *args);
{
switch(MSGTYPE)
{
case 1:
case 2:
//……
}
}

这个线程池的线程数量通常为 CPU 的数量乘以 2。由于各个服务器的 CPU 数量不一样,将这个设计为启动服务器程序时的一个参数。

(3)维护游戏房间列表线程:

1
void *thread_get_roomlist(void *args);

此线程定时遍历房间 cache,刷新在线人数,并遍历用户列表,将在线房间信息发送出去。

(4)游戏逻辑线程:

1
2
3
4
5
6
7
8
9
void *thread_epoll_gamelogic(void *args);
{
switch(MSGTYPE)
{
case 1:
case 2:
//……
}
}

接收、处理、发送进出房间、进出桌子、游戏数据、聊天的消息。

(5)统一出错处理的函数:

1
void LogOut(int fd);

详细设计之——客户端的设计

大厅进程的设计

(1)主逻辑线程:

  • 处理所有客户端大厅的界面操作及发送消息到服务器;
  • 处理接收通信包线程转发来的消息。
  • 处理五子棋进程发送过来的数据及转发过来的服务器消息。
  • 启动五子棋进程时杀死接收通信包线程;
  • 关闭五子棋进程时重启接收通信包线程。

(2)接收通信包线程:

1
2
3
4
5
6
switch(MSGTYPE)
{
case 1:
case 2:
//……
}

用于接收服务器发过来的消息,根据消息类型直接处理或者转发给主线程。

(3)给五子棋进程的数据:
通过管道,传递子进程初始化的平台需要数据。

五子棋进程

(1)主逻辑线程:

  • 处理大厅进程发送过来的数据;
  • 处理五子棋界面上的所有操作及发送消息到服务器;
  • 处理接收通信包线程发送过来的服务器消息、中间处理后的数据。

(2)接收通信包线程:

1
2
3
4
5
6
switch(MSGTYPE)
{
case 1:
case 2:
//……
}

中间处理服务器发送过来的消息及转发一些消息到主线程。

(3)给大厅的一些平台数据转发:
通过给大厅进程发送 WM_COPYDATA 消息来转发同房间玩家的桌子信息实时刷新及房间列表在线列表。

客户端套接字的共用

为了降低开销和简化同步,客户端的两个进程公用同一个套接字,具体设计为:
(1)大厅进程建立一个套接字与服务器建立连接,当新建五子棋进程时,获得子进程的进程号,并发送给大厅进程。
(2)大厅进程根据这个子进程(五子棋进程)号,用函数 WSADuplicateSocket 将自己的套接字复制一个并传递给子进程。子进程利用这个父进程传递过来的套接字跟服务器进行通信。

客户端的数据同步

当大厅进程里建立五子棋进程后,立即关闭大厅自己的接收网络包线程;五子棋进程开启接收网络包线程。

当五子棋玩毕,关掉此进程时,大厅检测到子进程的关闭,立即重启大厅的接收网络包线程。

这样可以屏蔽共享套接字跟服务器通信的互相干扰。

文章目录
  1. 1. 说明
  2. 2. 系统功能模块图
  3. 3. 系统流程图
  4. 4. 系统数据流图
  5. 5. 负载均衡策略图
  6. 6. 概要设计
  7. 7. E-R 图
  8. 8. 数据库表设计
  9. 9. 详细设计之——通信协议的制订
    1. 9.1. 枚举所有要用到的消息标识
    2. 9.2. 各个消息的数据结构设计
  10. 10. 详细设计之—— Socket 发包形式统一
  11. 11. 详细设计之——服务器的设计
    1. 11.1. 配置文件的设计
    2. 11.2. ccache 库的应用
    3. 11.3. MySQL 的操作封装
    4. 11.4. 主程序设计
  12. 12. 详细设计之——客户端的设计
    1. 12.1. 大厅进程的设计
    2. 12.2. 五子棋进程
    3. 12.3. 客户端套接字的共用
    4. 12.4. 客户端的数据同步