swift调用go

2020-05-27 17:00:53 Koala_Ivy 阅读数 145

Golang开发gRpc服务

  1. 首先本机安装proto客户端,并配置环境变量
  2. 创建golang项目 结构如下图

3.新建一个helloworld.proto文件,内容如下

syntax = "proto3";

package helloworld;
option java_package = "com.example.grpc.helloworld";
option java_multiple_files = true;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

这个文件稍后还会用在springboot项目中

4.使用命令将proto文件生成成go文件,命令如下

# proto文件编译为go文件命令
// protoc --go_out=plugins=grpc:{输出目录}  {proto文件}   
protoc --go_out=plugins=grpc:./helloworld/ ./helloworld.proto

5.在server文件夹下 新建main.go,作为gRpc的服务端

/*
@Time : 2020/5/25 15:01
@Author : wkang
@File : server
@Description:
*/
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
	"log"
	"net"
	pb "../helloworld"
)

const (
	port = ":50051"
)

// server is used to implement helloworld.GreeterServer.
type server struct{}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	fmt.Println("######### get client request name :"+in.Name)
	return &pb.HelloReply{Message: "GO gRpc服务的响应为: Hello " + in.Name}, nil
}

func main() {
	lis, err := net.Listen("tcp", port)
	fmt.Println(port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	// Register reflection service on gRPC server.
	reflection.Register(s)
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

6.服务端创建成功,我们在server文件夹下运行终端 输入命令启动服务

go run main.go

7.可以看到服务启动成功,我打印了一下端口号

8.我们可以初始化一个客户端,来看看服务端是否正常,在client文件夹下,添加main.go,代码如下

/*
@Time : 2020/5/25 15:01
@Author : wkang
@File : server
@Description:
*/
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
	"log"
	"net"
	pb "../helloworld"
)

const (
	port = ":50051"
)

// server is used to implement helloworld.GreeterServer.
type server struct{}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	fmt.Println("######### get client request name :"+in.Name)
	return &pb.HelloReply{Message: "GO gRpc服务的响应为: Hello " + in.Name}, nil
}

func main() {
	lis, err := net.Listen("tcp", port)
	fmt.Println(port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	// Register reflection service on gRPC server.
	reflection.Register(s)
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

9.在client文件夹下 启动客户端服务,可在命令行界面看到响应

10.服务端终端显示如下图

当前服务正常~

Springboot创建gRpc服务端和客户端

1.首先我们新建一个sprongboot项目,修改pom文件如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>grpc-java-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>grpc-java-demo</name>
    <description>Demo project for Spring Boot gRpc</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <grpc-spring-boot-starter.version>3.0.0</grpc-spring-boot-starter.version>
        <os-maven-plugin.version>1.6.1</os-maven-plugin.version>
        <protobuf-maven-plugin.version>0.6.1</protobuf-maven-plugin.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>io.github.lognet</groupId>
            <artifactId>grpc-spring-boot-starter</artifactId>
            <version>${grpc-spring-boot-starter.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>${os-maven-plugin.version}</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>${protobuf-maven-plugin.version}</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.16.1:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

2.将开始创建好的proto文件复制到项目中,目录结构如下

3.mvn compile之后 proto插件会根据当前proto文件生成对应的类,如下图

4.接下来的四个文件 是springboot的启动类 grpc客户端 grpc服务端 和一个控制器,用来测试效果

5.GrpcJavaDemoApplication 启动文件

package com.example.grpcjavademo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GrpcJavaDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(GrpcJavaDemoApplication.class, args);
    }

}

6.HelloWorldClient客户端文件

package com.example.grpcjavademo.grpc;

import com.example.grpc.helloworld.GreeterGrpc;
import com.example.grpc.helloworld.HelloReply;
import com.example.grpc.helloworld.HelloRequest;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class HelloWorldClient {

  private static final Logger LOGGER =
      LoggerFactory.getLogger(com.example.grpcjavademo.grpc.HelloWorldClient.class);

  private GreeterGrpc.GreeterBlockingStub helloWorldServiceBlockingStubForGo;
  private GreeterGrpc.GreeterBlockingStub helloWorldServiceBlockingStubForJava;

  @PostConstruct
  private void init() {
    //初始化一个连接Go grpc的客户端
    ManagedChannel managedChannelForGo = ManagedChannelBuilder
        .forAddress("127.0.0.1", 50051).usePlaintext().build();

    helloWorldServiceBlockingStubForGo =
            GreeterGrpc.newBlockingStub(managedChannelForGo);

    //初始化一个连接Java grpc的客户端
    ManagedChannel managedChannelForJava = ManagedChannelBuilder
            .forAddress("127.0.0.1", 6565).usePlaintext().build();

    helloWorldServiceBlockingStubForJava =
            GreeterGrpc.newBlockingStub(managedChannelForJava);
  }

  public String sayHello(String name,String server) {
    HelloRequest person = HelloRequest.newBuilder().setName(name).build();
    LOGGER.info("client sending {}", person);

    String res="";
    if (server=="go"){
      HelloReply greeting =
              helloWorldServiceBlockingStubForGo.sayHello(person);
      LOGGER.info("client received {}", greeting);
      res=greeting.getMessage();
    }

    if (server=="java"){
      HelloReply greeting =
              helloWorldServiceBlockingStubForJava.sayHello(person);
      LOGGER.info("client received {}", greeting);
      res=greeting.getMessage();
    }



    return res;
  }
}

在这里我们初始化了两个客户端,分别连接不同的端口,一个用来连接测试上面Go创建的grpc服务,一个用来连接测试java创建的gRpc服务

7.服务端文件HelloWorldServiceImpl

package com.example.grpcjavademo.grpc;


import com.example.grpc.helloworld.GreeterGrpc;
import com.example.grpc.helloworld.HelloReply;
import com.example.grpc.helloworld.HelloRequest;
import io.grpc.stub.StreamObserver;
import org.lognet.springboot.grpc.GRpcService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@GRpcService
public class HelloWorldServiceImpl
    extends GreeterGrpc.GreeterImplBase {

  private static final Logger LOGGER =
      LoggerFactory.getLogger(com.example.grpcjavademo.grpc.HelloWorldServiceImpl.class);

  @Override
  public void sayHello(HelloRequest request,
                       StreamObserver<HelloReply> responseObserver) {
    LOGGER.info("server received {}", request);

    String message = "Java gRpc服务的响应为: Hello " + request.getName() + "!";
    HelloReply greeting =
            HelloReply.newBuilder().setMessage(message).build();
    LOGGER.info("server responded {}", greeting);

    responseObserver.onNext(greeting);
    responseObserver.onCompleted();
  }
}

8.用来测试的控制器

package com.example.grpcjavademo.controller;

import com.example.grpcjavademo.grpc.HelloWorldClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/home")
public class HomeController {
    @Resource
    private HelloWorldClient helloWorldClient;

    //连接Go的gRpc服务端
    @GetMapping("/testgo")
    public String testGo(){
        String s = helloWorldClient.sayHello("World","go");
        return s;
    }

    //连接java(当前项目)的gRpc服务端
    @GetMapping("/testjava")
    public String testJava(){
        String s = helloWorldClient.sayHello("World","java");
        return s;
    }
}

 

9.启动项目,我们使用postman来访问控制器提供的两个接口

访问testgo时,我们通过客户端请求了Go语言开发的服务端

接下来我们访问testjava

我们请求了当前项目下启动的grpc。

 

至此完结,因为不复杂,所以没有写的很详细,可以查看两个项目的源码

go gRpc客户端和服务端的demo https://github.com/flyingkoala/grpc-go-demo

springboot  gRpc客户端和服务端的demo https://github.com/flyingkoala/grpc-java-demo

 

最后ps一下踩到的坑

1.go语言开发grpc用到的框架,因为被qiang的原因,可能在下载的过程中麻烦一些,方法可以自行百度一下,无非就是gopm、代理、git下载到本地自行安装这几种。

2.proto文件保持一致。

 

2018-04-17 18:06:55 u012359453 阅读数 1156

前言

Ceph 是一个高性能、可靠、可扩展的开源存储平台。它是一个自由的分布式存储系统,提供了对象、块和文件级存储的接口,并可以不受单点故障的影响而运行。上周调研了CEPH文件集群的优劣性,准备做一个实际搭建的性能测试,在物理机上虚了多个ubuntu出来,模拟多台物理机集群,参考网络资源,搭建起来的,但是有遇到一些错误,可能对于接触过linux的人来说都不算很严重的难题,这里做个记录。本文参考:https://linux.cn/article-8182-1.html, 但遇到了一些错误,记录搭建与解决错误是本文的主旨,错误处理在最后一节。

前提

1:5个安装了 Ubuntu 16.04 的服务器节点
2:所有节点上的 root 权限
(可选前提,如果配置osd为逻辑分区,需要在linux机器上挂一块逻辑分区,供格式化osd使用)
3:osd机器上sdb空闲分区

配置所有节点

配置5个节点来准备安装CEPH集群软件,所以你必须拥有root权限,在5台机器上都安装ssh-server。
1,创建cephuser用户,统一用户名和密码(tip:在所有节点都运行如下指令),

useradd -m -s /bin/bash cephuser
passwd cephuser

创建完新用户后,我们需要给 cephuser 配置无密码的 sudo 权限。这意味着 cephuser可以不先输入密码而获取到 sudo 权限运行:

echo "cephuser ALL = (root) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/cephuser
chmod 0440 /etc/sudoers.d/cephuser
sed -i s'/Defaults requiretty/#Defaults requiretty'/g /etc/sudoers

2,安装 NTP 来同步所有节点的日期和时间。先运行 ntpdate 命令通过 NTP 设置日期。我们将使用 US 池的 NTP 服务器。然后开启并使 NTP 服务在开机时启动。

sudo apt-get install -y ntp ntpdate ntp-doc
ntpdate 0.us.pool.ntp.org
hwclock --systohc
systemctl enable ntp
systemctl start ntp

3,如果你和我一样在虚拟机里面搭建集群,那么附加运行:

sudo apt-get install -y open-vm-tools

因为ceph-deploy等需要python环境,所以安装python环境,和依赖包parted:

sudo apt-get install -y python python-pip parted

4,编辑hosts文件(vi /etc/hosts):

# ceph-admin
192.168.40.16        mlv1-VirtualBox
# ceph-mon1
192.168.40.97        mlv2-VirtualBox
# ceph-osd1
192.168.40.106       mlv4-VirtualBox
# ceph-osd2
192.168.40.100       mlv6-VirtualBox
# ceph-gateway (client)
192.168.40.95        mlv3-VirtualBox

这里hostname最好和你集群机器的主机名一样,我就因为不一样在ssh安装ceph到其他节点的时候报错了。

配置SSH服务器

这一步的目的是配置admin节点,完成admin节点无密码访问其他节点,首先登录到ceph-admin节点:

ssh root@mlv1-VirtualBox
su - cephuser

管理节点用来安装配置所有集群节点,所以 ceph-admin 上的用户必须有不使用密码连接到所有节点的权限。我们需要为 ‘ceph-admin’ 节点的 cephuser 用户配置无密码 SSH 登录权限。
1, 生成密钥:

ssh-keygen
# 提示输入密码的时候请回车,让密码为空

2,为 ssh 创建一个配置文件

vim ~/.ssh/config

我的配置文件如下:

Host ceph-admin
        Hostname  mlv1-VirtualBox
        User cephuser

Host mon1
        Hostname  mlv2-VirtualBox
        User cephuser

Host ceph-osd1
        Hostname  mlv4-VirtualBox
        User cephuser

Host ceph-osd2
        Hostname  mlv6-VirtualBox
        User cephuser

Host ceph-client
        Hostname  mlv3-VirtualBox
        User cephuser

保存退出,并赋予文件权限,接着通过 ssh-copy-id 命令增加密钥到所有节点。

chmod 644 ~/.ssh/config
ssh-keyscan ceph-osd1 ceph-osd2 ceph-client mon1 >> ~/.ssh/known_hosts
ssh-copy-id ceph-osd1
ssh-copy-id ceph-osd2
ssh-copy-id mon1

当请求输入密码时输入你的 cephuser 密码,完成以上配置之后admin节点就可以访问集群别的机器了,顺带截个图:
image

Ubuntu 防火墙配置

出于安全原因,我们需要在服务器打开防火墙。我们更愿使用 Ufw(不复杂防火墙)来保护系统,这是 Ubuntu 默认的防火墙。在这一步,我们在所有节点开启 ufw,然后打开 ceph-admin、ceph-mon 和 ceph-osd 需要使用的端口。
1,admin节点(安装ufw包)

ssh root@ceph-admin
sudo apt-get install -y ufw
# 打开 80,2003 和 4505-4506 端口,然后重启防火墙,开机启动。
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 2003/tcp
sudo ufw allow 4505:4506/tcp
sudo ufw enable

2,其他节点防火墙配置

ssh ceph-osd1
sudo apt-get install -y ufw
sudo ufw allow 22/tcp
sudo ufw allow 6800:7300/tcp
sudo ufw enable

这里列举osd1的配置,其他节点完全一样。

配置OSD节点

在我的osd上,我在虚拟机上分配了一个逻辑分区sdb,供ceph存数据使用,从 ceph-admin 节点,登录到所有 OSD 节点,然后格式化 /dev/sdb 分区为 XFS 文件系统,在osd的两个机器上运行如下指令:

# 检查分区表
sudo fdisk -l /dev/sdb
# 格式化/dev/sdb 分区为 XFS 文件系统,使用 parted 命令创建一个 GPT 分区表。
sudo parted -s /dev/sdb mklabel gpt mkpart primary xfs 0% 100%
sudo mkfs.xfs -f /dev/sdb
# 查看成果
# 显示磁盘可用大小
sudo fdisk -s /dev/sdb
# 显示sdb文件系统格式,现在应该是xfs
sudo blkid -o value -s TYPE /dev/sdb

创建CEPH集群

回到admin节点

ssh root@ceph-admin
su - cephuser

1,用 pip 命令在 ceph-admin 节点安装 ceph-deploy.

sudo pip install ceph-deploy

2,使用ceph-deploy创建新集群

# 创建一个配置文件目录
mkdir cluster 
cd cluster
# ceph-deploy 命令通过定义监控节点 mon1 创建一个新集群。
# 命令将在集群目录生成 Ceph 集群配置文件 ceph.conf
ceph-deploy new mon1

接下来我们需要更改生成的配置文件

vi ceph.conf

在 [global] 块下,粘贴下面的配置。

# 我们的ip地址
public network = 192.168.40.0/24
# 默认备份数 2
osd pool default size = 2

保存文件并退出编辑器,如果这里出现了错误,请查看本文最后一节。

3,安装 Ceph 到所有节点
现在用一个命令从 ceph-admin 节点安装 Ceph 到所有节点

ceph-deploy install ceph-admin ceph-osd1 ceph-osd2 mon1

命令将自动安装 Ceph 到所有节点:mon1、osd1-3 和 ceph-admin - 安装将花一些时间,建议更换ceph安装源,执行如下指令:

export CEPH_DEPLOY_REPO_URL=http://mirrors.163.com/ceph/debian-jewel  
export CEPH_DEPLOY_GPG_URL=http://mirrors.163.com/ceph/keys/release.asc

现在到 mon1 节点部署监控节点,这一步也很可能报错,报错请查看最后一节。

ceph-deploy mon create-initial

命令将创建一个监控密钥,用 ceph 命令检查密钥。

ceph-deploy gatherkeys mon1

增加 OSD 到集群

在所有节点上安装了 Ceph 之后,现在我们可以增加 OSD 守护进程到该集群。OSD 守护进程将在磁盘 /dev/sdb 分区上创建数据和日志 。
检查所有 osd 节点的 /dev/sdb 磁盘可用性,应该看到是xfs格式可用磁盘。

ceph-deploy disk list ceph-osd1 ceph-osd2

下面,在所有 OSD 节点上用 zap 选项删除该分区表,这个命令将删除所有 Ceph OSD 节点的 /dev/sdb 上的数据。

ceph-deploy disk zap ceph-osd1:/dev/sdb ceph-osd2:/dev/sdb

现在准备所有 OSD 节点,请确保结果没有报错。

ceph-deploy osd prepare ceph-osd1:/dev/sdb ceph-osd2:/dev/sdb 

当你看到log里面有:host ceph-osd2 is now ready forosd use。那么可以进行激活了,激活之前我们查看一下osd的sdb分区:
image

我们激活data分区就行(如果命令报错就activate sdb):

ceph-deploy osd activate ceph-osd1:/dev/sdb1 ceph-osd2:/dev/sdb1

接下来,部署管理密钥到所有关联节点。

ceph-deploy admin ceph-admin mon1 ceph-osd1 ceph-osd2

在所有节点运行下面的命令,改变密钥文件权限。

sudo chmod 644 /etc/ceph/ceph.client.admin.keyring

Ceph 集群在 Ubuntu 16.04 已经创建完成。

测试集群

看看ceph运行状态:

# 登录到任何节点
ssh mon1
sudo ceph health
sudo ceph -s

截图
image

配置网关

1,安装ceph对象网关
我们之前配置了client节点,这里我们把gateway配置到client节点,在你的管理节点的工作目录下,给 Ceph 对象网关节点安装Ceph对象所需的软件包:

ceph-deploy install --rgw ceph-client

ceph-common 包是它的一个依赖性,所以 ceph-deploy 也将安装这个包。 ceph 的命令行工具就会为管理员准备好。为了让你的 Ceph 对象网关节点成为管理节点,可以在管理节点的工作目录下执行以下命令:

ceph-deploy admin ceph-client

2,新建网关实例
在你的管理节点的工作目录下,使用命令在 Ceph 对象网关节点上新建一个 Ceph对象网关实例:

ceph-deploy rgw create ceph-client

在网关服务成功运行后,你可以使用未经授权的请求来访问端口 7480 ,你可以在局域网里http://{client的ip}:7480,我这里是:http://192.168.40.95:7480/,网页内容如下:

<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Owner>
<ID>anonymous</ID>
<DisplayName/>
</Owner>
<Buckets/>
</ListAllMyBucketsResult>

Swift 访问实例

1,新建一个 SWIFT 用户
如果你想要使用这种方式访问集群,你需要新建一个 Swift 子用户。创建 Swift 用户包括两个步骤。第一步是创建用户。第二步是创建 secret key。
登录到client主机上,执行上诉两步:

sudo radosgw-admin subuser create --uid=testuser --subuser=testuser:swift --access=full

输出如下:

{
    "user_id": "testuser",
    "display_name": "First User",
    "email": "",
    "suspended": 0,
    "max_buckets": 1000,
    "auid": 0,
    "subusers": [
        {
            "id": "testuser:swift",
            "permissions": "full-control"
        }
    ],
    "keys": [
        {
            "user": "testuser",
            "access_key": "UYAHFPMQH1HRGB6EG5N5",
            "secret_key": "DfjtneOIoVMDKsnEpPQ2YWpq3OlqO1FCJPwBYAKe"
        }
    ],
    "swift_keys": [
        {
            "user": "testuser:swift",
            "secret_key": "0F2SWrRDNLL7skx4XLs9VagENv1XDjyNPTPjIGNw"
        }
    ],
    "caps": [],
    "op_mask": "read, write, delete",
    "default_placement": "",
    "placement_tags": [],
    "bucket_quota": {
        "enabled": false,
        "max_size_kb": -1,
        "max_objects": -1
    },
    "user_quota": {
        "enabled": false,
        "max_size_kb": -1,
        "max_objects": -1
    },
    "temp_url_keys": []
}

新建 secret key:

sudo radosgw-admin key create --subuser=testuser:swift --key-type=swift --gen-secret

输出如下:

{
    "user_id": "testuser",
    "display_name": "First User",
    "email": "",
    "suspended": 0,
    "max_buckets": 1000,
    "auid": 0,
    "subusers": [
        {
            "id": "testuser:swift",
            "permissions": "full-control"
        }
    ],
    "keys": [
        {
            "user": "testuser",
            "access_key": "UYAHFPMQH1HRGB6EG5N5",
            "secret_key": "DfjtneOIoVMDKsnEpPQ2YWpq3OlqO1FCJPwBYAKe"
        }
    ],
    "swift_keys": [
        {
            "user": "testuser:swift",
            "secret_key": "lYPpmXg7UN2WNBRc3ftsChrruJaAMWuNdFyrygAl"
        }
    ],
    "caps": [],
    "op_mask": "read, write, delete",
    "default_placement": "",
    "placement_tags": [],
    "bucket_quota": {
        "enabled": false,
        "max_size_kb": -1,
        "max_objects": -1
    },
    "user_quota": {
        "enabled": false,
        "max_size_kb": -1,
        "max_objects": -1
    },
    "temp_url_keys": []
}

2,测试 SWIFT 访问
在任何局域网机器上安装swift环境:

sudo apt-get install python-setuptools
sudo easy_install pip
sudo pip install --upgrade setuptools
sudo pip install --upgrade python-swiftclient

执行下面的命令验证 swift 访问:

swift -A http://{IP ADDRESS}:{port}/auth/1.0 -U testuser:swift -K '{swift_secret_key}' list

我的执行命令如下:

swift -A http://192.168.40.95:7480/auth -U testuser:swift -K 'lYPpmXg7UN2WNBRc3ftsChrruJaAMWuNdFyrygAl' list

如果成功的话,应该没输出,但是要是失败一定会报错,swift搭好了之后,我写了python脚本,做了测试,测试文档和脚本文件见地址:https://github.com/JakeRed/CephSwiftTest

错误处理

1, 对硬盘进行格式化: # mkfs.xfs /dev/sdb1, 系统显示:

 mkfs.xfs error: command not found.

因为系统缺少xfs的部分包,安装解决:

 apt-get -y install xfsprogs

2,安装集群时候,log显示:

Reading package lists... Done E: Problem executing scripts APT::Update::Post-Invoke-Success
'if /usr/bin/test -w /var/cache/app-info -a -e /usr/bin/appstreamcli; then appstreamcli refresh > /dev/null; fi' E: Sub-process returned an error code

系统libappstream3版本老久,解决:

sudo pkill -KILL appstreamcli

wget -P /tmp https://launchpad.net/ubuntu/+archive/primary/+files/appstream_0.9.4-1ubuntu1_amd64.deb https://launchpad.net/ubuntu/+archive/primary/+files/libappstream3_0.9.4-1ubuntu1_amd64.deb

 sudo dpkg -i /tmp/appstream_0.9.4-1ubuntu1_amd64.deb /tmp/libappstream3_0.9.4-1ubuntu1_amd64.deb

3,install安装缓慢,前期vpn解决,后期换源:

export CEPH_DEPLOY_REPO_URL=http://mirrors.163.com/ceph/debian-jewel 

export CEPH_DEPLOY_GPG_URL=http://mirrors.163.com/ceph/keys/release.asc

4,在执行安装激活的时候报如下错误:

[ceph_deploy][ERROR ] ExecutableNotFound: Could not locate executable 'ceph-volume' make sure it is installed and available on .........

这个是因为ceph-Deploy的版本高了,需要卸载高版本,安装低版本(admin节点):

pip uninstall ceph-deploy

然后下载1.5版本的gz文件,这里我下载的地址: https://pypi.python.org/pypi/ceph-deploy/1.5.39

然后编译执行setup.py文件。

wget https://files.pythonhosted.org/packages/63/59/c2752952b7867faa2d63ba47c47da96e2f43f5124029975b579020df3665/ceph-deploy-1.5.39.tar.gz

tar -zxvf ceph-deploy-1.5.39.tar.gz

python setup.py build 

python setup.py install

好像如下方法也能解决:

pip install ceph-deploy==1.5.39

5.问题:

[mlv4-VirtualBox][WARNIN] ceph_disk.main.FilesystemTypeError: Cannot discover filesystem type: device /dev/sdb: Line is truncated:
[mlv4-VirtualBox][ERROR ] RuntimeError: command returned non-zero exit status: 1 [ceph_deploy][ERROR ] RuntimeError: Failed to execute command:
/usr/sbin/ceph-disk -v activate --mark-init systemd --mount /dev/sdb

首先查看系统状态,我的情况是prepare 之后,已经activate 状态了,可以通过ceph-deploy disk list ceph-osd1 ceph-osd2 查看状态,如果非activate,可以将sdb的用户权限给ceph:

sudo chown ceph:ceph /dev/sdb,

我的问题其实是发现sdb 激活报错,但是 sdb1 也就是 data部分,可以激活,通过:

ceph-deploy osd activate ceph-osd1:/dev/sdb1 ceph-osd2:/dev/sdb1

解决问题。

6.问题:

cephuser@mlv1-VirtualBox:~/cluster$ ceph osd tree 2018-04-11 14:41:40.987041 7f1e42a57700 -1 auth: unable to find a keyring on /etc/ceph/ceph.client.admin.keyring,/etc/ceph/ceph.keyring,/etc/ceph/keyring,/etc/ceph/keyring.bin: (2) No such file or directory 2018-04-11 14:41:40.987511 7f1e42a57700 -1 monclient(hunting): ERROR: missing keyring, cannot use cephx for authentication 2018-04-11 14:41:40.987619 7f1e42a57700 0 librados: client.admin initialization error (2) No such file or directory Error connecting to cluster: ObjectNotFound

一般这个问题不会出现,但是出现了最简单的解决方法就是把之前cluster下的key文件(所有文件),cp到每一个节点的/etc/ceph文件夹下解决。

7.gateway搭建之后 auth怎么都不过,没找到具体原因,但重装是个好办法,作用于每一个节点安装:

ceph-deploy purge <ceph-node1> [<ceph-node2>]
ceph-deploy purgedata <ceph-node1> [<ceph-node2>]

8.在删除了所有文件之后,集群还占有空间,没及时释放。

官方的解释说,文件对象删除之后,并不会立刻执行GC机制,这个GC机制是可配置的,配置项如下:

"rgw_gc_max_objs": "521",
"rgw_gc_obj_min_wait": "1200",
"rgw_gc_processor_max_time": "600",
"rgw_gc_processor_period": "600",
2018-08-25 18:42:52 yiyihuazi 阅读数 226

【导语】Swift 自发布以来就备受众多 Apple 开发者关注,但由于 API 尚不稳定,系统没有内置 Framework 导致 App 包增大等问题,使得线上主力使用的公司还很少,不少客户端开发者都还没有机会使用 Swift 进行开发。等到 2015 年 12 月 Swift 开源并正式支持 Linux 系统,广大 Apple 开发者迎来了更广泛的开发场景,可以用它来进行服务端开发。不到一年时间各种 Server-Side Swift Web Framework 相继问世,其中以 Kitura、Perfect、Vapor、Zewo 最为成熟。


文章正式开始前,我们先对当前几款主流框架进行了解与对比。

Kitura 是 IBM 推出的框架,使用 IBM Cloud Tools for Swift 管理组件依赖,并支持部署代码到 IBM 的云服务 Bluemix。另外还有一个在线 Swift 编码网站,可以看作是线上 GUI 版本的 Swift REPL,开发者可以直接在 Web 上编写代码并查看输出。Kitura 整个产品从代码编写到部署全部包揽,提供了完整的生态环境。

Perfect 拥有 GitHub 上最多的 Star,各种功能组件和数据库连接工具也最为齐全。近期推出的 Perfect Assistant 是运行在 macOS 上的管理工具,同样支持组件依赖管理,自动化代码部署(支持 AWS、Azure),并通过调用本地 Docker 的方式实现了在 macOS 上编译 Linux 产物的功能。

Vapor 以其友好的文档和 Pure Swift 代码实现著称,其 HTTP Parser 是使用 Swift 编写实现,而不像 Kitura 和 Perfect 是用 CHTTPParser 封装,这对最终的服务性能有很大影响。Vapor 还开发了命令行工具对 SPM 进行封装,好处是开发体验更好,但提高了学习成本。另外 Vapor 比较早就做了 ORM 工具 Fluent,整体感觉十分技术范、小清新。

Zewo 是一系列开源组件的集合平台,特点是使用 libmill 实现了类似 Go 的协程功能,模块化的设计也不同于其他的框架。

这些框架在各具特色的前提下都有高性能、易扩展等优点。正好部门内部有一个信息管理平台项目,需求很简单,只要有基本的增删改查就行,于是不用麻烦后端同学排期,可以自己来开发,也算是提前实践 Swift,积累经验。

正式开发是在 2016 年 8 月,彼时 Swift 3 尚未发布,Beta 版本的 Toolchain 每周都在更新,框架也在积极跟进发布支持最新版本的 Toolchain。技术选型期间我先后尝试了 Kitura、Vapor 和 Perfect。Kitura 的整套产品耦合太紧密,用起来比较重,对于轻量级小项目并不合适。Vapor 一开始用起来很愉快,但写到数据库连接工具时一直无法连接成功,再加上当时还在 Beta 版本,问题不少也被弃用。最后,使用 Perfect 完成了项目研发。接下来,本文将着重介绍如何使用 Perfect 完成一套 RESTful API 的开发,希望能够对大家进行 Swift Server 端开发有所裨益。

在编写代码前,要先了解目前开源的 Swift 项目包括了 Swift、SPM 以及配套的编译调试工具,在核心库方面有 libdispatch、Foundation、XCTest 这三个项目。在客户端开发中,Foundation 是最常用的工具库,它提供了一系列国际化、系统无关的 API。服务端项目增加 Foundation 支持可以统一开发体验和复用客户端代码,尤其是和系统无关的 API 可以大大增加可移植性,本属于 Swift 3.0 的组成部分但至今并没有开发完成,原因在于 Foundation 中用到了一些 Objective-C Runtime 的代码,而这部分代码并不在开源范围之内。于是在开发中需要用到 Foundation 库时就会碰到不少问题。

环境配置

macOS 上依然是 Xcode 搞定一切事情,Linux 上目前只支持两个版本的 Ubuntu,所以推荐使用 Docker 搭建 Swift 编译和执行环境,这样可以支持所有 Linux 系统,也方便在 Swift 快速迭代时及时更新 Toolchain。代码都用 SPM 管理,在实际使用中还是有些问题,比如不支持 MySQL 5.7,创建工程文件配置时漏掉了编译设置,寻找公共代码库路径在不同操作系统上没有适配等。

开发中使用了两个第三方库 SwiftLog 和 MySQL-Swift。SwiftLog 支持自定义日志级别和增量写入的日志文件,并使用了喵神的 Rainbow 在 Linux 环境下输出彩色日志。MySQL-Swift 支持 MySQL 连接池复用,可以提高访问数据库的性能。

部署环境使用 CentOS 作为宿主机,开启两个 Docker 实例分别运行 Perfect 和 MySQL,两个 Docker 实例通过 link 方式实现通信。使用 link 参数运行 Docker 实例,主 Docker 的 hosts 文件会增加从 Docker 的 host 信息,从而达到通过 Docker 别名进行通信的效果。初始化 MySQL 容器时可以将数据库文件路径设置到虚拟卷中,再使用 crontab 执行定期任务运行 AutoMySQLBackup 来备份数据。

编码

先初始化工程,使用 swift package init 新建工程,此时会生成 Package.swift 配置文件和源代码、测试代码目录。在 Package.swift 中添加 Perfect、SwiftLog、MySQL-Swift 后执行 Swift Build 即可拉取所有依赖代码。此时的目录并不包含 Xcode 工程文件,需要再执行命令 swift package generate-xcodeproj 生成,工程中各个依赖库的配置都通过自身的 Package.swift 配置文件读取。 
SPM 不仅是去中心化的包管理器,它还可以编译出可执行文件,我们甚至能够直接在服务端编写代码并编译运行。如果说在使用 Objective-C 开发时用 Xcodebuild 开发自定义打包工作流,那么开发 Server-Side Swift 就需要使用 SPM 在服务端实现编译、打包等流程。虽然在服务端目前还有些兼容问题,但 SPM 作为 Swift 的组成部分,一直在快速改进与提高。

然后,添加 API 路由,我们先添加路由配置,后面再将所有配置一起设置到 Server 对象上。Perfect 的路由 API 设计参考了 Express,只需要设置 HTTP 请求类型、路由地址和对应的处理函数即可,支持使用通配符与参数,用起来还算简洁。我们可以先设置基础 API 路径,再通过 Routes().add 方法添加自定义路由,这样所有的路由都被添加到指定 API 路径下。最后将路由对象输出一下,这样开启服务时会将所有注册的路由输出到日志。

func makeURLRoutes() -> Routes {
    var routes = Routes()
    var apiRoutes = Routes(baseUri: "/api")
    var api = Routes()
    api.add(method: .get, uri: "/staff/{id}", handler: fetchStaffById)
    apiRoutes.add(routes: api)
    routes.add(routes: apiRoutes)
    SLogWarning("\(routes.navigator.description)")
    return routes
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Swift 没有 define 关键字,但可以通过 typealias 自定义类型名称,比如 Perfect 里的路由处理回调函数就被定义为 public typealias RequestHandler = (HTTPRequest, HTTPResponse) -> ()。从定义可以看到,响应函数有两个参数,HTTPRequest 包含了客户端请求的所有信息,包括 HTTP headers 和 content data。HTTPResponse 包括 HTTP headers、body 以及 HTTP Status,这些属性可以在函数中赋值并返回给客户端。

接着来实现路由函数。先将请求参数拿到,将参数进行错误处理后调用数据库工具方法获取信息,并根据获取数据的成败做对应的处理,最后返回 JSON 格式的结果。

在路由处理函数最后都需要调用 response.completed()方法通知 Server 回调处理完成,由于函数有几个结束点,在参数错误或者获取数据的时候都可能抛出异常并提前结束函数,所以需要在好几个地方执行 response.completed()方法。Swift 和 Golang 一样拥有 defer 关键字,我们可以在函数中使用它来完成资源回收或这种需要多处调用的代码。

defer {
    response.completed()
}
  • 1
  • 2
  • 3

进行数据库请求和生成 JSON 返回值的操作都有可能抛出异常,这样在 do-catch 中会抛出两种类型的异常,我们可以使用 switch-as 语法针对不同类型的异常进行处理。

do {
    let staffData = try StaffDataBaseUtil.sharedInstance.searchStaffByID(idString)
    try response.setBody(json: jsonBody(errorCode: returnCode, data: ["staff": staffData]))
} catch let error {
  switch error {
    case let error as QueryError:
        // 数据库请求出错
    default:
        // 服务端出错
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Perfect 并没有使用 SwiftyJSON 之类的第三方库,而是自己实现了很好用的 JSON 扩展,对常用的数据类型增加了 JSON 序列化和反序列化方法,Swift 的 Extension 在这里得到了充分的使用。

extension Dictionary: JSONConvertible {
    /// Convert a Dictionary into JSON text.
    public func jsonEncodedString() throws -> String {
        var s = "{"
        var first = true
        for (k, v) in self {
            guard let strKey = k as? String else {
                throw JSONConversionError.invalidKey(k)
            }
            if !first {
                s.append(",")
            } else {
                first = false
            }
            s.append(try strKey.jsonEncodedString())
            s.append(":")
            s.append(try jsonEncodedStringWorkAround(v))
        }
        s.append("}")
        return s
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

数据获取需要进行数据库请求,我们使用 MySQL-Swift 作为数据库连接工具,为的是使用连接池复用连接,但也给自己挖了个坑。在 Mac 上开发时都没有问题,但在服务器端就编译失败,后来发现有一部分的代码还不支持 Linux,原来是时区的问题。在连接数据库时需要传入一个 config,包括数据库地址、端口、密码等,其中就有时区配置。在从数据库获取数据时,如果字段中有日期类型,就会将获取的绝对时间转换为对应时区的时间字符串。在日期类型数据的处理部分用到了 NSTimezone,但这一类型在 Linux 上没有实现,于是编译失败。修复的方式很简单,使用 CFTimezone 传递信息就好,但 CFTimezone 返回的类型是 CFString,于是又要针对 macOS 和 Linux 实现不同的 CFString 转换到 String 的代码。如果 Swift 有一套跨平台的 Foundation 就不会出现这个问题了。

MySQL-Swift 底层使用的是 CMySQL 库进行连接。根据 options 初始化数据库连接,将连接保存到连接池中,这样后续的数据库操作不需要再次重新连接数据库。再对每个连接对象添加是否正在使用的标记,如果当前连接池中的所有连接都在使用当中,则再次新增一个数据库连接对象进行操作。可以手动设置连接池的最大连接数,当连接池中的连接数到达最大连接数时,后续的请求将会抛出获取数据库连接失败异常。 
MySQL-Swift 将查询方法封装的十分优雅,工具类在初始化时候根据提前设置好的 config 生成连接池,再调用 pool.execute 方法,并传入查询执行的闭包就行。这里用到了高阶函数和范型,语法简洁又安全。使用的时候不需要关心数据库连接对象的创建和状态,只需要直接使用闭包里传进来的 Connection 连接对象进行查询即可。

private init() {
    pool = ConnectionPool(options: DBConfigOption)
}
let result: [Staff] = try pool.execute { conn in
    try conn.query(querySQL)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

对于同名不同返回值的函数,Swift 会针对代码中返回值的类型推断进行分析,确定最终运行时调用哪一个函数。上面 conn.query 函数就会根据不同的返回值执行不同的函数,所以在编写上面代码的时候一定要显示的声明返回的是 Staff 数组,不然函数返回的结果就会不同。另外在 query 的实现中可以看到,最后的不同返回结果是从同一个函数返回的 tuple 中拿到的,这样的代码阅读起来很有效率。

public func query<t: queryrowresulttype="">(_ query: String, _ args: [QueryParameter] = []) throws -> ([T], QueryStatus) {
    return try self.query(query: queryString)
}

public func query<t: queryrowresulttype="">(_ query: String, _ args: [QueryParameter] = []) throws -> [T] {
    let (rows, _) = try self.query(query, args) as ([T], QueryStatus)
    return rows
}

public func query(_ query: String, _ args: [QueryParameter] = []) throws -> QueryStatus {
    let (_, status) = try self.query(query, args) as ([EmptyRowResult], QueryStatus)
    return status
}</t:></t:>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

拿到数据后可以将结果转换为自定义的数据类型。这里会将数据库查询结果解码为定义了默认值的结构体,并提供序列化方法给路由处理函数。

static func decodeRow(r: QueryRowResult) throws -> Staff {
    return try Staff(
        id: r <| 0,
        name: r <| "name",
        department: r <| "department",
        timestamp: r <| "timestamp"
    )
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在开发完成后的测试中发现返回数据的时间戳总是差几个小时,但是直接查看数据库里的数据,时间戳又是正确的,于是一步步从获取数据到返回 response 调试看看哪里出的问题,最后发现是 Swift 时区没有自动设置为和系统相同,而一直是 UTC 时间,在生成时间戳文案的时候就出错了。

所以在使用 Dateformatter 的时候需要手动设置时区,这也是 Foundation 的一个坑。

func resultDic() -> Dictionary<string, any=""> {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
    dateFormatter.timeZone = TimeZone(identifier: "Asia/Shanghai")
    let dateString = dateFormatter.string(from: timestamp.date())
    return Dictionary<string, any="">.init(dictionaryLiteral:
        ("id", id),
        ("name", name),
        ("department", department),
        ("timestamp", dateString)
    )
}</string,></string,>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在拿到数据后,可以将 HTTP Response 包装一下,在每个返回结果中都包括错误码,数据和时间戳这三个字段,并增加错误码对应的错误提示。这样不用在每个路由处理函数的最后都手动写一次。

public func jsonBody(errorCode: ErrorCode, data: Dictionary<string, any="">?) -> [String: Any] {
    var body = [String: Any]()
    body["s"] =  ["code": errorCode.rawValue, "desc": errorCode.description]
    if let jsonData = data {
        body["data"] = jsonData
    }
    body["t"] = UInt(Date().timeIntervalSince1970)
    return body
}</string,>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

至此代码基本编写完成,为了方便调试,我们可以通过 Perfect 的 Filter 方法添加自定义日志,将每次的 HTTP 请求和返回的信息输出。这样我们不需要在每个路由处理函数中调用日志方法就可以输出所有请求的参数和最终返回的结果。

func incomeMiddleware(request: HTTPRequest) {
    SLogInfo("Request URL: " + request.uri)
    SLogInfo("Request Method: " + request.method.description)
    SLogVerbose("Request Params: " + String(describing: request.params()))
    for (name, detail) in request.headers {
        SLogVerbose("Request HEADER: " + name.standardName.uppercased() + " -> " + detail)
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

我使用的 SwiftLog 作为日志工具,没想到在部署测试的时候也出了问题,表现为开启服务的时候可以输出日志,一旦请求进来就崩溃,抛出 Segmentation fault。但是代码在开发机上运行正常,一路调试下来发现是 “(Date())” 的问题,这段代码第一次执行没问题,第二次执行就会导致崩溃。直接在服务端用 REPL 执行两次 pring(Date())也发生了相同的崩溃。推断应该是 Date 对象的 description 代码执行出错,于是自定义 Date 对象的 description 方法,避免调用自带的方法,问题解决。

所有配置和路由函数开发完成后,开始设置 server 对象。Perfect 支持静态文件路由,可以设置静态文件的路径。这里推荐针对 macOS 设置静态文件路径单独设置,因为 Xcode 编译出的可执行文件并不在代码目录,因此在本地调试时候会出现找不到静态文件的问题。

#if os(OSX)
server.documentRoot = "~/webroot"
#elseif os(Linux)
server.documentRoot = "./webroot"
#endif
  • 1
  • 2
  • 3
  • 4
  • 5

代码开发完成后,使用 Docker 初始化 Swift 实例并拉取代码,使用 swift build -c release 生成可执行文件,将可执行文件和 libCHTTPParser.so、libCOpenSSL.so、libLinuxBridge.so 三个依赖库文件复制出来提交到目标 Docker 中即完成部署。目前 Swift 官方只支持 Ubuntu 系统,也有人尝试在 CentOS 上手动编译 Swift 源码,但由于缺少官方的全面测试所以不推荐在生产环境使用。

总结

Swift 作为服务端开发语言的新成员,有着不少的先天优势,比如智能的类型推导、强大的协议扩展、丰富好用的语法糖,这也是官方宣传的 Safe、Fast 和 Expressive 的具体体现。开源后的 Swift 吸引了更多的开发者参与其中,从 4.0 演进表也可以看到更多强力且有趣的功能包括反射、并发、稳定的 ABI 等。未来是美好的,现实是残酷的,以目前国内的 Swift 开发生态环境,在客户端尚且无法占据主流位置,更不用想挑战 Java、PHP、Python 等语言在服务端的地位。想要用 Swift 替代各大公司线上成熟的开发方案是不现实的,但可以从小做起,从辅助工具之类的角度着眼,先做出广泛使用的产品,逐步找到自己的定位,再扩展使用场景。私以为 Go 在这方面做的很好,Docker 的流行让更多人知道了 Go 这门语言且证明了其实力。

目前看来 Swift 最需要解决的是 ABI 稳定性和跨平台兼容两大问题,对于 ABI 来说,之所以到 3.0 版本还没有稳定下来,是开发小组认为目前稳定 ABI 将无法去掉现有实现中错误的部分,且很可能带着补丁开发后续版本将成倍提高今后的开发难度。越早提交的代码留存率越低。这对于语言的开发是件好事,不用带着很多历史包袱开发新功能。但对于开发者来说这意味着在语言的新版本发布后不能方便的快速跟进,除非所有依赖的 Swift 代码库都及时跟进并发布基于新版本编译的代码库,这会大大降低使用 Swift 开发的积极性。

另外 Server-Side Swift 目前只支持 Ubuntu 系统,Foundation 的移植也还在进行当中,并且各种兼容 Bug 频出,在开发过程中很容易遇到开发环境和部署环境运行效果不同的情景。好消息是针对后面的问题,Swift 开发团队成立了 Server APIs Work Group,工作组的目标就是提供服务端跨平台 API,消除平台相关代码差异,提供基础框架功能代替 C 库的引入,进一步降低服务端的开发门槛,提高客户端代码的可移植性。同时 Swift 3.1 的修改内容中明确说明了会改进 Swift on Linux 和 SPM 的质量,期待 2017 年春天发布的这个版本会给 Server-Side Swift 带来显著的改进。

2016-02-14 15:23:32 u011530389 阅读数 642

出自:http://www.csdn.net/article/2014-12-09/2823025

Google于2009年第一次提出了Go的构思,Facebook在去年春天引入了Hack,随后不久Apple也发布了其Swift语言。


在战争中,胜利者写历史书;在科技中,赢的公司都在写编程语言。互联是建立在开发标准和代码之上,但是社交网络和云计算领域受企业巨头控制,并且它们开始把自己独一无二的印记烙在数字科技上——这是必然的,就像征服者威廉和他的诺曼人一样,大量新的词汇加入到英语中。他带来许多新事物,如陪审制度,这些都影响着当地的法律和语言的方式。

可以确定的是,新的语言给予程序员一些有用的支撑点。Google Go语言的构建简化了并发运行代码的工作,由于存在并行编程模式,因此这一语言也被设计用来解决多处理器的任务。对于流行的Web脚本语言,像PHP和JavaScript,Apple的Swift语言提供给iPhone程序员一些简洁性和灵活性。每个语言都有它自己的标志:Swift是一只风格化的鸟,Go语言则是一只拗脚的地鼠。

Ken Thompson、 Rob Pike 和Robert Griesemer是Google的三位编码大师,于2009年半开玩笑的提出了Go的构思。像C++和Java这些广泛使用的编程语言是迟钝的,尤其是面对Google那种大规模的项目部署时。每次添加或改变一些东西时候,你都需要等待编译器将代码转换为机器可读的二进制版本。

Rob Pike说:“这个等待过程要45分钟,我认为这是痛苦的。当需要等待那么长时间时,你有很多时间可以考虑,你需要怎么做可以更好。”

设计编程语言在于权衡——对于程序员容易,对于机器最适合

创建运行快的代码要求程序员付出更多的努力。人们编写运行迅速的代码需要花费多少时间和精力呢?另一个主要权衡在于直接访问机器时语言提供的内存数量。不仅在这里,在其它地方也一样,语言发明者必须选择:知道他们可能会搞砸时,你为程序员提供多少自由?你为他们提供多少措施来缓解他们的失误?

语言设计的任务是庄严的、正式的、宏伟的、充满了难题和矛盾。这里没有正确的答案,只有不同的选择,以适应不断变化的硬件、多变的用户和挑剔的程序员。

Go语言的创造者有足够的经验去做这些选择,Ken Thompson创建了Unix,他和他的同伴Pike(贝尔实验室老将)共同设计了字符编码的风格,被称为UTF-8,现如今大多数Web会使用。所以他们知道小的决策也会有巨大的影响。现在增加的每一条规则,可能就意味着在未来极多的按键需要程序员去操作。每一条规则的遗漏可能意味着无数的崩溃。

例如,编程语言通常使用分号来分隔语句,使用括号将相关语句组织在一起。典型的如“Hello,World”C语言程序:

  1. main()  
  2. {  
  3. printf(“hello world”);  
  4. }  

Go的创造者认为括号是至关重要的。有些语言(尤其是Python)把这放到了一边,允许程序员使用缩进(空格)或“隐形的字符”来告诉人和机器代码的位置。Go团队认为这是一个“深刻的错误”,括号意味着程序员可以明确不含糊的告诉计算机如何在很大一块区域组合代码。在一次与Sergey Brin会面上,Google的创始人提出Go的设计人员使用方括号,而不是花括号,节约开发者无数趟使用“Shift”键。

在2009年12月份,Go决定停止要求程序员以分号结束语句。分号用于解析器——把项目分解成块(相关代码组成一块)的幕后工具,目前的FAQ解释道:“我们想尽可能的消除它,从此当你递送你代码到解析器的时候,机器将注入分号。”

Go清除分号减轻了程序员的眼睛疲劳,但是程序员需要更严格的部署花括号,否则分号会注入到错误的地方。

这些选择并不是没有争议的,一个批评家在Pike的讲座上抱怨道:“他们通过冗余的花括号破坏了语言。”语言也可以被设计的很容易,仅仅使用空格行使花括号的角色来阻断代码的不同部分。Google的Andrew Gerrand 回应道:“每天都有一定规模不可思议的倒霉事发生。意思是有人会偷偷的把一些看不见的符号放入代码库中,导致一个微妙的Bug。这在Google的Python程序中已经发生了不止一次。”

然而对于那些在软件论坛争论细微差别的程序员来讲,“任性的”语法也会使他们“醉了”。

和我们所说的语言不同,程序员称之为自然语言。编程语言是为特别目的和用途精心编制的。Go,正如Pike解释的那样,它是由Google设计的用来解决Google问题。Google存在大的问题......我们需要一种语言,可以让我们更容易的完成我们的工作——编写服务软件。

Google在云服务中运行它自己的全球性超级计算机,这种计算恰恰使Go能够得到最佳化的使用。但是Google并没有从销售软件中获得一分钱。并且Go已经是一种免费的、开源的项目,这使得它迅速的进入了其他装备的技术库。Redmonk咨询公司的一名分析师说:“它成为云基础设施的新兴语言。”

Go已经逐渐的流行起来。例如,Dropbox已经从Python转移了大部分的后端代码到Go。Automattic,一家运营Wordpress.com的公司,也使用了Go,虽然WordPress本身一直使用PHP脚本语言。Automattic开发者Demitrious Kelly说:“这些天有十几个新的框架和方法摆在那,你必须要问自己,它比我们拥有的要好吗?但是这本身就是一个复杂的问题,要好到什么程度?它可以让我们做到什么是我们以前做不到的事情?值得吗?”Kelly称,在众多测试当中Go的表现不错,部分原因是该语言比较小,他说:“Go语言很容易在一个星期内就上手。”

鉴于Go的设计主要是针对Google特定的问题,语法的选择,即分号和花括号。这个问题并不那么简单,需要追求细节,还要有热情。愿意无视传统的编程给这个世界带来新的语言。可能最终推动语言的采用的就是程序员认真注意日常的编码——程序员称之为“痛点”

Swift的起源

每个编程政权都有这样的痛点,但是迅速崛起的iOS给了开发者多于常规的份额。直到今年夏天Swift语言的出现为止,如果你想为iOS写一个程序,你必须使用Objective C语言。在80年代,Steve Jobs就已经采用了Objective C,并且当Steve Jobs重返Apple之后,该语言逐渐成为Mac OSX的主力工具。

现在开发者说一个语言也就透露了他的年纪。编写Mac操作系统老将Andy Hertzfeld说:“当看到Swift公告的时候我非常激动。因为我一直鄙视Objective C,我喜欢其背后的原则,但是讨厌其语法,我从来没能够真正的享受编程。”

Apple将其下一代产品、编程语言项目委托给一位名叫Chris Lattner的计算机科学家,LLVM项目的主要发起人与作者之一。LLVM 是一个开源项目,其核心库提供了与编译器相关的支持,可以在不同的平台上运行(包括Apple和Google在内,都广泛的使用它)。在2005年加入Apple后,Chris Lattner继续LLVM项目以及相关的工作,随后在大众视野里消失了几年,去年6月份,在Apple全球开发者大会上,其携带Swift出现在大家的面前。

Swift旨在成为“第一款工业级质量的系统编程语言!”换句话说,Swift是有前途的,你将能够不用费力就可写出运行迅速的代码。这赢得了大批iOS开发者和旁观者的欢呼。 Hertzfeld说::“做得好,它缓解了每个人巨大的痛点。对于iOS开发者来说不去追求Swift的顶端是愚蠢的。”

如果你“签约”了Swift,意味着你购买了Apple的整个领域:你需要通过Apple开发和销售你的程序;你的程序将运行在Apple机器上;如果你想在其它地方运行的话,你需要使用其它语言重新写一遍;你的命运将和Apple密不可分。

Hertzfeld说:“你必须提交到这个“有围墙的花园”,所以他抵制了用Swift工作的诱惑。不过如果他们实现开源,或者对跨平台有一点点兴趣的话,我可能会接受Swift工作。”

开源版本的Swift意味着开发者能够找到一种方法快速的将程序移植到不同的平台,并且这也能保证在未来,当Apple失去了Swift继续下去的兴趣,该语言还是有未来的。那些曾逗留在“有围墙的花园”外围的开发者往往非常关心这个问题。Apple对于开源并不完全过敏,虽然它似乎决心要抓住世界范围内iOS的控制权。在Swift公布不久后,LLVM项目里的开发者开始就Swift“隔绝自然”问题纠缠于Apple和Lattner,Lattner回应说:

伙伴们,你的推测仅仅是——投机。我们还没有讨论过这个,因为我们有大量的工作去应对大量的反馈,并要在今秋发布1.0版本之前落实大量的事情(如访问控制)。你可以想象我们当中有很多人希望它开源,并成为LLVM的一部分,但是讨论尚未发生,并且短时间内也不会出现。

Swift的出现并没有Go时间长,在任何情况下,其未来在Apple领域是安全的,如果Apple说Swift是十亿iOS设备的未来,那么它就是。就像一位波特兰独立的iOS开发者David Wheeler一样,人们会采用它,因为从长远看来,他们别无选择,同时它也有意义。Wheeler说Swift迅速的将他“拿下”了,他以为Apple将继续修补Objective C,“我想知道它现在去哪了?”Wheeler说。

语言本能

大型计算机时代的主流语言有相似的起源:来自IBM的FORTRAN,以及COBOL在很大程度上是基于Grace Hopper的Flow-matic,而这个是为Remington Rand公司的Unicac创建的。在1990年,Sun创建了Java,2000年 Microsoft创建了C#。

事实上,大多数的语言是大型机构、企业或学校的产物。

Hertzfeld说:“创建一种新的编程语言需要花费大量的资源。这是一个长达十年的项目去让新的语言完全用工具加工、建立和使用,小公司做不到这点。”

尽管有困难,但是悲哀的是自1960年以来,计算机行业里有太多的语言存在重复的部分。今天的悲哀是无用的,程序员不太可能停止设计新的语言,或者同意去分享,因为如早期开发者Alex Payne说的那样:没有激励,语言的历史充斥着标准化的努力。这是非常错误的,浪费了大量的时间,却没有产出一个让任何人都满意的结果。

这么说并不意味着忽视Facebook开发的新语言Hack,尽管Hack是开放源代码的,但是本质上PHP语言的变体或扩展。并没有受到公司外部的人追捧。毫无疑问,Facebook希望看到变化,但是它并不是社交网络积极推动的东西。这些天对于Facebook的Hack的反应就是“观望”。

开发者指出,每一个新的语言开始作为一个摆脱不掉的种子存在于个人或小组的大脑中,这一直困扰着我。我们可以做的更高。无论如何,需要有耐心和努力去学习一个新的语言;开发者要仔细选择。Payne说:“当选用一个新的语言时,我会跟随其他人的东西,因为这些人和你一样依赖于图书馆和文档,我想你希望知道你是否进入了一个正确的‘小镇’。”

有一点我们可以有信心的说,新语言是好的,它们简化了程序员的生活;简化了编程的工艺;它们融入了新的有前途的思想;它们赢得了包括公司内外部开发者的尊重。

因为这些原因,霸权主义可能是一个错误的历史对照来制造新编程语言潮流。相反,我们讨论的这些更像是称为软实力的外交政策类型:Go和Swift以特定方式体现它们公司的精髓。服务器VS个人设备;开放Web VS应用商店;一个跨平台的世界VS一个公司。所有区分编程语言的分歧——编译或解释?静态VS动态变量类型?内存管理/垃圾回收?在今天这些可能都是很重要的。

换句话说,任何人担心企业发展编程语言的真正原因可能不是“OMG,他们想要接管世界!”相反,他们担心的原因是不管他们成长的多大,他们总会受到根源语言的约束。

关于编程语言,一旦它们占领程序员的“头脑”,那么你永远不会知道它们最终会去哪里。在80年代创造Objective C的面向对象编程爱好者,他们可能不知道该语言在25年以后会变成一个巨大的全球生态系统移动设备所必要的编程语言。当Sun于1995年推出Java时,每个人都认为它将是一个用于构建浏览器小程序的好工具,然而它的命运走向了服务器端。同时发布的Javascript被广泛的遗忘,在今天却让很多Web“移动”。

对于开发者来说,选择一个语言就像选择一个国家的国籍,你不仅仅购买语法和语义,你也购买了经济和文化,以及你怎样获得生计和力量的规则。就像他们常说的:为了避免一个“死”语言控制了世界,购买需要谨慎。

2018-11-16 18:44:26 a1368783069 阅读数 5393

1 背景

这两天在考虑使用golang搭建一个http服务,调用别人的linux sdk。由于linux sdk是用c写的,直接使用cgo调用c是很方便的,但是个人想使用c++来调用c的函数,封装成c++语法,c++可以使用一些库,对于开发是比较方便的,所以就得考虑使用cgo调用c++程序。

网上一搜,目前实现cgo调用c++主要有三种方式:
1 swig
2 swift
3 使用c包装c++,然后cgo调用c

对于以上3种方式,第2种我是没看过,第1种就浏览了下,没写过。主要是想用第3种,可以纯粹的写c++,c。

环境:
ubuntu 16 64bit (个人喜欢用centos 7.*)
gcc g++都是默认的。
golang gopath = /home/user/gopath

说明:以下代码是从网上下载的,但是别人的实现是要编译可执行文件,即go build, 然后执行可执行文件。(源码地址:draffensperger/go-interlang )不能通过go run *.go 来执行,很嫌弃这种,麻烦,所以自己就来该动了。自己项目中的代码就不能放在这了,原理都是相同的。

2 golang 调用c++ 实例

源码: example
目录结构:
exampleOne 一定要放在src中,防止找不到包

$gopath:
    src - exampleOne 
             -  main.go
             -  temp
               - one.go,  point.cxx  point.hxx  wrap_point.cxx  wrap_point.hxx 

temp中是从网上下的源码,不作改动。
然后就可以执行:

go run main.go

文件与代码:
main.go

// main.go
package main
import (
"funcExample/temp"
)

func main () {
   temp.P()
}

temp 下的 one.go

// one.go
package temp

// #include "wrap_point.hxx"
import "C"

import "fmt"

func init() {
        fmt.Println("Hi from Go, about to calculate distance in C++ ...")
        distance := C.distance_between(1.0, 1.0, 2.0, 2.0)
        fmt.Printf("Go has result, distance is: %v\n", distance)
}
func P() {
        distance := C.distance_between(1.0, 1.0, 2.0, 2.0)
       fmt.Println(distance)
}

temp下的 point.cxx

// point.cxx
#include "point.hxx"
#include <math.h>
#include <iostream>

Point::Point(double x, double y): x_(x), y_(y) {
}

double Point::distance_to(Point p) {
  std::cout << "C++ says: finding distance between (" << x_ << "," << y_
    << "), and (" << p.x_ << "," << p.y_ << ")" << std::endl;
  double x_dist = x_ - p.x_;
  double y_dist = y_ - p.y_;
  return sqrt(x_dist * x_dist + y_dist * y_dist);
}

temp 下的 point.hxx

// point.hxx 
#ifndef POINT_H
#define POINT_H

class Point {
public:
  Point(double x, double y);
  double distance_to(Point p);
private:
  double x_, y_;
};
#endif

temp 下的wrap_point.cxx

// wrap_point.cxx
#include "wrap_point.hxx"
#include "point.hxx"

double distance_between(double x1, double y1, double x2, double y2) {
  return Point(x1, y1).distance_to(Point(x2, y2));
}

temp 下的 wrap_point.hxx

// wrap_point.hxx
#ifndef WRAP_POINT_H
#define WRAP_POINT_H

// __cplusplus gets defined when a C++ compiler processes the file
#ifdef __cplusplus
// extern "C" is needed so the C++ compiler exports the symbols without name
// manging.
extern "C" {
#endif
double distance_between(double x1, double y1, double x2, double y2);
#ifdef __cplusplus
}
#endif
#endif

对于以上的代码自己不做过多说明,都是c/c++ 的用法。
关于cgo语法类型与c的对应关系,可以直接查看cgo文档 command cgo,cgo除了提供直接与c数据类型的对应类型,还支持使用函数将golang数据类型转换成c的数据类型,比如 func C.CString(string) *C.char 见文档说明。

3 其他实例

github源码中的exmapleTwo 是与以上实例类似的。只不过以上对c++类的包装使用比上面更详细。其中的temp也是从网上下载的源码。
更多cgo设置文件include, lib的方式可以看看其他相关资料,此处懒得打字。