最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go?對多個網(wǎng)絡(luò)命令空間中的端口進行監(jiān)聽的解決方案

 更新時間:2024年07月04日 08:59:57   作者:文一路挖坑俠  
這篇文章主要介紹了Go?如何對多個網(wǎng)絡(luò)命令空間中的端口進行監(jiān)聽,本文給大家介紹的非常詳細,需要的朋友可以參考下

需求為 對多個命名空間內(nèi)的端口進行監(jiān)聽和代理。

剛開始對 netns 的理解不夠深刻,以為必須存在一個新的線程然后調(diào)用 setns(2) 切換過去,如果有新的 netns 那么需要再新建一個線程切換過去使用,這樣帶來的問題就是線程數(shù)量和 netns 的數(shù)量為 1:1,資源占用會比較多。

當(dāng)時沒有想到別的好辦法,Go 里面也不能創(chuàng)建線程,只能想到使用一個 C 進程來實現(xiàn)這個功能,這里就多了 通信交互/協(xié)議解析處理/資源占用 的成本。

新方案

后面在 stackoverflow 中閑逛看到一篇文章 https://stackoverflow.com/questions/28846059/can-i-open-sockets-in-multiple-network-namespaces-from-my-python-code,看到了關(guān)鍵點 在套接字創(chuàng)建之前,切換到對應(yīng)的命名空間,并不需要創(chuàng)建線程

這樣就可以一個線程下對多個命名空間的端口進行監(jiān)聽,可以減少線程本身資源的占用以及額外的管理成本。

原來 C 實現(xiàn)的改造比較好實現(xiàn),刪除創(chuàng)建線程那一步差不多就可以了。如何更進一步使用 Go 實現(xiàn),減少維護的成本?

使用 Go 進行實現(xiàn)

保證套接字創(chuàng)建時在某個命名空間內(nèi),就可以完成套接字后續(xù)的操作,不必使用一個線程來持有一個命名空間,建立一個典型的 TCP 服務(wù)如下

  • 獲取并且保存默認網(wǎng)絡(luò)命名空間
  • 加鎖防止多個網(wǎng)絡(luò)命名空間同時切換,將 goroutine 綁定到當(dāng)前的線程上防止被調(diào)度
  • 獲取需要操作的網(wǎng)絡(luò)命名空間,并且切換過去 setns
  • 監(jiān)聽套接字 net.Listen
  • 切換到默認的命名空間(還原)
  • 釋放當(dāng)前線程的綁定,釋放鎖

實現(xiàn)對 TCP 的監(jiān)聽

使用 github.com/vishvananda/netns 這個庫對網(wǎng)絡(luò)命名空間進行操作,一個同時在 默認/ns1/ns2 三個命名空間內(nèi)監(jiān)聽 8000 端口的例子如下:

命名空間創(chuàng)建命令

ip netns add ns1
ip netns add ns2
package main
import (
	"net"
	"runtime"
	"sync"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/vishvananda/netns"
)
var (
	mainNetnsHandler netns.NsHandle
	mainNetnsMutex   sync.Mutex
)
func mustInitMainNetnsHandler() {
	nh, err := netns.Get()
	if err != nil {
		panic(err)
	}
	mainNetnsHandler = nh
}
func ListenInsideNetns(ns, network, address string) (net.Listener, error) {
	if ns == "" {
		return net.Listen(network, address)
	}
	var set bool
	mainNetnsMutex.Lock()
	runtime.LockOSThread()
	defer func() {
		if set {
			err := netns.Set(mainNetnsHandler)
			if err != nil {
				logrus.WithError(err).Warn("Fail to back to main netns")
			}
		}
		runtime.UnlockOSThread()
		mainNetnsMutex.Unlock()
	}()
	nh, err := netns.GetFromName(ns)
	if err != nil {
		return nil, errors.Wrap(err, "netns.GetFromName")
	}
	defer nh.Close()
	err = netns.Set(nh)
	if err != nil {
		return nil, errors.Wrap(err, "netns.Set")
	}
	set = true
	return net.Listen(network, address)
}
func serve(listener net.Listener) error {
	for {
		conn, err := listener.Accept()
		if err != nil {
			return err
		}
		logrus.WithFields(logrus.Fields{"local": conn.LocalAddr(), "remote": conn.RemoteAddr()}).Info("New conn")
		conn.Write([]byte("hello"))
		conn.Close()
	}
}
func main() {
	mustInitMainNetnsHandler()
	wg := sync.WaitGroup{}
	wg.Add(3)
	go func() {
		defer wg.Done()
		lis, err := ListenInsideNetns("", "tcp", ":8000")
		if err != nil {
			panic(err)
		}
		logrus.WithFields(logrus.Fields{"netns": "", "addr": lis.Addr()}).Info("Listen on")
		serve(lis)
	}()
	go func() {
		defer wg.Done()
		lis, err := ListenInsideNetns("ns1", "tcp", ":8000")
		if err != nil {
			panic(err)
		}
		logrus.WithFields(logrus.Fields{"netns": "ns1", "addr": lis.Addr()}).Info("Listen on")
		serve(lis)
	}()
	go func() {
		defer wg.Done()
		lis, err := ListenInsideNetns("ns2", "tcp", ":8000")
		if err != nil {
			panic(err)
		}
		logrus.WithFields(logrus.Fields{"netns": "ns2", "addr": lis.Addr()}).Info("Listen on")
		serve(lis)
	}()
	wg.Wait()
}

UDP/SCTP 的監(jiān)聽

UDP 監(jiān)聽和 TCP 無異,Go 會做好調(diào)度不會產(chǎn)生新線程。

SCTP 如果是使用庫 github.com/ishidawataru/sctp,那么需要注意這個庫就是簡單的 fd 封裝,并且其 Accept() 是一個阻塞的動作,在 for 循環(huán)內(nèi)調(diào)用 Accept() 會導(dǎo)致 Go runtime 會創(chuàng)建一個新線程來防止阻塞。

解決方案如下,直接操作 fd

  • 設(shè)置非阻塞
  • 手動使用 epoll 封裝(必須是 epoll,select/poll 在幾百個fd的情況下性能很差,無連接的情況負載都很高)。

獲取 fd 的方式如下

type sctpWrapListener struct {
	*sctp.SCTPListener
	fd int
}
func listenSCTP(network, address string) (*sctpWrapListener, error) {
	addr, err := parseSCTPAddr(address)
	if err != nil {
		return nil, err
	}
	sctpFd := 0
	sc := sctp.SocketConfig{
		InitMsg: sctp.InitMsg{NumOstreams: sctp.SCTP_MAX_STREAM},
		Control: func(network, address string, c syscall.RawConn) error {
			return c.Control(func(fd uintptr) {
				err := syscall.SetNonblock(int(fd), true)
				if err != nil {
					syscall.Close(int(fd))
					return
				}
				sctpFd = int(fd)
			})
		},
	}
	l, err := sc.Listen(network, addr)
	if err != nil {
		return nil, err
	}
	return &sctpWrapListener{SCTPListener: l, fd: sctpFd}, nil
}

實際應(yīng)用的數(shù)據(jù)參考

打開的文件如下

root@localhost:~# lsof -p $(pidof fake_name) | tail
fake_name 1599860 root 1203u     sock                0,8       0t0   20374830 protocol: UDP
fake_name 1599860 root 1204u     pack           20375161       0t0        ALL type=SOCK_RAW
fake_name 1599860 root 1205u     sock                0,8       0t0   20374831 protocol: SCTPv6
fake_name 1599860 root 1206u     sock                0,8       0t0   20375156 protocol: TCP
fake_name 1599860 root 1207u     sock                0,8       0t0   20375157 protocol: UDP
fake_name 1599860 root 1208u     sock                0,8       0t0   20375158 protocol: SCTPv6
fake_name 1599860 root 1209u     pack           20381769       0t0        ALL type=SOCK_RAW
fake_name 1599860 root 1210u     sock                0,8       0t0   20381764 protocol: TCP
fake_name 1599860 root 1211u     sock                0,8       0t0   20381765 protocol: UDP
fake_name 1599860 root 1212u     sock                0,8       0t0   20381766 protocol: SCTPv6
root@localhost:~# lsof -p $(pidof fake_name) | wc -l
1216

業(yè)務(wù)機器CPU為 4 核心,創(chuàng)建的線程如下

root@localhost:~# ll /proc/$(pidof fake_name)/task
total 0
dr-xr-xr-x 13 root root 0 Jul  3 14:51 ./
dr-xr-xr-x  9 root root 0 Jul  3 14:51 ../
dr-xr-xr-x  7 root root 0 Jul  3 14:51 1599860/
dr-xr-xr-x  7 root root 0 Jul  3 14:57 1599861/
dr-xr-xr-x  7 root root 0 Jul  3 14:57 1599862/
dr-xr-xr-x  7 root root 0 Jul  3 14:57 1599863/
dr-xr-xr-x  7 root root 0 Jul  3 14:57 1599864/
dr-xr-xr-x  7 root root 0 Jul  3 14:57 1599865/
dr-xr-xr-x  7 root root 0 Jul  3 14:57 1600021/
dr-xr-xr-x  7 root root 0 Jul  3 14:57 1600033/
dr-xr-xr-x  7 root root 0 Jul  3 14:57 1600056/
dr-xr-xr-x  7 root root 0 Jul  3 14:57 1600058/
dr-xr-xr-x  7 root root 0 Jul  3 14:57 1602524/
root@localhost:~# ll /proc/$(pidof fake_name)/task | wc -l
14

到此這篇關(guān)于Go 如何對多個網(wǎng)絡(luò)命令空間中的端口進行監(jiān)聽的文章就介紹到這了,更多相關(guān)Go 端口監(jiān)聽內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang中unicode碼和中文的互相轉(zhuǎn)換函數(shù)使用

    Golang中unicode碼和中文的互相轉(zhuǎn)換函數(shù)使用

    這篇文章主要為大家介紹了Golang中unicode碼和中文的互相轉(zhuǎn)換函數(shù)使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • golang求連續(xù)子數(shù)組的最大和實例

    golang求連續(xù)子數(shù)組的最大和實例

    這篇文章主要介紹了golang求連續(xù)子數(shù)組的最大和實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Golang?使用os?庫的?ReadFile()?讀文件最佳實踐

    Golang?使用os?庫的?ReadFile()?讀文件最佳實踐

    這篇文章主要介紹了Golang使用os庫的ReadFile()讀文件最佳實踐,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-09-09
  • Go并發(fā)之RWMutex的源碼解析詳解

    Go并發(fā)之RWMutex的源碼解析詳解

    RWMutex是一個支持并行讀串行寫的讀寫鎖。RWMutex具有寫操作優(yōu)先的特點,寫操作發(fā)生時,僅允許正在執(zhí)行的讀操作執(zhí)行,后續(xù)的讀操作都會被阻塞。本文就來從源碼解析一下RWMutex的使用
    2023-03-03
  • go 如何修改postgresql的配置參數(shù)

    go 如何修改postgresql的配置參數(shù)

    這篇文章主要介紹了go 如何修改postgresql的配置參數(shù),本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2024-01-01
  • Go 微服務(wù)開發(fā)框架DMicro設(shè)計思路詳解

    Go 微服務(wù)開發(fā)框架DMicro設(shè)計思路詳解

    這篇文章主要為大家介紹了Go 微服務(wù)開發(fā)框架DMicro設(shè)計思路詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • 如何利用golang運用mysql數(shù)據(jù)庫

    如何利用golang運用mysql數(shù)據(jù)庫

    這篇文章主要介紹了如何利用golang運用mysql數(shù)據(jù)庫,文章對依賴包、db對象注入ApiRouter等內(nèi)容,需要的小伙伴可以參考一下
    2022-03-03
  • go日志庫中的logrus

    go日志庫中的logrus

    這篇文章主要介紹了go日志庫中的logrus主要包括go日志庫logrus的安裝和使用,本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2022-08-08
  • golang日志框架之logrus的安裝使用教程

    golang日志框架之logrus的安裝使用教程

    logrus是一個非常強大的日志框架,具有靈活的功能和易于使用的API,適合處理各種類型的日志需求,這篇文章主要介紹了golang日志框架之logrus的安裝使用,需要的朋友可以參考下
    2023-08-08
  • 詳解Go中Map類型和Slice類型的傳遞

    詳解Go中Map類型和Slice類型的傳遞

    這篇文章主要為大家詳細介紹了Go中Map類型和Slice類型的傳遞,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11

最新評論

盱眙县| 沿河| 洛川县| 巢湖市| 延边| 栾川县| 荆门市| 故城县| 呈贡县| 津市市| 卓尼县| 贺州市| 永靖县| 阳高县| 辰溪县| 益阳市| 鄂州市| 琼结县| 沂源县| 甘孜县| 温宿县| 宿州市| 新田县| 龙泉市| 阳西县| 渝中区| 金秀| 页游| 离岛区| 麻阳| 信宜市| 凤台县| 灵武市| 尖扎县| 莱阳市| 顺平县| 涪陵区| 汾西县| 宁乡县| 廊坊市| 桑植县|