Perfect 网络框架的应用 —— 基本方法、MySQL连接和Cpp代码接入

发布于: 2016-11-17 23:07
阅读: 2978
评论: 0
喜欢: 27

「Perfect」是一款基于 Swift 的服务端网络框架,并支持在 Linux 系统上部署。

「我的博客」的服务端就是使用Perfect框架搭建的。

引言

随着 Swift 语言开源,它被带到了 Linux 端。服务端软件广泛部署于 Linux 服务器中,Swift 被带到 Linux 端以后,基于 Swift 开发服务端软件也不再是梦想了。几款 Swift 语言的服务端框架应运而生,Perfect就是其中一款。关于Perfect的表现,可以看这篇文章:「不服跑个分 - 顶级 Swift 服务端框架对决 Node.js」

简单的 Demo 已经被写烂了,本文除了简单的数据接口和Web服务的实现方法以外,还会涉及到MySQL数据库相关功能的实现,以及C/C++代码的接入方法。最后是在 Linux 服务器上部署的过程。所有使用的工具都由官方「PerfectlySoft」提供。

开发环境

仅在以下环境测试通过,运行环境方面处处是坑,还望大家小心。

  • 开发环境:macOS 10.12 + Xcode 8.1 with Swift 3.0 + MySQL 5.7
  • 编译部署:Ubuntu 14.04 64-bit with Swift Package Manager + MySQL 5.6

服务端开发

项目首先将在 macOS 中开发,然后再讨论如何在 Linux 中编译部署。 官方提供的例子「PerfectTemplate」使用了SPM(Swift Package Manager)进行项目管理。本文的项目将会从修改这个官方例子开始。将项目克隆下来以后会发现并没有Xcode的工程文件,但是根目录下有一个Package.swift文件,它长这样:

import PackageDescription

let package = Package(
    name: "PerfectTemplate",
    targets: [],
    dependencies: [
        .Package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", majorVersion: 2, minor: 0)
    ]
)

从代码中可以看出项目依赖了Perfect-HTTPServer,然而此时我们的源码中是不包含这个库的。怎么办呢,要手动去克隆HTTPServer库吗?并不用。打开终端,cd 进入该目录,使用命令:

$ swift build

SPM 会自动从git上下载依赖,建立Package文件夹。对了,我们要用到MySQL相关的库,在依赖中需要加入一行内容。加完以后它是这个样子的:

import PackageDescription

let package = Package(
    name: "PerfectTemplate",
    targets: [],
    dependencies: [
        .Package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git",majorVersion: 2, minor: 0),
        .Package(url:"https://github.com/PerfectlySoft/Perfect-MySQL.git",majorVersion: 2, minor: 0)
    ]
)

坑1:clone过程中极易卡住,就算是翻出去了依然如此。如果在clone过程中卡住了,打开当前Package文件夹中当前克隆的项目的文件夹。如果该文件夹里有代码,clone其实已经完成了,ctrl+c 强制结束命令,然后再次build即可。

这一步build的目的只是为了下载依赖包,失败了也并不要紧,只要依赖包都下载下来了就可以了。需要使用到的依赖库有:

  • PerfectHTTPServer
  • PerfectHTTP
  • PerfectLib
  • PerfectNet
  • COpenSSL
  • PerfectThread
  • MySQL
  • mysqlclient

检查每一个文件夹内都有代码以后,下一步建立 Xcode 工程文件。打开终端,在工程目录下使用:

$ swift package generate-xcodeproj

PerfectTemplate.xcodeproj工程文件就建立好了。确保已经安装MySQL的情况下可以打开工程编译运行了。没有安装MySQL?编译报错?「这篇文章」有你想要的答案。

坑2:Xcode 请使用8.1版本。8.0在编译过程中会遇到上面链接文章中的问题3,文章底下还能看到我的留言呢,因为我没有找到解决方法。在Xcode 8.1 中不会报错。

到这里应该是可以在 Mac 中跑起官方的例子了。噢,上面链接中的文章帮我把怎么添加数据接口和查询数据库也说完了,我大概再解释一下吧。我们可以直接修改官方例子中的main.swift来构造自己的服务器软件。

  • 配置服务器

IP绑定、端口、根目录等配置在官方的例子中都有,自行查看代码。

  • 添加接口的方法

构造Routes对象,在该对象中调用add方法添加接口,在add方法中有一个回调闭包,在该闭包中处理数据和返回结果。它大概的结构是这样子的:

    let server = HTTPServer.init()
    var routes = Routes.init()
    routes.add(method: ttp.get, uri: "/", handler: { request, response in
        //取得url中的参数,类型是`[(String, String)]`,遍历即可
        let param = request.params()
        //返回数据头
        response.setHeader(.contentType, value: "text/html")
        //返回数据体
        response.appendBody(string: "数据")
        //返回
        response.completed()
    })
    server.addRoutes(routes)
  • 访问数据库

这里使用自带的MySQL类进行访问。

坑3:在连接数据库之前记得设置客户机的编码格式,否则将会看到中文全是问号。这里的utf8mb4utf8的扩展编码。

    private func initDataBase() -> Bool {
        dataMysql = MySQL.init()
        guard dataMysql.setOption(.MYSQL_OPT_RECONNECT, true) == true else {
            log.p("Failure setting option MYSQL_OPT_RECONNECT")
            return false
        }
        guard dataMysql.setOption(.MYSQL_SET_CHARSET_NAME, "utf8mb4") == true else {
            log.p("Failure setting option MYSQL_SET_CHARSET_NAME utf8mb4")
            return false
        }
        if (dataMysql.connect(host: testHost, user: testUser, password: testPassword) == true) {
            return true
        } else {
            log.p("Failure connecting to data server \(testHost)")
            return false
        }
    }

查询结果会被存储在storeResults中,使用下面所示的while循环取出数据装入数组中。

    @discardableResult
    func query(_ s: String) -> [[String?]]? {
        locker.lock()
        guard dataMysql.selectDatabase(named: testSchema) && dataMysql.query(statement: s) else {
            log.p("Failure: \(dataMysql.errorCode()) \(dataMysql.errorMessage())")
            locker.unlock()
            return nil
        }
        let results = dataMysql.storeResults()
        var resultArray = [[String?]]()
        while let row = results?.next() {
            resultArray.append(row)
        }
        locker.unlock()
        return resultArray
    }

最后从数组中取出对应的字符串即可。

  • 接入C/C++代码

使用桥接的方法。在我的「这篇文章」中提到了桥接Objective-C代码。但是不幸的是「官方网站」提到:

Swift without the Objective-C Runtime: Swift on Linux does not depend on the Objective-C runtime nor includes it. While Swift was designed to interoperate closely with Objective-C when it is present, it was also designed to work in environments where the Objective-C runtime does not exist.

在 Linux 中的 Swift 不再支持Objective-C Runtime了,因此我们无法在Linux中使用使用现有的 Objective-C 代码了。但是幸运的是我们可以桥接C代码:

目录结构为:

- bridge.h
- include
    - CCode.h
- MyObject.h
- MyObject.cpp
- CWrap.h
- CWrap.cpp

代码为:

bridge.h

#include "include/CCode.h"

CCode.h

#include "../CWrap.h"

CWrap.h

#ifdef __cplusplus
extern "C"  {
#endif

void initObj();
void objSayHello();

#ifdef __cplusplus
}
#endif

CWrap.cpp

#include "CWrap.h"
#include "MyObject.h"

MyObject * obj;

void initObj() {
    obj = new MyObject();
}

void objSayHello() {
    obj->sayHello();
}

MyObject.h

#include <stdio.h>

class MyObject
{
public:
    MyObject();
    ~MyObject();
    void sayHello();
};

MyObject.cpp

#include "MyObject.h"

MyObject::MyObject() {

}
MyObject::~MyObject() {

}
void MyObject::sayHello() {
    printf("hello\n");
}

经过封装后的C接口直接在 Swift 中调用,像这样:

initObj()
objSayHello()

至此,在 macOS 中整个流程就已经走通了。

Linux 部署

在 Linux 中部署的文章还是比较少,因此在这里打算详细讲一下环境配置过程。测试的环境为 Ubuntu 14.04 64-bit。不要因为服务器配置较低内存较小而选用32位系统。Swift 仅支持64位 Linux 系统。

  • Swift 环境
apt-get update
//安装clang
sudo apt-get install clang libicu-dev
//导入keys
wget -q -O - https://swift.org/keys/all-keys.asc | gpg --import -
//下载对应系统的Swift:https://swift.org/download/#releases
//解压后将目录添加到环境变量:
echo "export PATH=%你的解压目录%/usr/bin:\"\${PATH}\"" >> ~/.basic
source ~/.bashrc
//查看环境变量是否配置正确
swift -version
//Just do it
sudo apt-get install openssl libssl-dev uuid-dev

到这一步应该可以看到 Swift 的版本号了,下面步骤将克隆官方的例子来验证环境是否正常:

//没安装git的先安装git
sudo apt-get install git
git clone https://github.com/PerfectlySoft/PerfectTemplate.git
//cd到该目录后编译,运行
swift build
.build/debug/PerfectTemplate

build过程可能会提示找不到libcurl.so.4之类的,安装curl解决:

sudo apt-get install curl

到这一步应该可以跑起官方的例子了。

  • MySQL 环境

「这里」下载mysql-apt-config,安装时选择5.6版本。为什么要选择5.6版本?

坑4:使用apt安装的MySQL是5.5版本的,是不能用的。现在最新的是5.7版本的,同样也是有问题的。MySQL要安装5.6版本的。

安装完之后update,然后安装MySQL服务即可。

sudo apt-get update
sudo apt-get install mysql-server
sudo apt-get install libmysqlclient-dev

数据库管理方面,如果使用MySQLWorkbench远程登录的话,需要:解除IP绑定,编辑以下文件,将绑定IP字段注释,强制保存:

sudo vi /etc/mysql/my.cnf

在终端登录mysql后执行以下SQL开放权限:

GRANT ALL PRIVILEGES ON *.* TO '你的用户名'@'%' IDENTIFIED BY '你的密码' WITH GRANT OPTION;
  • 服务端代码

Linux 端的 Perfect 依赖是不太一样的,Linux 下会多一个LinuxBridge依赖。因此最佳方法是,在 Linux 上克隆官方的例子,让 SPM 自己下载依赖 ,下载完以后替换掉Source文件夹中的代码,换成自己的服务端程序,编译即可。

坑5:C/C++接入方面,由于在 Linux 中由于没有 Xcode 那样的选项,似乎也没法直接操作编译器参数,因此无法设置桥接头文件。在这里要采用 SPM 的Target方式接入 C/C++ 代码。

Source文件夹中新建SwiftCode文件夹,将 Swift 代码放入。然后新建CCode文件夹,将C/C++ 代码放入。最后在Package.swifttargets中加入:

targets: [Target(name: "SwiftCode", dependencies:["CCode"])],

最后在main.swift中加入:

#if os(Linux)
import CCode
#endif

即可完成 C/C++ 代码接入。

- 关于时区

坑6:Linux 下的时区识别问题。

Swift中DateFormatter提供了格式化时间的功能,但在 Linux 中有时区识别的问题。可以通过添加Locale解决:

    let formatter = DateFormatter.init()
    formatter.locale = Locale.init(identifier: "zh_CN")

在我的 Ubuntu 虚拟机中问题得到了解决,但是云服务器仍然无效。终极的解决办法是,手动设置时差:

    let formatter = DateFormatter.init()
    formatter.timeZone = TimeZone.init(secondsFromGMT: 8 * 3600)

代码管理

由于开发工作在 macOS 中完成,但是 Linux 环境可能在虚拟机中,也可能在云端。来回复制代码固然是很麻烦的一件事。怎样管理两边的代码呢?

事实上 Xcode 有着更直观的文件管理功能,我们完全可以把代码放在 Linux 工程的目录中,在 Xcode中进行引用。但两端的依赖库是不一样的,所以依赖的目录还是要分开放。Linux 工程目录下放完整的工程代码,Xcode 工程目录下放 macOS 端的依赖库,业务代码直接引用 Linux 工程中的源码文件即可。在两端不同的设置上,例如数据库账户等,可以使用预编译命令#if ... #endif进行灵活管理。

结语

Swift 语言在业务开发的过程中效率是非常高的,写的也非常爽。Perfect 是非常优秀的框架,整个服务端的开发过程非常愉快。倒是在 Linux 部署问题上踩了不少坑。希望本文能给正在 Perfect 坑坑洼洼的道路上前进的人一点参考。


Thanks for reading.

All the best wishes for you! 💕