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

利用Golang實(shí)現(xiàn)TCP連接的雙向拷貝詳解

 更新時(shí)間:2017年09月27日 10:53:08   作者:陶文  
公司中遇到了一個(gè)使用golang編寫的agent程序,所以這篇文章主要給大家介紹了關(guān)于利用Go如何實(shí)現(xiàn)TCP連接的雙向拷貝的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考,下面隨著小編來(lái)一起看看吧。

前言

本文主要給大家介紹了關(guān)于Golang實(shí)現(xiàn)TCP連接的雙向拷貝的相關(guān)內(nèi)容,分享出來(lái)供大家參考學(xué)習(xí),下面話不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹吧。

最簡(jiǎn)單的實(shí)現(xiàn)

每次來(lái)一個(gè)Server的連接,就新開(kāi)一個(gè)Client的連接。用一個(gè)goroutine從server拷貝到client,再用另外一個(gè)goroutine從client拷貝到server。任何一方斷開(kāi)連接,雙向都斷開(kāi)連接。

func main() {
 runtime.GOMAXPROCS(1)
 listener, err := net.Listen("tcp", "127.0.0.1:8848")
 if err != nil {
 panic(err)
 }
 for {
 conn, err := listener.Accept()
 if err != nil {
 panic(err)
 }
 go handle(conn.(*net.TCPConn))
 }
}

func handle(server *net.TCPConn) {
 defer server.Close()
 client, err := net.Dial("tcp", "127.0.0.1:8849")
 if err != nil {
 fmt.Print(err)
 return
 }
 defer client.Close()
 go func() {
 defer server.Close()
 defer client.Close()
 buf := make([]byte, 2048)
 io.CopyBuffer(server, client, buf)
 }()
 buf := make([]byte, 2048)
 io.CopyBuffer(client, server, buf)
}

一個(gè)值得注意的地方是io.Copy的默認(rèn)buffer比較大,給一個(gè)小的buffer可以支持更多的并發(fā)連接。

這兩個(gè)goroutine并序在一個(gè)退出之后,另外一個(gè)也退出。這個(gè)的實(shí)現(xiàn)是通過(guò)關(guān)閉server或者client的socket來(lái)實(shí)現(xiàn)的。因?yàn)閟ocket被關(guān)閉了,io.CopyBuffer 就會(huì)退出。

Client端實(shí)現(xiàn)連接池

一個(gè)顯而易見(jiàn)的問(wèn)題是,每次Server的連接進(jìn)來(lái)之后都需要臨時(shí)去建立一個(gè)新的Client的端的連接。這樣在代理的總耗時(shí)里就包括了一個(gè)tcp連接的握手時(shí)間。如果能夠讓Client端實(shí)現(xiàn)連接池復(fù)用已有連接的話,可以縮短端到端的延遲。

var pool = make(chan net.Conn, 100)

func borrow() (net.Conn, error) {
 select {
 case conn := <- pool:
 return conn, nil
 default:
 return net.Dial("tcp", "127.0.0.1:8849")
 }
}

func release(conn net.Conn) error {
 select {
 case pool <- conn:
 // returned to pool
 return nil
 default:
 // pool is overflow
 return conn.Close()
 }
}

func handle(server *net.TCPConn) {
 defer server.Close()
 client, err := borrow()
 if err != nil {
 fmt.Print(err)
 return
 }
 defer release(client)
 go func() {
 defer server.Close()
 defer release(client)
 buf := make([]byte, 2048)
 io.CopyBuffer(server, client, buf)
 }()
 buf := make([]byte, 2048)
 io.CopyBuffer(client, server, buf)
}

這個(gè)版本的實(shí)現(xiàn)是顯而易見(jiàn)有問(wèn)題的。因?yàn)檫B接在歸還到池里的時(shí)候并不能保證是還保持連接的狀態(tài)。另外一個(gè)更嚴(yán)重的問(wèn)題是,因?yàn)閏lient的連接不再被關(guān)閉了,當(dāng)server端關(guān)閉連接時(shí),從client向server做io.CopyBuffer的goroutine就無(wú)法退出了。

所以,有以下幾個(gè)問(wèn)題要解決:

  • 如何在一個(gè)goroutine時(shí)退出時(shí)另外一個(gè)goroutine也退出?
  • 怎么保證歸還給pool的連接是有效的?
  • 怎么保持在pool中的連接仍然是一直有效的?

通過(guò)SetDeadline中斷Goroutine

一個(gè)普遍的觀點(diǎn)是Goroutine是無(wú)法被中斷的。當(dāng)一個(gè)Goroutine在做conn.Read時(shí),這個(gè)協(xié)程就被阻塞在那里了。實(shí)際上并不是毫無(wú)辦法的,我們可以通過(guò)conn.Close來(lái)中斷Goroutine。但是在連接池的情況下,又無(wú)法Close鏈接。另外一種做法就是通過(guò)SetDeadline為一個(gè)過(guò)去的時(shí)間戳來(lái)中斷當(dāng)前正在進(jìn)行的阻塞讀或者阻塞寫。

var pool = make(chan net.Conn, 100)

type client struct {
 conn net.Conn
 inUse *sync.WaitGroup
}

func borrow() (clt *client, err error) {
 var conn net.Conn
 select {
 case conn = <- pool:
 default:
 conn, err = net.Dial("tcp", "127.0.0.1:18849")
 }
 if err != nil {
 return nil, err
 }
 clt = &client{
 conn: conn,
 inUse: &sync.WaitGroup{},
 }
 return
}

func release(clt *client) error {
 clt.conn.SetDeadline(time.Now().Add(-time.Second))
 clt.inUse.Done()
 clt.inUse.Wait()
 select {
 case pool <- clt.conn:
 // returned to pool
 return nil
 default:
 // pool is overflow
 return clt.conn.Close()
 }
}

func handle(server *net.TCPConn) {
 defer server.Close()
 clt, err := borrow()
 if err != nil {
 fmt.Print(err)
 return
 }
 clt.inUse.Add(1)
 defer release(clt)
 go func() {
 clt.inUse.Add(1)
 defer server.Close()
 defer release(clt)
 buf := make([]byte, 2048)
 io.CopyBuffer(server, clt.conn, buf)
 }()
 buf := make([]byte, 2048)
 io.CopyBuffer(clt.conn, server, buf)
}

通過(guò)SetDeadline實(shí)現(xiàn)了goroutine的中斷,然后通過(guò)sync.WaitGroup來(lái)保證這些使用方都退出了之后再歸還給連接池。否則一個(gè)連接被復(fù)用的時(shí)候,之前的使用方可能還沒(méi)有退出。

連接有效性

為了保證在歸還給pool之前,連接仍然是有效的。連接在被讀寫的過(guò)程中如果發(fā)現(xiàn)了error,我們就要標(biāo)記這個(gè)連接是有問(wèn)題的,會(huì)釋放之后直接close掉。但是SetDeadline必然會(huì)導(dǎo)致讀取或者寫入的時(shí)候出現(xiàn)一次timeout的錯(cuò)誤,所以還需要把timeout排除掉。

var pool = make(chan net.Conn, 100)

type client struct {
 conn net.Conn
 inUse *sync.WaitGroup
 isValid int32
}

const maybeValid = 0
const isValid = 1
const isInvalid = 2

func (clt *client) Read(b []byte) (n int, err error) {
 n, err = clt.conn.Read(b)
 if err != nil {
 if !isTimeoutError(err) {
 atomic.StoreInt32(&clt.isValid, isInvalid)
 }
 } else {
 atomic.StoreInt32(&clt.isValid, isValid)
 }
 return
}

func (clt *client) Write(b []byte) (n int, err error) {
 n, err = clt.conn.Write(b)
 if err != nil {
 if !isTimeoutError(err) {
 atomic.StoreInt32(&clt.isValid, isInvalid)
 }
 } else {
 atomic.StoreInt32(&clt.isValid, isValid)
 }
 return
}

type timeoutErr interface {
 Timeout() bool
}

func isTimeoutError(err error) bool {
 timeoutErr, _ := err.(timeoutErr)
 if timeoutErr == nil {
 return false
 }
 return timeoutErr.Timeout()
}

func borrow() (clt *client, err error) {
 var conn net.Conn
 select {
 case conn = <- pool:
 default:
 conn, err = net.Dial("tcp", "127.0.0.1:18849")
 }
 if err != nil {
 return nil, err
 }
 clt = &client{
 conn: conn,
 inUse: &sync.WaitGroup{},
 isValid: maybeValid,
 }
 return
}

func release(clt *client) error {
 clt.conn.SetDeadline(time.Now().Add(-time.Second))
 clt.inUse.Done()
 clt.inUse.Wait()
 if clt.isValid == isValid {
 return clt.conn.Close()
 }
 select {
 case pool <- clt.conn:
 // returned to pool
 return nil
 default:
 // pool is overflow
 return clt.conn.Close()
 }
}

func handle(server *net.TCPConn) {
 defer server.Close()
 clt, err := borrow()
 if err != nil {
 fmt.Print(err)
 return
 }
 clt.inUse.Add(1)
 defer release(clt)
 go func() {
 clt.inUse.Add(1)
 defer server.Close()
 defer release(clt)
 buf := make([]byte, 2048)
 io.CopyBuffer(server, clt, buf)
 }()
 buf := make([]byte, 2048)
 io.CopyBuffer(clt, server, buf)
}

判斷 error 是否是 timeout 需要類型強(qiáng)轉(zhuǎn)來(lái)實(shí)現(xiàn)。

對(duì)于連接池里的conn是否仍然是有效的,如果用后臺(tái)不斷ping的方式來(lái)實(shí)現(xiàn)成本比較高。因?yàn)椴煌膮f(xié)議要連接保持需要不同的ping的方式。一個(gè)最簡(jiǎn)單的辦法就是下次用的時(shí)候試一下。如果連接不好用了,則改成新建一個(gè)連接,避免連續(xù)拿到無(wú)效的連接。通過(guò)這種方式把無(wú)效的連接給淘汰掉。

關(guān)于正確性

本文在杭州機(jī)場(chǎng)寫成,完全不保證內(nèi)容的正確性

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • Golang截取字符串方法示例講解及對(duì)比

    Golang截取字符串方法示例講解及對(duì)比

    這篇文章主要介紹了Golang截取字符串方法,文中介紹了使用rune函數(shù)和utf包以及range遍歷的方式,熟練掌握這些可以幫助我們更方便地處理字符串,提高編程效率和代碼質(zhì)量,感興趣的同學(xué)可以參考下文
    2023-05-05
  • 一文帶你掌握go中的異常處理

    一文帶你掌握go中的異常處理

    代碼在執(zhí)行的過(guò)程中可能因?yàn)橐恍┻壿嬌系膯?wèn)題而出現(xiàn)錯(cuò)誤,我們稱之為異常,那么如何處理這些異常呢,下面小編就來(lái)和大家詳細(xì)聊聊golang中的異常處理機(jī)制吧
    2023-09-09
  • 詳解如何修改Go結(jié)構(gòu)體的私有字段

    詳解如何修改Go結(jié)構(gòu)體的私有字段

    在 Go 語(yǔ)言中,結(jié)構(gòu)體字段的訪問(wèn)權(quán)限是由字段名的首字母決定的:首字母大寫表示公共字段(public),首字母小寫表示私有字段(private),本文給大家介紹了如何修改Go結(jié)構(gòu)體的私有字段,需要的朋友可以參考下
    2025-01-01
  • golang中iota的用法小結(jié)

    golang中iota的用法小結(jié)

    iota是Go語(yǔ)言中的一個(gè)預(yù)定義標(biāo)識(shí)符,用于表示const聲明塊中每個(gè)常量的偏移值,本文主要介紹了golang中iota的用法小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-03-03
  • 詳解玩轉(zhuǎn)直播系列之消息模塊演進(jìn)

    詳解玩轉(zhuǎn)直播系列之消息模塊演進(jìn)

    本篇文章針對(duì)秀場(chǎng)直播,簡(jiǎn)單地描述一下消息模型,說(shuō)明一下我們消息模型的架構(gòu),并結(jié)合我們一年以來(lái),通過(guò)處理不同的業(yè)務(wù)線上問(wèn)題,來(lái)進(jìn)行演進(jìn)式的消息模型架構(gòu)的升級(jí)與調(diào)整,將此整理成文,并分享給大家
    2021-06-06
  • Go基礎(chǔ)教程系列之回調(diào)函數(shù)和閉包詳解

    Go基礎(chǔ)教程系列之回調(diào)函數(shù)和閉包詳解

    這篇文章主要介紹了Go基礎(chǔ)教程系列之回調(diào)函數(shù)和閉包詳解,需要的朋友可以參考下
    2022-04-04
  • Go singleflight使用以及原理

    Go singleflight使用以及原理

    singleflight官方解釋其為:singleflight提供了一個(gè)重復(fù)的函數(shù)調(diào)用抑制機(jī)制。通俗的解釋其作用是,若有多個(gè)協(xié)程運(yùn)行某函數(shù)時(shí),只讓一個(gè)協(xié)程去處理,然后批量返回。非常適合來(lái)做并發(fā)控制。常見(jiàn)用于緩存穿透的情況
    2023-01-01
  • Go語(yǔ)言基礎(chǔ)if條件語(yǔ)句用法及示例詳解

    Go語(yǔ)言基礎(chǔ)if條件語(yǔ)句用法及示例詳解

    這篇文章主要為大家介紹了Go語(yǔ)言基礎(chǔ)if條件語(yǔ)句的用法及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2021-11-11
  • Golang map實(shí)現(xiàn)原理淺析

    Golang map實(shí)現(xiàn)原理淺析

    Go中Map是一個(gè)KV對(duì)集合,下面這篇文章主要給大家介紹了關(guān)于Golang中map探究的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2022-12-12
  • GO語(yǔ)言中ni,零值與空結(jié)構(gòu)體的使用

    GO語(yǔ)言中ni,零值與空結(jié)構(gòu)體的使用

    Go語(yǔ)言為Java開(kāi)發(fā)者帶來(lái)了一些新概念,如零值、nil和空結(jié)構(gòu)體,理解這些概念有助于Go語(yǔ)言的學(xué)習(xí)和應(yīng)用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-10-10

最新評(píng)論

平乐县| 江安县| 四平市| 天津市| 化德县| 青浦区| 巴塘县| 福清市| 永定县| 措美县| 哈尔滨市| 张家界市| 沁水县| 东阿县| 望江县| 通辽市| 卫辉市| 禹城市| 云梦县| 宜兰县| 沙雅县| 聂荣县| 玉田县| 河东区| 简阳市| 汝南县| 锦州市| 田阳县| 深泽县| 南川市| 屏南县| 丹巴县| 辛集市| 洛阳市| 确山县| 鲁甸县| 积石山| 垫江县| 铜陵市| 高唐县| 年辖:市辖区|