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

发布于:2016-11-17 23:07,阅读数:3385,点赞数:27


> [「Perfect」](https://github.com/PerfectlySoft/Perfect)是一款基于 Swift 的服务端网络框架,并支持在 Linux 系统上部署。
>
> [「我的博客」](http://enumsblog.com)的服务端就是使用`Perfect`框架搭建的。

# 引言

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

简单的 Demo 已经被写烂了,本文除了简单的数据接口和Web服务的实现方法以外,还会涉及到`MySQL`数据库相关功能的实现,以及`C/C++`代码的接入方法。最后是在 Linux 服务器上部署的过程。所有使用的工具都由官方[「PerfectlySoft」](https://github.com/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」](https://github.com/PerfectlySoft/PerfectTemplate)使用了`SPM(Swift Package Manager)`进行项目管理。本文的项目将会从修改这个官方例子开始。将项目克隆下来以后会发现并没有Xcode的工程文件,但是根目录下有一个`Package.swift`文件,它长这样:

```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 进入该目录,使用命令:

```bash
$ swift build
```

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

```swift
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 工程文件。打开终端,在工程目录下使用:

```bash
$ swift package generate-xcodeproj
```

`PerfectTemplate.xcodeproj`工程文件就建立好了。确保已经安装`MySQL`的情况下可以打开工程编译运行了。没有安装MySQL?编译报错?[「这篇文章」](http://www.jianshu.com/p/6496e53f1690)有你想要的答案。

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

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

- 配置服务器

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

- 添加接口的方法

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

```swift
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:在连接数据库之前记得设置客户机的编码格式,否则将会看到中文全是问号。这里的`utf8mb4`是`utf8`的扩展编码。**


```swift
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`循环取出数据装入数组中。

```swift
@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++`代码

使用桥接的方法。在我的[「这篇文章」](http://www.jianshu.com/p/b4529917dd99)中提到了桥接Objective-C代码。但是不幸的是[「官方网站」](https://swift.org/blog/swift-linux-port/)提到:

>**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`:

```c++
#include "include/CCode.h"
```

`CCode.h`:

```c++
#include "../CWrap.h"
```

`CWrap.h`:

```c++
#ifdef __cplusplus
extern "C" {
#endif

void initObj();
void objSayHello();

#ifdef __cplusplus
}
#endif
```

`CWrap.cpp`:

```c++
#include "CWrap.h"
#include "MyObject.h"

MyObject * obj;

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

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

`MyObject.h`:

```c++
#include <stdio.h>

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

`MyObject.cpp`:

```c++
#include "MyObject.h"

MyObject::MyObject() {

}
MyObject::~MyObject() {

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

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

```swift
initObj()
objSayHello()
```

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

# Linux 部署

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

- Swift 环境

```bash
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 的版本号了,下面步骤将克隆官方的例子来验证环境是否正常:

```bash
//没安装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`解决:

```bash
sudo apt-get install curl
```

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

- MySQL 环境

在[「这里」](http://dev.mysql.com/get/mysql-apt-config_0.8.0-1_all.deb)下载`mysql-apt-config`,安装时选择5.6版本。为什么要选择5.6版本?

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

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

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

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

```bash
sudo vi /etc/mysql/my.cnf
```

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

```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.swift`的`targets`中加入:

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

最后在`main.swift`中加入:

```swift
#if os(Linux)
import CCode
#endif
```
即可完成 C/C++ 代码接入。

- 关于时区
-
**坑6:Linux 下的时区识别问题。**

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

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

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

```swift
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 坑坑洼洼的道路上前进的人一点参考。


评论:0条


返回列表

返回归档

返回主页