您现在的位置是:网站首页> Go语言

Go经验总结

摘要

Go显示和设置Go的环境变量

golang打包成android

项目目录下编译

Go生成go动态库或静态库的方法

Go不定参数

搞定 Go 实时消息推送



项目目录下编译

go build .



Go显示和设置Go的环境变量

go env

go env -w GO111MODULE=auto



golang打包成android

本文只讲述如果使用将golang工程打包成jar/aar/apk,适用于android开发者需要集成一些golang开发的微服务或者其他golang语言实现的功能。

环境安装

1. golang开发环境安装

安装包下载地址:下载,windows建议下载msi文件,直接安装之后环境变量都配置好了。 创建一个go的workspace,然后创建一个hello.go

//hello.go

//包名

package hello

//引入模块

import "fmt"

//main函数/程序入口

func main() {

   fmt.Println("Hello, World!")

}

执行命令:go run hello.go


2. Android 开发环境安装

android studio 已默认安装了编译需要的ndk,但是路径并没有添加到path中,所以执行ndk-build时会提示找不到命令,这时只要找到ANDROID_SDK_ROOT/ndk和ANDROID_SDK_ROOT/ndk-bundle,将其添加到path中,这时的环境已经能满足下一步的要求了。

3. gomobile安装

要将go打包成aar/jar/apk以及IOS应用(需要xcode环境),则需要一个go的打包工具gomobile, 安装方式有两种

#方法1: 

    使用命令:`go get golang.org/x/mobile/cmd/gomobile`(需要翻墙)

#方法2:

    #下载源码包:

    git clone https://github.com/golang/mobile.git

    #将源码拷贝到 $GOPATH/src/golang.org/x

    #执行命令: 

    go build golang.org/x/mobile/cmd/gomobile

    #这时会在C:\Users\username\go\bin下生成gobind和gomobile两个文件

#gomobile的初始化,

    gomobile init

4. 打包示例

#将hello工程目录拷贝到C:\Go\src下面

#打包sdk

    gomobile bind -target=android hello

#会在当前目录下生成hello.arr 和 hello.jar 两个sdk包

#打包apk

    gomobile build -target=android hello

#会在当前目录下直接生成hello.apk


gomobile编译

(1.)下载代码,并生成二进制文件,gomobile和gobind


go get golang.org/x/mobile/cmd/gomobile

或者

git clone https://github.com/golang/mobile

copy到$GOPATH/src/golang.org/x/


//编译生成gobind二进制文件

cd mobile/cmd/gobind

go build .

拷贝gobind到$GOPATH/bin目录,并加入环境变量


//编译生成gomobile二进制文件

cd mobile/cmd/gomobile

go build .

拷贝gomobile到$GOPATH/bin目录,并加入环境变量


//查看是否安装成功

gomobile version

gomobile example测试

gomobile init


//此命令会生成名为basic的apk安装包

gomobile build -target=android golang.org/x/mobile/example/basic


//此命令将安装apk包到已连接的android设备

gomobile install golang.org/x/mobile/example/basic


//生成jar、aar文件

gomobile bind -target=android golang.org/x/mobile/example/bind/hello

或 D:\go\src\golang.org\x\mobile\example\bind>gomobile bind -target=android ./hello

gomobile example编译错误处理

配置环境变量

1.png

gomobile-ipfs编译

(1.)go代码中编译

代理路径: https://github.com/ipfs-shipyard/gomobile-ipfs


cd /go/bind/

gomobile bind -target=android ./ipfs

生成jar和aar文件


gomobile bind -target=android hello

生成hello.aar文件和hello-sources.jar文件,放到Android工程的libs目录里,aar文件供调用,source.jar可以看源码。

// Copyright 2015 The Go Authors. All rights reserved.

// Use of this source code is governed by a BSD-style

// license that can be found in the LICENSE file.

 

// Package hello is a trivial package for gomobile bind example.

package hello

 

import "fmt"

 

func Greetings(name string) string {

fmt.Printf("hello go,this is log\n")

return fmt.Sprintf("Hello aaa, %s!", name)

}

 

func Test1(buf []byte) []byte{

 

fmt.Printf("in buf is:%x\n",buf)

outbuf := []byte{0x31,0x32,0x33,0x34}

return outbuf

   

}

然后应用里就可以很爽的调用:

/*

 * Copyright 2015 The Go Authors. All rights reserved.

 * Use of this source code is governed by a BSD-style

 * license that can be found in the LICENSE file.

 */

 

package org.golang.example.bind;

 

import android.app.Activity;

import android.os.Bundle;

import android.util.Log;

import android.widget.TextView;

 

import hello.Hello;

 

public class MainActivity extends Activity {

 

    private TextView mTextView;

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        mTextView = (TextView) findViewById(R.id.mytextview);

 

        // Call Go function. test String

        String greetings = Hello.greetings("Android and Gopher aaa11");

        mTextView.setText(greetings);

        // Call Go function. test byte[]

        byte[] inbuf = new byte[6];

        inbuf[0] = 0x39;

        inbuf[1] = 0x38;

        inbuf[2] = 0x37;

        inbuf[3] = 0x36;

        byte[] outbuf =  Hello.test1(inbuf);

       System.out.println(outbuf);

    }

}

AndroidStdio的配置麻不麻烦呢?也不麻烦。配置下引用外部库即可。

dependencies {

    implementation fileTree(include: ['*.jar'], dir: 'libs')

    implementation fileTree(include: ['*.aar'], dir: 'libs')

    implementation 'com.android.support:appcompat-v7:22.1.1'

    //implementation project(':hello')

    implementation files('libs/hello.aar')

}

最后再来说下环境,也很简单,只需配置一次即可。

总结下:


直接用AndroidNDK编写SDK,需要自己写JNI。而gomobile一个命令,把脏活累活都给弄好了。

可以一份代码支持Android和iOS,维护上比较方便。

体积上,gomobile的so最起码有2.8MB,比C要大不少,也还能接受。因为效率高啊。


如果再有人找我封装JNI层的.so?我想,我想用go来做!


至于执行的效率,可反编译过来看下,其实内部还是调的c的JNI,只不过gomobile命令把这些繁琐的事做了。


效率应差不了多少。至于稳定性,虽然gomobile是谷歌内部的一个实验性项目,但是你只使用gobind做native层的工作,这部分已经很稳定了。

gomobile init之前需要环境变量中配置了ndk环境,可把ndk环境加到系统环境变量,或者通过ndk标签指定ndk目录gomobile init -ndk 指定。注意,要求ndk版本是在19以上才行


Go生成go动态库或静态库的方法

预备知识

plugin模式

插件运行方式

go plugin包使用

相关知识(推荐:go语言教程)

go build 可以指定buildmode。分为了多种模式。具体模式如下。

模式 说明

当前go版本 1.10.3

archive 编译成二进制文件。一般是静态库文件。 xx.a

c-archive 编译成C归档文件。C可调用的静态库。xx.a。注意要编译成此类文件需要import C 并且要外部调用的函数要使用 “//export 函数名” 的方式在函数上方注释。否则函数默认不会被导出。

c-shared 编译成C共享库。同样需要 import “C” 和在函数上方注释 // export xxx

default 对于有main包的直接编译成可执行文件。没有main包的,编译成.a文件

exe 编译成window可执行程序

plugin 将main包和依赖的包一起编译成go plugin。非main包忽略。【类似C的共享库或静态库。插件式开发使用】

实例

结构:


    -softplugin         //根目录

        -soft           //软件目录

        -plugins         //插件目录

        -itf            //接口目录

无自定义数据

// plugins/hello.go

package main

import "fmt"

func Hello(){

    fmt.Println("hello")

}

// go build -buildmode=plugin -o hello.so hello.go 


// soft/basetype.go

package main

import (

    "os"

    "path"

    "plugin"

    "fmt"


func main(){

    //加载插件

    pluginDir := "../plugins"

    //扫描文件夹下所有so文件

    f, err := os.OpenFile(pluginDir, os.O_RDONLY, 0666)

    if err != nil {

        panic(err)

    }

    fi, err := f.Readdir(-1)

    if err != nil {

        panic(err)

    }

    plugins := make([]os.FileInfo, 0)

    for _, ff := range fi {

        if ff.IsDir() || path.Ext(ff.Name()) != ".so" {

            continue

        }

        plugins = append(plugins, ff)

        pdll, err := plugin.Open(pluginDir + "/" + ff.Name())

        if err != nil {

            fmt.Println(err)

            continue

        }

        plg, err := pdll.Lookup("Hello")

        if err != nil {

            panic(err)

        }

       plg.(func())()

    }

}

// go run basetype.go

定义插件接口 interface。

//------------------------------------------------------

// itf/itf1.go

package itf

type Printor interface{

    Print(v interface{})

}

//------------------------------------------------------

// plugins/p1.go

package main

import (

    "fmt"

    "softplugin/itf"

)

type ScreenPrintor struct{}

func (ScreenPrintor)Print(v interface{}){

    fmt.Println("p1p1 ",v)

}

func Install() Printor{

    return &ScreenPrintor{}

}


//-----------------------------------------------------


// soft/s1.go

package main

import (

    "softplugin/itf"

    "os"

    "path"

    "plugin"

    "fmt"

)

var(

    printors = make([]itf.Printor, 0)

)

func main(){

    //加载插件

    pluginDir := "../plugins"

    //扫描文件夹下所有so文件

    f, err := os.OpenFile(pluginDir, os.O_RDONLY, 0666)

    if err != nil {

        panic(err)

    }

    fi, err := f.Readdir(-1)

    if err != nil {

        panic(err)

    }

    plugins := make([]os.FileInfo, 0)

    for _, ff := range fi {

        if ff.IsDir() || path.Ext(ff.Name()) != ".so" {

            continue

        }

        plugins = append(plugins, ff)

        pdll, err := plugin.Open(pluginDir + "/" + ff.Name())

        if err != nil {

            fmt.Println(err)

            continue

        }

        plg, err := pdll.Lookup("Hello")

        if err != nil {

            panic(err)

        }

       printors = append(printors, (plg.(func() itf.Printor))())

    }

    for _, p := range printors {

        p.Print("pppp")

    }   

}



Go不定参数

package main

import "fmt"

func MyPrintf(args ...interface{}) {

    for _, arg := range args {

        switch arg.(type) {

            case int:

                fmt.Println(arg, "is an int value.")

            case string:

                fmt.Println(arg, "is a string value.")

            case int64:

                fmt.Println(arg, "is an int64 value.")

            default:

                fmt.Println(arg, "is an unknown type.")

        }

    }

}

func main() {

    var v1 int = 1

    var v2 int64 = 1111

    var v3 string = "hello world!"

    var v4 float32 = 1.1111

    MyPrintf(v1, v2, v3, v4)

}



func P (v... string) {


for _,item := range v {


    fmt.Println("item:",item)


    }


}


func main() {


    var l []string


    l = append(l,"a")


    l = append(l,"b")


    fmt.Println("l is ",l)


    P(l...)


}



搞定 Go 实时消息推送

在 Web 开发中,实时数据推送是一个常见需求。比如,股票价格更新或聊天消息通知。Server-Sent Events (SSE) 是一种基于 HTTP 的轻量级技术,特别适合服务器主动向客户端推送更新的场景。今天,我们将结合 go-zero,带你一步步实现一个简单的 SSE 服务,并附上完整代码和运行步骤。


什么是 SSE?

SSE(Server-Sent Events)是 HTML5 提供的一种技术,允许服务器通过持久化的 HTTP 连接向客户端单向推送事件。相比 WebSocket,SSE 更轻量,支持简单的实时更新场景,且基于标准 HTTP 协议,开箱即用。


SSE 的核心特点

单向通信:服务器主动推送,客户端被动接收。

简单协议:基于 text/event-stream 格式,易于实现。

自动重连:浏览器内置重连机制,断开后可自动尝试恢复。

接下来,我们用 go-zero 实现一个 SSE 服务,功能是每秒向客户端推送当前服务器时间。


实现步骤

1. 项目初始化

首先,确保你已安装 Go 并引入 go-zero 依赖:


go get -u github.com/zeromicro/go-zero

创建一个项目目录,结构如下:


sse-demo/

├── main.go         # 主程序

└── static/

    └── index.html  # 前端页面


2. 编写服务端代码

我们将使用 go-zero 的 REST 服务,同时集成 SSE 和静态文件服务。完整代码如下:


package main


import (

    "fmt"

    "net/http"

    "sync"

    "time"


    "github.com/zeromicro/go-zero/core/logx"

    "github.com/zeromicro/go-zero/rest"

)


type SseHandler struct {

    clients map[chan string]struct{}

    lock sync.RWMutex

}


func NewSseHandler() *SseHandler {

    return &SseHandler{

        clients: make(map[chan string]struct{}),

    }

}


// Serve 处理 SSE 连接

func (h *SseHandler) Serve(w http.ResponseWriter, r *http.Request) {

    // 设置 SSE 必需的 HTTP 头

    w.Header().Add("Content-Type", "text/event-stream")

    w.Header().Add("Cache-Control", "no-cache")

    w.Header().Add("Connection", "keep-alive")


    // 为每个客户端创建一个 channel

    clientChan := make(chan string)

    h.lock.Lock()

    h.clients[clientChan] = struct{}{}

    h.lock.Unlock()


    // 客户端断开时清理

    defer func() {

        h.lock.Lock()

        delete(h.clients, clientChan)

        h.lock.Unlock()

        close(clientChan)

    }()


    // 持续监听并推送事件

    for {

        select {

        case msg := <-clientChan:

            // 发送事件数据

            fmt.Fprintf(w, "data: %s\n\n", msg)

            w.(http.Flusher).Flush()

        case <-r.Context().Done():

            // 客户端断开连接

            return

        }

    }

}


// SimulateEvents 模拟周期性事件

func (h *SseHandler) SimulateEvents() {

    ticker := time.NewTicker(time.Second)

    defer ticker.Stop()


    for range ticker.C {

        message := fmt.Sprintf("Server time: %s", time.Now().Format(time.RFC3339))

        // 广播给所有客户端

        h.lock.RLock()

        for clientChan := range h.clients {

            select {

            case clientChan <- message:

            default:

                // 跳过阻塞的 channel

            }

        }

        h.lock.RUnlock()

    }

}


func main() {

    // 创建 go-zero REST 服务,集成静态文件服务

    server := rest.MustNewServer(rest.RestConf{

        Host: "0.0.0.0",

        Port: 8080,

    }, rest.WithFileServer("/static", http.Dir("static")))

    defer server.Stop()


    // 初始化 SSE 处理

    sseHandler := NewSseHandler()


    // 注册 SSE 路由

    server.AddRoute(rest.Route{

        Method:  http.MethodGet,

        Path:    "/sse",

        Handler: sseHandler.Serve,

    }, rest.WithTimeout(0))


    // 在单独的 goroutine 中模拟事件

    go sseHandler.SimulateEvents()


    logx.Info("Server starting on :8080")

    server.Start()

}


代码解析

SseHandler 结构:

使用 map[chan string]bool 维护所有客户端的 channel,方便广播消息。

NewSseHandler 初始化这个 map。

Serve 方法:

设置 SSE 必需的 HTTP 头:Content-Type: text/event-stream、Cache-Control: no-cache 和 Connection: keep-alive。

为每个连接创建一个 channel,存储到 clients 中。

使用 select 监听 channel 消息或客户端断开信号(通过 r.Context().Done())。

收到消息时,格式化为 SSE 协议(data: 消息\n\n),并通过 Flush() 立即推送。

SimulateEvents 方法:

使用 time.Ticker 每秒生成一个事件(当前时间)。

遍历 clients,将消息广播给所有连接的客户端。

使用非阻塞发送(select + default),避免某个客户端阻塞影响整体。

main 函数:

使用 rest.MustNewServer 创建服务,监听 8080 端口。

通过 rest.WithFileServer 配置静态文件服务,映射 /static 到本地 static 目录。

注册 /sse 路由,绑定 SseHandler.Serve,并禁用超时,确保长连接不会被超时机制中断,如果是在 api 文件中定义 SSE 路由,需要加上 timeout: 0s。

在 goroutine 中启动事件模拟。

3. 编写前端代码

在 static/index.html 中编写简单的客户端代码:


<!DOCTYPE html>

<html>

<head>

    <title>SSE 示例</title>

</head>

<body>

    <h1>Server-Sent Events 演示</h1>

    <div id="events"></div>


    <script>

        const eventList = document.getElementById('events');

        // 连接到同一服务器的 SSE 端点

        const source = new EventSource('/sse');


        source.onmessage = function(event) {

            const newElement = document.createElement("p");

            newElement.textContent = event.data;

            eventList.appendChild(newElement);

        };


        source.onerror = function() {

            console.log("发生错误");

        };

    </script>

</body>

</html>

前端解析

使用 EventSource 连接到 /sse 端点。

onmessage 回调接收服务器推送的数据,动态添加到页面。

onerror 处理连接错误(例如服务器关闭)。

4. 运行和测试

保存文件:确保 main.go 和 static/index.html 在正确的位置。

启动服务:go run main.go

访问页面:打开浏览器,输入 http://localhost:8080/static/index.html。

效果:页面每秒显示一条新的服务器时间。


项目地址

https://github.com/zeromicro/go











Top