go tcp server client及粘包拆包處理

先上代碼
server.go

package main
import (
    "wingbow.com.cn/server/controller"
)

func main() {
    controller.ServerRun()
}

controller.go

package controller

import (
    "fmt"
    "io"
    "net"

    "wingbow.com.cn/server/network"
)

//ServerRun 服務(wù)
func ServerRun() {
    lister, err := net.Listen("tcp", "localhost:8888")
    fmt.Println("服務(wù)啟動(dòng)成功:localhost:8888")
    CheckErr(err)
    defer lister.Close()
    for {
        conn, err := lister.Accept()
        CheckErr(err)
        fmt.Println("用戶接入")
        client := network.NewTCPSocket(conn)
        go func() {
            defer client.Close()
            for {
                data, err := client.Read()
                if err == io.EOF {
                    fmt.Println("斷開鏈接")
                    return
                }
                if err != nil {
                    continue
                }
                switchController(data, client)
            }
        }()
    }
}

//CheckErr 異常檢查
func CheckErr(err error) {
    if err != nil {
        panic(err)
    }
}

func switchController(data []byte, c *network.TCPSocket) {
    fmt.Println("讀到的數(shù)據(jù): " + string(data))
    switch string(data) {
    case "ping":
        c.Write([]byte("pong"))
        fmt.Println("發(fā)出的數(shù)據(jù): pong")
        break
    }
}

network.go

package network

import (
    "bufio"
    "bytes"
    "encoding/binary"
    "net"
)

//TCPSocket 連接
type TCPSocket struct {
    tag  string
    conn net.Conn
    r    *bufio.Reader
}

//NewTCPSocket 創(chuàng)建一個(gè)TCP客戶端
func NewTCPSocket(conn net.Conn) *TCPSocket {
    return &TCPSocket{conn: conn, r: bufio.NewReader(conn)}
}

//LocalAddr 本地地址
func (c *TCPSocket) LocalAddr() net.Addr {
    return c.conn.LocalAddr()
}

//RemoteAddr 遠(yuǎn)程地址
func (c *TCPSocket) RemoteAddr() net.Addr {
    return c.conn.RemoteAddr()
}

//Close 關(guān)閉
func (c *TCPSocket) Close() error {
    return c.conn.Close()
}

//Write 寫消息
func (c *TCPSocket) Write(message []byte) (int, error) {
    // 讀取消息的長(zhǎng)度
    var length = int32(len(message))
    var pkg = new(bytes.Buffer)
    //寫入消息頭
    err := binary.Write(pkg, binary.BigEndian, length)
    if err != nil {
        return 0, err
    }
    //寫入消息體
    err = binary.Write(pkg, binary.BigEndian, message)
    if err != nil {
        return 0, err
    }
    nn, err := c.conn.Write(pkg.Bytes())
    if err != nil {
        return 0, err
    }
    return nn, nil
}

//Read 讀消息
func (c *TCPSocket) Read() ([]byte, error) {
    // Peek 返回緩存的一個(gè)切片,該切片引用緩存中前 n 個(gè)字節(jié)的數(shù)據(jù)嘉蕾,
    // 該操作不會(huì)將數(shù)據(jù)讀出双戳,只是引用你踩,引用的數(shù)據(jù)在下一次讀取操作之
    // 前是有效的。如果切片長(zhǎng)度小于 n,則返回一個(gè)錯(cuò)誤信息說明原因汁政。
    // 如果 n 大于緩存的總大小,則返回 ErrBufferFull缀旁。
    lengthByte, err := c.r.Peek(4)
    if err != nil {
        return nil, err
    }
    //創(chuàng)建 Buffer緩沖器
    lengthBuff := bytes.NewBuffer(lengthByte)
    var length int32
    // 通過Read接口可以將buf中得內(nèi)容填充到data參數(shù)表示的數(shù)據(jù)結(jié)構(gòu)中
    err = binary.Read(lengthBuff, binary.BigEndian, &length)
    if err != nil {
        return nil, err
    }
    // Buffered 返回緩存中未讀取的數(shù)據(jù)的長(zhǎng)度
    if int32(c.r.Buffered()) < length+4 {
        return nil, err
    }
    // 讀取消息真正的內(nèi)容
    pack := make([]byte, int(4+length))
    // Read 從 b 中讀出數(shù)據(jù)到 p 中记劈,返回讀出的字節(jié)數(shù)和遇到的錯(cuò)誤。
    // 如果緩存不為空并巍,則只能讀出緩存中的數(shù)據(jù)目木,不會(huì)從底層 io.Reader
    // 中提取數(shù)據(jù),如果緩存為空懊渡,則:
    // 1刽射、len(p) >= 緩存大小,則跳過緩存剃执,直接從底層 io.Reader 中讀
    // 出到 p 中誓禁。
    // 2、len(p) < 緩存大小忠蝗,則先將數(shù)據(jù)從底層 io.Reader 中讀取到緩存
    // 中现横,再?gòu)木彺孀x取到 p 中。
    _, err = c.r.Read(pack)
    if err != nil {
        return nil, err
    }
    return pack[4:], nil
}

客戶端
client.go

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "log"
    "net"
    "time"
)

func main() {
    conn, err := net.Dial("tcp", "localhost:8888")
    if err != nil {
        log.Println("dial error:", err)
        return
    }
    defer conn.Close()
    for {
        data, _ := Encode(time.Now().Format("2006-01-02 15:04:05"))
        time.Sleep(time.Second)
        _, err := conn.Write(data)
        fmt.Println(err)
    }
}
func Encode(message string) ([]byte, error) {
    // 讀取消息的長(zhǎng)度
    var length = int32(len(message))
    var pkg = new(bytes.Buffer)
    // 寫入消息頭
    err := binary.Write(pkg, binary.BigEndian, length)
    if err != nil {
        return nil, err
    }
    // 寫入消息實(shí)體
    err = binary.Write(pkg, binary.BigEndian, []byte(message))
    if err != nil {
        return nil, err
    }
    return pkg.Bytes(), nil
}
  上面處理粘包的方法是用了自定義的非定長(zhǎng)的數(shù)據(jù)包阁最,這也是一種常用的粘包處理方式戒祠。
   另外還有自定義分隔符、定長(zhǎng)分割等方法速种。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末姜盈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子配阵,更是在濱河造成了極大的恐慌馏颂,老刑警劉巖示血,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異救拉,居然都是意外死亡难审,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門亿絮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來告喊,“玉大人,你說我怎么就攤上這事派昧∏” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵蒂萎,是天一觀的道長(zhǎng)秆吵。 經(jīng)常有香客問我,道長(zhǎng)五慈,這世上最難降的妖魔是什么纳寂? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮豺撑,結(jié)果婚禮上烈疚,老公的妹妹穿的比我還像新娘。我一直安慰自己聪轿,他們只是感情好爷肝,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著陆错,像睡著了一般灯抛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上音瓷,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天对嚼,我揣著相機(jī)與錄音,去河邊找鬼绳慎。 笑死腔寡,一個(gè)胖子當(dāng)著我的面吹牛浩销,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼吨些,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼酸钦!你這毒婦竟也來了聋伦?” 一聲冷哼從身側(cè)響起绪爸,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎厕宗,沒想到半個(gè)月后画舌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堕担,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年曲聂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了霹购。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡朋腋,死狀恐怖厕鹃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情乍丈,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布把将,位于F島的核電站轻专,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏察蹲。R本人自食惡果不足惜请垛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望洽议。 院中可真熱鬧宗收,春花似錦、人聲如沸亚兄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)审胚。三九已至匈勋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間膳叨,已是汗流浹背洽洁。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留菲嘴,地道東北人饿自。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像龄坪,于是被迫代替她去往敵國(guó)和親昭雌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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