2.6 做留言板,使用数据库

游戏服务端的另一项重要功能是保存玩家数据,Skynet提供了操作MySQL数据库、MongoDB数据库的模块。

2.6.1 功能需求

如图2-18所示,客户端发送“set XXX”命令时,程序会把留言“XXX”存入数据库,发送“get”命令时,程序会把整个留言板返回给客户端。

图2-18 留言板示意图

2.6.2 学习数据库模块

skynet.db.mysql模块提供操作MySQL数据库的方法,如表2-7所示。

表2-7 连接MySQL数据库的API

2.6.3 准备数据库

服务端与MySQL通过TCP相连,获取数据时,服务端会以特定形式发送形如“查询id为101的玩家数据”的消息,MySQL收到消息后,回应查到的数据。启动留言板程序前,需要先开启MySQL数据库,预先创建数据表。

知识拓展:完整地安装和启动MySQL数据库包括如下步骤:

(1)安装MySQL数据库

在CentOS下执行如下三条指令,下载MySQL5.7并安装它。如果提示系统找不到wget或rpm,请先用yum install XXX安装它们。


wget 'https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm'
rpm -Uvh mysql57-community-release-el7-11.noarch.rpm
yum install mysql-community-server

(2)启动MySQL数据库

执行如下指令启动MySQL数据库。


service mysqld start

(3)查看默认数据库密码

新版MySQL(5.7之后)出于安全考虑,要求用户重设密码,之后才能正常操作。要重设密码,就要先登录数据库,再执行修改密码的指令。要登录数据库,就得用默认的密码。那么,默认的密码是什么?执行如下指令打开MySQL的日志文件。


vim/var/log/mysqld.log

会看到其中有一句


A temporary password is generated for root@localhost: qeWupq-Bp4K5

其中的“qeWupq-Bp4K5”就是首次登录时要输入的密码,这是个随机数,先记下它。

(4)修改密码

输入如下指令登录数据库,其中-u后面的root代表用户名,-p后面的字符代表初始密码。


mysql -h127.0.0.1 -uroot -pqeWupq-Bp4K5

在MySQL的命令行中输入如下指令,其中的12345678aB-代表新密码。密码必须是8位以上,且含有数字、字母和特殊字符。


mysql> alter user 'root'@'localhost' identified by "12345678aB-";

(5)开放权限

出于安全考虑,默认情况下,新版MySQL只开放本地root权限,即只能在本机登录。由于我们只是做实验,不需要考虑安全性问题,因此可以输入如下语句,让其他电脑连接(安全的做法是新建一些受限账号,对这些账号仅开放所需的权限)。


mysql> use mysql;
mysql> update user set host='%' where user='root';   
mysql> flush privileges;

(6)测试

重新连接数据库,输入如下SQL语句显示MySQL数据库中的库。如能成功,说明一切就绪。


mysql> show databases;

图2-19展示了MySQL数据库的结构。一个MySQL包含多个库,库中包含多个表,每个表包含多个栏位。操作数据库时,需选定某个库(如表2-5中mysql.connect的database项),再增删改表中的数据。

图2-19 MySQL数据库的结构

知识拓展:有多种手动操作MySQL数据库的方法。

(1)用命令行操作

使用“mysql-h127.0.0.1-uroot-pXXX”登录数据库,再输入SQL语句即可实现操作,只是不太直观。

(2)用工具操作

可在Windows上安装“Navicat for MySQL”来操作MySQL数据库,它是个可视化的MySQL客户端软件。本节会演示“Navicat for MySQL”的操作方法。

先创建名为message_board的库。如图2-20所示,打开Navicat,填入MySQL数据库的IP、端口、用户名和密码,登录数据库(如果连接失败,除了检查用户名、密码外,还需设置云服务器的安全策略,开放3306端口)。

图2-20 用Navicat连接数据库

右键单击连接名,选择“新建数据库”,命名为“message_board”。选择新创建的数据库,创建名为“msgs”的表,表结构如图2-21所示。msgs表包含id和text两个栏位。id栏位为int类型,将其设置为主键,不允许空值,并勾选自动递增;text栏位设为text类型。

图2-21 msgs表结构

在msgs表里添加几条数据(用于测试),如图2-22所示。

图2-22 在msgs表里添加测试数据

2.6.4 代码实现

这里先不直接做留言板,而是写个小程序尝试测试数据库读写功能,以便融会贯通。编写代码2-8所示的主服务,功能如下:

·调用mysql.connect连接MySQL,并选用message_board库。

·使用db:query("insert ...")向数据库插入一条数据,在text栏位插入字符串“hehe”。

·使用db:query("select ...")查询数据库,将结果保存到res中,遍历它并打印出来。

代码2-8 examples/Pmain.lua

(资源:Chapter2/5_mysql.lua)


local skynet = require "skynet"
local mysql = require "skynet.db.mysql"

skynet.start(function()
    --连接
    local db=mysql.connect({
        host="39.100.116.101",
        port=3306,
        database="message_board",
        user="root",
        password="7a77-788b889aB",
        max_packet_size = 1024 * 1024,
        on_connect = nil
    })
    --插入
    local res = db:query("insert into msgs (text) values (\'hehe\')")
    --查询
    res = db:query("select * from msgs")
    --打印
    for i,v in pairs(res) do
        print ( i," ",v.id, " ",v.text)
    end
end)

运行服务端,能看到如图2-23所示的输出,其中“hello”和“good”是手动添加的数据,“hehe”是主服务添加的数据。

图2-23 程序运行结果

现在将网络编程和数据库操作结合起来,完成本节的需求。在代码2-9中,新增变量db用于保存数据库对象;服务启动后,开启网络监听,并发起数据库连接。

代码2-9 examples/Pmain.lua中的部分内容

(资源:Chapter2/5_messageboard.lua)


local skynet = require "skynet"
local socket = require "skynet.socket"
local mysql = require "skynet.db.mysql"

local db = nil

skynet.start(function()
    --网络监听
    local listenfd = socket.listen("0.0.0.0", 8888)
    socket.start(listenfd ,connect)
    --连接数据库
    db=mysql.connect({
            host="127.0.0.1",
            port=3306,
            database="message_board",
            user="root",
            password="12345678aB+",
            max_packet_size = 1024 * 1024,
            on_connect = nil
        })
end)

新连接的回调方法connect如代码2-10所示,它分成两个部分:

·如果客户端发送的数据是“get\r\n”,则查询数据库,然后将结果一条条地发回。

·如果客户端发送的是“set XXX”(为了简洁,假设用户会输入正确的数据),则用正则表达式将字符串XXX提取出来(变量data),然后插入数据库中。

代码2-10 examples/Pmain.lua中connect方法的部分代码


function connect(fd, addr)
    ……
    --正常接收
    if readdata ~= nil then
        --返回留言板内容
        if readdata == "get\r\n" then
            local res = db:query("select * from msgs")
            for i,v in pairs(res) do
                socket.write (fd, v.id.." "..v.text.."\r\n")
            end
        --留言
        else
            local data = string.match( readdata, "set (.-)\r\n")
            db:query("insert into msgs (text) values (\'"..data.."\')")
        end
   ……
end

说明:“\r\n”即换行符,在telnet中输入字符串,它会把换行符也发给服务端。

2.6.5 运行结果

开启服务端,再用telnet连接。客户端的运行结果如图2-24所示:输入get命令,能看到所有留言;输入set lpy表示插入留言;再输入get可获取相应信息。

图2-24 留言板客户端的运行结果