本文目錄一覽:
如何在golang 中調用c的靜態庫或者動態庫
Cgo 使得Go程序能夠調用C代碼. cgo讀入一個用特別的格式寫的Go語言源文件, 輸出Go和C程序, 使得C程序能打包到Go語言的程序包中.
舉例說明一下. 下面是一個Go語言包, 包含了兩個函數 — Random 和 Seed — 是C語言庫中random和srandom函數的馬甲.
package rand
/*
#include stdlib.h
*/ import “C” func Random() int { return int(C.random()) } func Seed(i int) { C.srandom(C.uint(i)) }
我們來看一下這裡都有什麼內容. 開始是一個包的導入語句.
rand包導入了”C”包, 但你會發現在Go的標準庫里沒有這個包. 那是因為C是一個”偽包”, 一個為cgo引入的特殊的包名, 它是C命名空間的一個引用.
rand 包包含4個到C包的引用: 調用 C.random和C.srandom, 類型轉換 C.uint(i)還有引用語句.
Random函數調用libc中的random函數, 然後回返結果. 在C中, random返回一個C類型的長整形值, cgo把它輪換為C.long. 這個值必需轉換成Go的類型, 才能在Go程序中使用. 使用一個常見的Go類型轉換:
func Random() int { return int(C.random()) }
這是一個等價的函數, 使用了一個臨時變量來進行類型轉換:
func Random() int { var r C.long = C.random() return int(r) }
Seed函數則相反. 它接受一個Go語言的int類型, 轉換成C語言的unsigned int類型, 然後傳遞給C的srandom函數.
func Seed(i int) { C.srandom(C.uint(i)) }
需要注意的是, cgo中的unsigned int類型寫為C.uint; cgo的文檔中有完整的類型列表.
這個例子中還有一個細節我們沒有說到, 那就是導入語句上面的注釋.
/*
#include stdlib.h
*/ import “C”
Cgo可以識別這個注釋, 並在編譯C語言程序的時候將它當作一個頭文件來處理. 在這個例子中, 它只是一個include語句, 然而其實它可以是使用有效的C語言代碼. 這個注釋必需緊靠在import “C”這個語句的上面, 不能有空行, 就像是文檔注釋一樣.
Strings and things
與Go語言不同, C語言中沒有顯式的字符串類型. 字符串在C語言中是一個以0結尾的字符數組.
Go和C語言中的字符串轉換是通過C.CString, C.GoString,和C.GoStringN這些函數進行的. 這些轉換將得到字符串類型的一個副本.
下一個例子是實現一個Print函數, 它使用C標準庫中的fputs函數把一個字符串寫到標準輸出上:
package print // #include stdio.h // #include stdlib.h import “C” import “unsafe” func Print(s string) { cs := C.CString(s) C.fputs(cs, (*C.FILE)(C.stdout)) C.free(unsafe.Pointer(cs)) }
在C程序中進行的內存分配是不能被Go語言的內存管理器感知的. 當你使用C.CString創建一個C字符串時(或者其它類型的C語言內存分配), 你必需記得在使用完後用C.free來釋放它.
調用C.CString將返回一個指向字符數組開始處的指錯, 所以在函數退出前我們把它轉換成一個unsafe.Pointer(Go中與C的void 等價的東西), 使用C.free來釋放分配的內存. 一個慣用法是在分配內存後緊跟一個defer(特別是當這段代碼比較複雜的時候), 這樣我們就有了下面這個Print函數:
func Print(s string) { cs := C.CString(s) defer C.free(unsafe.Pointer(cs)) C.fputs(cs, (*C.FILE)(C.stdout)) }
構建 cgo 包
如果你使用goinstall, 構建cgo包就比較容易了, 只要調用像平常一樣使用goinstall命令, 它就能自動識別這個特殊的import “C”, 然後自動使用cgo來編譯這些文件.
如果你想使用Go的Makefiles來構建, 那在CGOFILES變量中列出那些要用cgo處理的文件, 就像GOFILES變量包含一般的Go源文件一樣.
rand包的Makefile可以寫成下面這樣:
include $(GOROOT)/src/Make.inc
TARG=goblog/rand
CGOFILES=\ rand.go\ include $(GOROOT)/src/Make.pkg
然後輸入gomake開始構建.
更多 cgo 的資源
cgo的文檔中包含了關於C偽包的更多詳細的說明, 以及構建過程. Go代碼樹中的cgo的例子給出了更多更高級的用法.
一個簡單而又符合Go慣用法的基於cgo的包是Russ Cox寫的gosqlite. 而Go語言的網站上也列出了更多的的cgo包.
最後, 如果你對於cgo的內部是怎麼運作這個事情感到好奇的話, 去看看運行時包的cgocall.c文件的注釋吧.
GoLang — Gin框架
• 何為框架:
框架一直是敏捷開發中的利器,能讓開發者很快的上手並做出應用,甚至有的時候,脫離了框架,一些開發者都不會寫程序了。成長總不會一蹴而就,從寫出程序獲取成就感,再到精通框架,快速構造應用,當這些方面都得心應手的時候,可以嘗試改造一些框架,或是自己創造一個。
Gin是一個golang的微框架,封裝比較優雅,API友好,源碼注釋比較明確,已經發佈了1.0版本。具有快速靈活,容錯方便等特點。其實對於golang而言,web框架的依賴要遠比Python,Java之類的要小。自身的net/http足夠簡單,性能也非常不錯。框架更像是一些常用函數或者工具的集合。藉助框架開發,不僅可以省去很多常用的封裝帶來的時間,也有助於團隊的編碼風格和形成規範。
(1)首先需要安裝,安裝比較簡單,使用go get即可
go get github.com/gin-gonic/gin
如果安裝失敗,直接去Github clone下來,放置到對應的目錄即可。
(2)代碼中使用:
下面是一個使用Gin的簡單例子:
package main
import (
“github.com/gin-gonic/gin”
)
func main() {
router := gin.Default()
router.GET(“/ping”, func(c *gin.Context) {
c.JSON(200, gin.H{
“message”: “pong”,
})
})
router.Run(“:8080”) // listen and serve on 0.0.0.0:8080
}
簡單幾行代碼,就能實現一個web服務。使用gin的Default方法創建一個路由handler。然後通過HTTP方法綁定路由規則和路由函數。不同於net/http庫的路由函數,gin進行了封裝,把request和response都封裝到gin.Context的上下文環境。最後是啟動路由的Run方法監聽端口。麻雀雖小,五臟俱全。當然,除了GET方法,gin也支持POST,PUT,DELETE,OPTION等常用的restful方法。
Gin可以很方便的支持各種HTTP請求方法以及返回各種類型的數據,詳情可以前往查看。
2.1 匹配參數
我們可以使用Gin框架快速的匹配參數,如下代碼所示:
冒號:加上一個參數名組成路由參數。可以使用c.Param的方法讀取其值。當然這個值是字串string。諸如/user/rsj217,和/user/hello都可以匹配,而/user/和/user/rsj217/不會被匹配。
瀏覽器輸入以下測試:
返回結果為:
其中c.String是gin.Context下提供的方法,用來返回字符串。
其中c.Json是gin.Context下提供的方法,用來返回Json。
下面我們使用以下gin提供的Group函數,方便的為不同的API進行分類。
我們創建了一個gin的默認路由,並為其分配了一個組 v1,監聽hello請求並將其路由到視圖函數HelloPage,最後綁定到 0.0.0.0:8000
C.JSON是Gin實現的返回json數據的內置方法,包含了2個參數,狀態碼和返回的內容。http.StatusOK代表返回狀態碼為200,正文為{“message”: 「welcome”}。
註:Gin還包含更多的返回方法如c.String, c.HTML, c.XML等,請自行了解。可以方便的返回HTML數據
我們在之前的組v1路由下新定義一個路由:
下面我們訪問
可以看到,通過c.Param(「key」)方法,Gin成功捕獲了url請求路徑中的參數。同理,gin也可以捕獲常規參數,如下代碼所示:
在瀏覽器輸入以下代碼:
通過c.Query(「key」)可以成功接收到url參數,c.DefaultQuery在參數不存在的情況下,會由其默認值代替。
我們還可以為Gin定義一些默認路由:
這時候,我們訪問一個不存在的頁面:
返回如下所示:
下面我們測試在Gin裏面使用Post
在測試端輸入:
附帶發送的數據,測試即可。記住需要使用POST方法.
繼續修改,將PostHandler的函數修改如下
測試工具輸入:
發送的內容輸入:
返回結果如下:
備註:此處需要指定Content-Type為application/x-www-form-urlencoded,否則識別不出來。
一定要選擇對應的PUT或者DELETE方法。
Gin框架快速的創建路由
能夠方便的創建分組
支持url正則表達式
支持參數查找(c.Param c.Query c.PostForm)
請求方法精準匹配
支持404處理
快速的返回給客戶端數據,常用的c.String c.JSON c.Data
bpftrace動態追蹤golang應用-函數內聯問題
在上一篇文章的golang代碼中,函數add的上一行,增加了一條注釋語句: //go:noinline 。在bpftrace追蹤時,是否可以去掉?有什麼作用?
為了說明該問題,設計一個例子。
golang代碼中,有兩個求和函數。其中,add1加上 //go:noinline ,另一個add2不加。代碼如下:
bpftrace程序分別對函數add1和add2的輸入參數、返回值進行追蹤,代碼如下:
執行程序後,可以看到bpftrace程序能夠正常追蹤到函數add1,但是無法追蹤到函數add2。
通過上文中的示例代碼,可以看到,沒有加 //go:noinline 的函數無法被bpftrace程序追蹤到。通過查閱golang相關文檔,可以知道, //go:noinline 表示該函數在編譯時,不會被內聯。
使用 objump -S 生成golang程序的彙編代碼如下:
通過彙編代碼,我們可以看到,主函數中,地址 0x498e52 處 callq 498e00 調用了add1函數,地址 0x498ebb 處 movq $0x4,(%rsp) 直接計算求值。
因此,golang編譯器在編譯代碼時,會對代碼進行分析,並按照內聯規則,將某些函數生成內聯代碼。一旦函數被內聯,bpftrace將無法追蹤到對應函數。也就是,上文中函數 add2 無法被追蹤到。
針對golang程序中編譯器內聯的問題,可以通過禁止內聯的方式來解決。禁止內聯的方式有:
在實踐中,可以通過 go build -gcflags=”-m -m” 來查看,哪些函數會在編譯時執行內聯,如:
從輸出中,可以看到:
關於golang編譯器進行內聯的場景,可以參考golang源碼:。
由於golang編譯器內聯優化,bpftrace可能無法正常追蹤golang程序。在編寫bpftrace腳本時,可以先使用 nm 命令查看一下可執行程序,是否存在需要追蹤的函數的符號信息。如果沒有則bpftrace將不能對其進行追蹤。
前面的示例中,都是對 int 類型的參數進行追蹤,那對於 string 類型的參數,是否也可以用同樣的方式進行追蹤?將在下一篇中進行討論。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/159005.html