golang tcp 編程

打開鏈接

TCP Socket的連接的建立需要經(jīng)歷客戶端和服務(wù)端的三次握手的過程核偿。
連接建立過程中闻坚,服務(wù)端是一個標準的Listen + Accept的結(jié)構(gòu)(可參考上面的代碼),而在客戶端Go語言使用net.Dial或DialTimeout進行連接建立:

阻塞Dial:

conn, err := net.Dial("tcp", "google.com:80")

超時機制的Dial:

conn, err := net.DialTimeout("tcp", ":8080", 2 * time.Second)

客戶端連接的建立會遇到如下幾種情形

1 網(wǎng)絡(luò)不可達或?qū)Ψ椒?wù)未啟動

如果傳給Dial的Addr是可以立即判斷出網(wǎng)絡(luò)不可達急黎,或者Addr中端口對應(yīng)的服務(wù)沒有啟動疏橄,端口未被監(jiān)聽,Dial會幾乎立即返回錯誤奶浦,比如:

2 對方服務(wù)的listen backlog滿

還有一種場景就是對方服務(wù)器很忙兄墅,瞬間有大量client端連接嘗試向server建立,server端的listen backlog隊列滿澳叉,server accept不及時((即便不accept隙咸,那么在backlog數(shù)量范疇里面,connect都會是成功的成洗,因為new conn已經(jīng)加入到server side的listen queue中了五督,accept只是從queue中取出一個conn而已),這將導致client端Dial阻塞瓶殃。我們還是通過例子感受Dial的行為特點:

3 網(wǎng)絡(luò)延遲較大充包,Dial阻塞并超時

如果網(wǎng)絡(luò)延遲較大,TCP握手過程將更加艱難坎坷(各種丟包)碌燕,時間消耗的自然也會更長误证。
Dial這時會阻塞继薛,如果長時間依舊無法建立連接修壕,則Dial也會返回“ getsockopt: operation timed out”錯誤。

在連接建立階段遏考,多數(shù)情況下慈鸠,Dial是可以滿足需求的,即便阻塞一小會兒灌具。
但對于某些程序而言青团,需要有嚴格的連接時間限定,如果一定時間內(nèi)沒能成功建立連接咖楣,程序可能會需要執(zhí)行一段“異扯桨剩”處理邏輯,為此我們就需要DialTimeout了诱贿。
下面的例子將Dial的最長阻塞時間限制在2s內(nèi)娃肿,超出這個時長,Dial將返回timeout error.

服務(wù)端讀的行為特點

1 Socket中無數(shù)據(jù)

連接建立后珠十,如果對方未發(fā)送數(shù)據(jù)到socket料扰,接收方(Server)會阻塞在Read操作上。執(zhí)行該Read操作的goroutine也會被掛起焙蹭。runtime會監(jiān)視該socket晒杈,直到其有數(shù)據(jù)才會重新
調(diào)度該socket對應(yīng)的Goroutine完成read。

2 Socket中有部分數(shù)據(jù)

如果socket中有部分數(shù)據(jù)孔厉,且長度小于一次Read操作所期望讀出的數(shù)據(jù)長度拯钻,那么Read將會成功讀出這部分數(shù)據(jù)并返回帖努,而不是等待所有期望數(shù)據(jù)全部讀取后再返回。

3 Socket中有足夠數(shù)據(jù)

如果socket中有數(shù)據(jù)粪般,且長度大于等于一次Read操作所期望讀出的數(shù)據(jù)長度然磷,那么Read將會成功讀出這部分數(shù)據(jù)并返回。

4 Socket關(guān)閉

如果client端主動關(guān)閉了socket刊驴,那么Server的Read分為“有數(shù)據(jù)關(guān)閉”和“無數(shù)據(jù)關(guān)閉”姿搜。

“有數(shù)據(jù)關(guān)閉”是指在client關(guān)閉時,socket中還有server端未讀取的數(shù)據(jù)捆憎,Read返回“EOF error“舅柜。
“無數(shù)據(jù)關(guān)閉”情形下的結(jié)果,那就是Read直接返回EOF error躲惰。

5 讀取操作超時

有些場合對Read的阻塞時間有嚴格限制致份,在這種情況下,會反復執(zhí)行了多次础拨,沒能出現(xiàn)“讀出部分數(shù)據(jù)且返回超時錯誤”的情況氮块。

服務(wù)端寫的行為特點

1 成功寫

client端在Write時并未判斷Write的返回值。
所謂“成功寫”指的就是Write調(diào)用返回的n與預期要寫入的數(shù)據(jù)長度相等诡宗,且error = nil滔蝉。

2 寫阻塞

TCP連接通信兩端的OS都會為該連接保留數(shù)據(jù)緩沖,一端調(diào)用Write后塔沃,實際上數(shù)據(jù)是寫入到OS的協(xié)議棧的數(shù)據(jù)緩沖的蝠引。
TCP是全雙工通信,因此每個方向都有獨立的數(shù)據(jù)緩沖蛀柴。當
發(fā)送方將對方的接收緩沖區(qū)以及自身的發(fā)送緩沖區(qū)寫滿后螃概,Write就會阻塞。當接收方讀取的時候鸽疾,緩沖區(qū)騰出了空間吊洼,客戶端就又可以寫入了。

3 寫入部分數(shù)據(jù)

Write操作存在寫入部分數(shù)據(jù)的情況制肮,此時服務(wù)端關(guān)閉冒窍,但是寫入的緩沖區(qū)不會阻塞。而是后續(xù)又寫入部分數(shù)據(jù)后發(fā)生了阻塞弄企,程序需要對這部分寫入的部分字節(jié)做特定處理超燃。

4 寫入超時

如果非要給Write增加一個期限,那我們可以調(diào)用SetWriteDeadline方法拘领。
可以看到在寫入超時時意乓,依舊存在部分數(shù)據(jù)寫入的情況。

Socket屬性

原生Socket API提供了豐富的sockopt設(shè)置接口,但Golang有自己的網(wǎng)絡(luò)架構(gòu)模型届良,golang提供的socket必要的屬性設(shè)置笆凌。

SetKeepAlive 是否開啟長連接
SetKeepAlivePeriod 設(shè)置長連接的周期,超出會斷開
SetLinger 設(shè)定當連接中仍有數(shù)據(jù)等待發(fā)送或接受時的Close方法的行為士葫。
SetNoDelay (默認no delay) 設(shè)定操作系統(tǒng)是否應(yīng)該延遲數(shù)據(jù)包傳遞乞而,以便發(fā)送更少的數(shù)據(jù)包(Nagle's算法)。默認為真慢显,即數(shù)據(jù)應(yīng)該在Write方法后立刻發(fā)送爪模。
SetWriteBuffer 連接的系統(tǒng)發(fā)送緩沖
SetReadBuffer 連接的系統(tǒng)接收緩沖

關(guān)閉連接

由于socket是全雙工的,client和server端在己方已關(guān)閉的socket和對方關(guān)閉的socket上操作的結(jié)果有不同荚藻。

從client的結(jié)果來看屋灌,在己方已經(jīng)關(guān)閉的socket上再進行read和write操作,會得到”use of closed network connection” error应狱;

從server1的結(jié)果來看共郭,在對方關(guān)閉的socket上執(zhí)行read操作會得到EOF error,但write操作會成功疾呻,因為數(shù)據(jù)會成功寫入己方的內(nèi)核socket緩沖區(qū)中除嘹,
即便最終發(fā)不到對方socket緩沖區(qū)了,因為己方socket并未關(guān)閉岸蜗。因此當發(fā)現(xiàn)對方socket關(guān)閉后尉咕,己方應(yīng)該正確合理處理自己的socket,再繼續(xù)write已經(jīng)無任何意義了散吵。

參考 Go語言TCP Socket編程

Tcp編程常見問題及解決方法總結(jié)

問題1龙考、粘包問題

解決方法一:TCP提供了強制數(shù)據(jù)立即傳送的操作指令push,TCP軟件收到該操作指令后矾睦,就立即將本段數(shù)據(jù)發(fā)送出去,而不必等待發(fā)送緩沖區(qū)滿炎功;

解決方法二:發(fā)送固定長度的消息

解決方法三:把消息的尺寸與消息一塊發(fā)送

解決方法四:雙方約定每次傳送的大小

解決方法五:雙方約定使用特殊標記來區(qū)分消息間隔

解決方法六:標準協(xié)議按協(xié)議規(guī)則處理枚冗,如Sip協(xié)議

問題2、字符串編碼問題

將中文字符串用utf8編碼格式轉(zhuǎn)換為字節(jié)數(shù)組發(fā)送時蛇损,一個中文字符可能會占用2~4個字節(jié)(假設(shè)為3個字節(jié))赁温,這3個字節(jié)可能分3次接收,接收端每次接收完后用utf8編碼格式轉(zhuǎn)換為字符串淤齐,就會出現(xiàn)亂碼股囊,并導致接收長度計算錯誤的情況。

解決方法一:以字節(jié)數(shù)做為消息長度的計算單位更啄,而不是字符個數(shù)稚疹。

解決方法二:發(fā)送方和接收方都采用unicode編碼格式。

問題3祭务、長連接的蹦诠罚活問題

標準TCP層協(xié)議里把對方超時設(shè)為2小時怪嫌,若服務(wù)器端超過了2小時還沒收到客戶的信息,它就發(fā)送探測報文段柳沙,若發(fā)送了10個探測報文段(每一個相隔75S)還沒有收到響應(yīng)岩灭,就假定客戶出了故障,并終止這個連接赂鲤。因此應(yīng)對tcp長連接進行痹刖叮活。

以下是異步通信時會遇到的問題:

問題4数初、緩沖區(qū)臟數(shù)據(jù)問題

同步發(fā)送的拷貝熄云,是直接拷貝數(shù)據(jù)到基礎(chǔ)系統(tǒng)緩沖區(qū),拷貝完成后返回妙真;

異步發(fā)送消息的拷貝缴允,是將Socket自帶的Buffer空間內(nèi)的所有數(shù)據(jù),拷貝到基礎(chǔ)系統(tǒng)發(fā)送緩沖區(qū)珍德,并立即返回练般;

因此異步發(fā)送時緩沖區(qū)設(shè)置不好會導致接收到臟數(shù)據(jù)的問題,如下所示:

第一次發(fā)送數(shù)據(jù):1234567890

第一次接受數(shù)據(jù):1234567890

第二次發(fā)送數(shù)據(jù):abc

第二次接受數(shù)據(jù):abc4567890

請參考:http://www.cnblogs.com/tianzhiliang/archive/2010/09/08/1821623.html

解決方法一:將緩沖區(qū)的大小設(shè)置為實際發(fā)送數(shù)據(jù)的大小锈候。

問題5薄料、內(nèi)存碎片問題

頻繁的申請緩沖區(qū)會導致內(nèi)存碎片的問題。

解決方法一:使用對象池和內(nèi)存池泵琳。

請參考MSDN:http://msdn.microsoft.com/zh-cn/library/bb517542(v=vs.100).aspx

http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.socketasynceventargs(v=vs.100).aspx

問題6摄职、亂序問題

多個線程使用異步通信方式向同一個接收端(socket)同時發(fā)送數(shù)據(jù),會導致接收端接收的數(shù)據(jù)混亂获列。如下所示:

線程1第一次發(fā)送:123456789谷市,假設(shè)未發(fā)送完,只發(fā)送了123

線程2第一次發(fā)送:abcdefgh击孩,假設(shè)未發(fā)送完迫悠,只發(fā)送了abc

線程1第二次發(fā)送:456789,發(fā)送完成

線程2第二次發(fā)送:defgh巩梢,發(fā)送完成

接收端最終接收的數(shù)據(jù)為:123abc456789defgh创泄。

解決方法一:一個連接的發(fā)送端線程排隊發(fā)送數(shù)據(jù)。

代碼示例

服務(wù)端:

package main


import (
    "fmt"
    "net"
    "strings"
)

// 讀取數(shù)據(jù)
func handleConnection(conn net.Conn) {

    for {
        buf := make([]byte, 1024)
        if _,err := conn.Read(buf);err == nil {
            result := strings.Replace(string(buf),"\n","",1)
            fmt.Println(result)
        }else{
            fmt.Println(err)
        }

    }
}

func main() {

    /*
    Listen: 返回在一個本地網(wǎng)絡(luò)地址laddr上監(jiān)聽的Listener括蝠。網(wǎng)絡(luò)類型參數(shù)net必須是面向流的網(wǎng)絡(luò): "tcp"鞠抑、"tcp4"、"tcp6"忌警、"unix"或"unixpacket"搁拙。
    */
    listener, err := net.Listen("tcp", "localhost:9999")
    if err != nil {
        fmt.Println("listen error:", err)
        return
    }

    //todo 限速算法
    fmt.Println("server listen success")
    for {
        //等待客戶端接入
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("accept error:", err)
            break
        }
        // 使用協(xié)程
        go handleConnection(conn)
    }
}

客戶端:

package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
    "os"
    "strings"
    "time"
)

func main() {

    //阻塞Dial
    /*
    Dial:
        在網(wǎng)絡(luò)network上連接地址address,并返回一個Conn接口。
        可用的網(wǎng)絡(luò)類型有:"tcp"感混、"tcp4"端幼、"tcp6"、"udp"弧满、"udp4"婆跑、"udp6"、"ip"庭呜、"ip4"滑进、"ip6"、"unix"募谎、"unixgram"扶关、"unixpacket"
        對TCP和UDP網(wǎng)絡(luò),地址格式是host:port或[host]:port
    */
    //conn, err := net.Dial("tcp", "localhost:7777")
    //超時
    conn, err := net.DialTimeout("tcp", "localhost:9999",time.Second*2)
    if err != nil {
        log.Println("dial error:", err)
        return
    }
    fmt.Println("client dial success")

    inputReader := bufio.NewReader(os.Stdin)
    for {

        fmt.Println("Please enter a message? 'quit' exit")
        //讀取消息
        input, _ := inputReader.ReadString('\n')
        msg := strings.Trim(input, "\r\n")
        //quit 退出
        if msg == "quit" {
            fmt.Println("quit")
            conn.Write([]byte("client quit "))
            return
        }
        _, err = conn.Write([]byte( msg))
    }
}


參考 Tcp編程常見問題及解決方法總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末数冬,一起剝皮案震驚了整個濱河市节槐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拐纱,老刑警劉巖铜异,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異秸架,居然都是意外死亡揍庄,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門东抹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚂子,“玉大人,你說我怎么就攤上這事缭黔∈尘ィ” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵试浙,是天一觀的道長董瞻。 經(jīng)常有香客問我,道長田巴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任挟秤,我火速辦了婚禮壹哺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘艘刚。我一直安慰自己管宵,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著箩朴,像睡著了一般岗喉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上炸庞,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天钱床,我揣著相機與錄音,去河邊找鬼埠居。 笑死查牌,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的滥壕。 我是一名探鬼主播纸颜,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了沪停?” 一聲冷哼從身側(cè)響起渴庆,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涮较,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胡岔,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡法希,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了靶瘸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苫亦。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖怨咪,靈堂內(nèi)的尸體忽然破棺而出屋剑,到底是詐尸還是另有隱情,我是刑警寧澤诗眨,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布唉匾,位于F島的核電站,受9級特大地震影響匠楚,放射性物質(zhì)發(fā)生泄漏巍膘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一芋簿、第九天 我趴在偏房一處隱蔽的房頂上張望峡懈。 院中可真熱鬧,春花似錦与斤、人聲如沸肪康。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽磷支。三九已至谒撼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間雾狈,已是汗流浹背廓潜。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留箍邮,地道東北人茉帅。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像锭弊,于是被迫代替她去往敵國和親堪澎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容