53. Socket服務(wù)三次握手的示例

什么是三次握手呢葵硕?這是服務(wù)器和客戶端之間溝通的過程。
首先贯吓,客戶端對服務(wù)器發(fā)送了條信息懈凹。
然后,服務(wù)端對客戶端說悄谐,我收到了介评。
最后,客戶端對服務(wù)端說爬舰,好的们陆,我知道你收到了。
怎么樣情屹?這就是三次握手棒掠。哈哈!

服務(wù)端屁商,我們先聲明本地要監(jiān)聽的地址和端口烟很。

netListent, err := net.Listen("tcp", "localhost:7373")

有開始就有結(jié)束颈墅,當主函數(shù)運行結(jié)束時,需要釋放資源雾袱。

defer netListent.Close()

在服務(wù)端做標記恤筛,表明現(xiàn)在開始等待客戶端訪問了。

Log(time.Now().Format("2006-01-02 15:04:05.0000000"),"Waiting for client ...")

監(jiān)聽是一個不停息的循環(huán)運行芹橡。所以使用 for{}

    for{
        conn, err := netListent.Accept()
        if err != nil{
            continue
        }
        //標記發(fā)生了一次連接
        Log(conn.RemoteAddr().String(), "tcp connect success")
        go handleConnection(conn)
    }

每個從客戶端請求的連接毒坛,都會在服務(wù)端生成一個 goroutine 協(xié)程去處理。
go handleConnection(conn)
服務(wù)端處理的過程是這樣的林说。建立一個緩存煎殷,接收客戶端信息。如果接收正確腿箩,就反饋給客戶端說“我收到了”豪直。如果客戶端沒有反應(yīng),就證明客戶端“沒有收到回執(zhí)”珠移。如果客戶端有反饋弓乙,就說明“客戶端收到回執(zhí)”。
最后钧惧,溝通結(jié)束暇韧,釋放連接資源。defer conn.Close()

//客戶端連接處理
func handleConnection(conn net.Conn) {
    buffer := make([]byte, 1024)
    for{
        //接收客戶端信息
        msg, err := conn.Read(buffer)
        if err != nil{
            //接收錯誤浓瞪,日志打印
            Log(conn.RemoteAddr().String(), "connection error: ", err)
            return
        }
        //接收正確懈玻,日志打印
        Log(conn.RemoteAddr().String(), "receive data: ", string(buffer[:msg]))
        //反饋給客戶端
        bufferReturn := "我收到了"
        msgR, err2 := conn.Write([]byte(bufferReturn))
        //確認客戶端未收到回執(zhí)
        if err2 != nil{
            Log(conn.RemoteAddr().String(), "沒有收到回執(zhí)")
            return
        }
        //確認客戶端收到回執(zhí)
        msg, err = conn.Read(buffer)
        Log(conn.RemoteAddr().String(), "客戶端收到回執(zhí)", string(buffer[:msg]), "客戶收到了", msgR, ";實際發(fā)送了", len(bufferReturn))
    }
    defer conn.Close()
}

其中應(yīng)用到的日志記錄和錯誤處理乾颁。

//日志記錄
func Log(i ...interface{}) {
    fmt.Println(i...)
    return
}
//錯誤處理
func CheckErr(err error) {
    if err != nil{
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

完整的服務(wù)端代碼示例

/**
* MySocketProtocolServer
* @Author:  Jian Junbo
* @Email:   junbojian@qq.com
* @Create:  2017/9/15 9:07
* Copyright (c) 2017 Jian Junbo All rights reserved.
*
* Description:  服務(wù)端 Socket 信息接收
*/
package main

import (
    "fmt"
    "net"
    "os"
    "time"
)

func main() {
    //監(jiān)聽服務(wù)聲明實例化
    netListent, err := net.Listen("tcp", "localhost:7373")
    CheckErr(err)
    defer netListent.Close()
    //標記開始服務(wù)
    Log(time.Now().Format("2006-01-02 15:04:05.0000000"),"Waiting for client ...")
    //服務(wù)監(jiān)聽
    for{
        conn, err := netListent.Accept()
        if err != nil{
            continue
        }
        //標記發(fā)生了一次連接
        Log(conn.RemoteAddr().String(), "tcp connect success")
        go handleConnection(conn)
    }
}
//客戶端連接處理
func handleConnection(conn net.Conn) {
    buffer := make([]byte, 1024)
    for{
        //接收客戶端信息
        msg, err := conn.Read(buffer)
        if err != nil{
            //接收錯誤酪刀,日志打印
            Log(conn.RemoteAddr().String(), "connection error: ", err)
            return
        }
        //接收正確,日志打印
        Log(conn.RemoteAddr().String(), "receive data: ", string(buffer[:msg]))
        //反饋給客戶端
        bufferReturn := "我收到了"
        msgR, err2 := conn.Write([]byte(bufferReturn))
        //確認客戶端未收到回執(zhí)
        if err2 != nil{
            Log(conn.RemoteAddr().String(), "沒有收到回執(zhí)")
            return
        }
        //確認客戶端收到回執(zhí)
        msg, err = conn.Read(buffer)
        Log(conn.RemoteAddr().String(), "客戶端收到回執(zhí)", string(buffer[:msg]), "客戶收到了", msgR, "钮孵;實際發(fā)送了", len(bufferReturn))
    }
    defer conn.Close()
}
//日志記錄
func Log(i ...interface{}) {
    fmt.Println(i...)
    return
}
//錯誤處理
func CheckErr(err error) {
    if err != nil{
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

客戶端的任務(wù)是發(fā)送信息給服務(wù)端骂倘,等待服務(wù)端的反饋。收到服務(wù)端的反饋后巴席,再通知服務(wù)端說“ok”历涝,表示已經(jīng)知道服務(wù)端收到信息了。
先要確定獲取服務(wù)端的地址和通訊端口漾唉。

server := "localhost:7373"
tcpAddr, err := net.ResolveTCPAddr("tcp4", server)

建立服務(wù)器連接

conn, err := net.DialTCP("tcp", nil, tcpAddr)

連接成功后荧库,日志打印表示一下

Log("connection success")

發(fā)送信息到服務(wù)器,是一個函數(shù) sender(conn) 最終完成的赵刑。

func sender(conn *net.TCPConn) {
    myage := int(time.Now().Year())
    myage -= 1973
    words := "{\"ID\":\"i\",\"Name\":\"Joel\",\"Age\":\""+strconv.Itoa(myage)+"\",\"Programming Language\":\"go\"}"
    //msgBack, err := conn.Write(protocol.Enpack([]byte(words)))
    msgBack, err := conn.Write([]byte(words))
    if err != nil{
        Log(conn.RemoteAddr().String(), "Fatal error: ", err)
        os.Exit(1)
    }
    buffer := make([]byte, 1024)
    msg, err := conn.Read(buffer)
    Log(conn.RemoteAddr().String(), "服務(wù)器反饋: ", string(buffer[:msg]), msgBack,"分衫;實際發(fā)送了", len(words))
    conn.Write([]byte("ok"))
}

這個函數(shù)中,我們準備好了要發(fā)送的信息 words般此。
寫入信息到連接中

msgBack, err := conn.Write([]byte(words))

接收服務(wù)器對信息的反饋

msg, err := conn.Read(buffer)

在告訴服務(wù)器蚪战,它的反饋收到了牵现。

conn.Write([]byte("ok"))

完整的客戶端代碼示例

/**
* MySocketProtocolClient
* @Author:  Jian Junbo
* @Email:   junbojian@qq.com
* @Create:  2017/9/15 10:44
* Copyright (c) 2017 Jian Junbo All rights reserved.
*
* Description:  客戶端 Socket
*/
package main

import (
    "net"
    "fmt"
    "os"
    "time"
)

func main() {
    //獲取服務(wù)器地址和端口
    server := "localhost:7373"
    tcpAddr, err := net.ResolveTCPAddr("tcp4", server)
    if err != nil{
        Log(os.Stderr, "Fatal error: ", err)
        os.Exit(1)
    }
    //建立服務(wù)器連接
    conn, err := net.DialTCP("tcp", nil, tcpAddr)
    if err != nil{
        Log(conn.RemoteAddr().String(), os.Stderr, "Fatal error: ", err)
        os.Exit(1)
    }
    Log("connection success")
    sender(conn)
    fmt.Println("send over")
}

func sender(conn *net.TCPConn) {
    myage := int(time.Now().Year())
    myage -= 1973
    words := "{\"ID\":\"i\",\"Name\":\"Joel\",\"Age\":\""+strconv.Itoa(myage)+"\",\"Programming Language\":\"go\"}"
    msgBack, err := conn.Write([]byte(words))
    if err != nil{
        Log(conn.RemoteAddr().String(), "Fatal error: ", err)
        os.Exit(1)
    }
    buffer := make([]byte, 1024)
    msg, err := conn.Read(buffer)
    Log(conn.RemoteAddr().String(), "服務(wù)器反饋: ", string(buffer[:msg]), msgBack,";實際發(fā)送了", len(words))
    conn.Write([]byte("ok"))
}
//日志打印
func Log(v ...interface{}) {
    fmt.Println(v...)
}

服務(wù)端運行結(jié)果

127.0.0.1:50756 tcp connect success
127.0.0.1:50756 receive data:  {"ID":"i","Name":"Joel","Age":"44","Programming Language":"go"}
127.0.0.1:50756 客戶端收到回執(zhí) ok 客戶收到了 12 邀桑;實際發(fā)送了 12
127.0.0.1:50756 connection error:  read tcp 127.0.0.1:7373->127.0.0.1:50756: wsarecv: An existing connection was forcibly closed by the remote host.

客戶端運行結(jié)果

connection success
127.0.0.1:7373 服務(wù)器反饋:  我收到了 63 瞎疼;實際發(fā)送了 63
send over

現(xiàn)在通過網(wǎng)絡(luò)傳遞的信息,沒有特別的加工壁畸,沒有自定義的通訊協(xié)議贼急。如果要增加這個,建立自己的通訊協(xié)議后(封裝 Enpack捏萍、解析 Depack)太抓,在客戶端進行封裝,在服務(wù)端進行解析令杈。
在當前的代碼中做修改走敌。
服務(wù)端

Log(conn.RemoteAddr().String(), "receive data: ", string(buffer[:msg]))

改成 ↓

tmpBuffer := make([]byte, 1024)
tmpBuffer = protocol.Depack(append(tmpBuffer,buffer[:msg]...))
Log(conn.RemoteAddr().String(), "receive data: ", string(tmpBuffer))

客戶端

msgBack, err := conn.Write([]byte(words))
...
Log(conn.RemoteAddr().String(), "服務(wù)器反饋: ", string(buffer[:msg]), msgBack,";實際發(fā)送了", len(words))

改成 ↓

msgBack, err := conn.Write(protocol.Enpack([]byte(words)))
...
Log(conn.RemoteAddr().String(), "服務(wù)器反饋: ", string(buffer[:msg]), msgBack,"这揣;實際發(fā)送了", len(protocol.Enpack([]byte(words))))

由于添加了協(xié)議后,發(fā)送內(nèi)容增加了頭部的一些信息影斑,所以實際發(fā)送信息的長度也變化了给赞。
協(xié)議protocol的代碼示例

/**
* protocol
* @Author:  Jian Junbo
* @Email:   junbojian@qq.com
* @Create:  2017/9/14 11:49
* Copyright (c) 2017 Jian Junbo All rights reserved.
*
* Description:  通訊協(xié)議處理
*/
package protocol

import (
    "bytes"
    "encoding/binary"
)

const (
    ConstHeader = "Headers"
    ConstHeaderLength = 7
    ConstMLength = 4
)

//封包
func Enpack(message []byte) []byte {
    return append(append([]byte(ConstHeader), IntToBytes(len(message))...), message...)
}

//解包
func Depack(buffer []byte) []byte {
    length := len(buffer)

    var i int
    data := make([]byte, 32)
    for i = 0; i < length; i++ {

        if length < i + ConstHeaderLength + ConstMLength{
            break
        }
        if string(buffer[i:i+ConstHeaderLength]) == ConstHeader {
            messageLength := ByteToInt(buffer[i+ConstHeaderLength : i+ConstHeaderLength+ConstMLength])
            if length < i+ConstHeaderLength+ConstMLength+messageLength {
                break
            }
            data = buffer[i+ConstHeaderLength+ConstMLength : i+ConstHeaderLength+ConstMLength+messageLength]
        }
    }

    if i == length {
        return make([]byte, 0)
    }

    return data
}

//字節(jié)轉(zhuǎn)換成整形
func ByteToInt(n []byte) int {
    bytesbuffer := bytes.NewBuffer(n)
    var x int32
    binary.Read(bytesbuffer, binary.BigEndian, &x)

    return int(x)
}

//整數(shù)轉(zhuǎn)換成字節(jié)
func IntToBytes(n int) []byte {
    x := int32(n)
    bytesBuffer := bytes.NewBuffer([]byte{})
    binary.Write(bytesBuffer, binary.BigEndian, x)
    return bytesBuffer.Bytes()
}

增加了解析功能的服務(wù)端代碼示例

/**
* MySocketProtocolServer
* @Author:  Jian Junbo
* @Email:   junbojian@qq.com
* @Create:  2017/9/15 9:07
* Copyright (c) 2017 Jian Junbo All rights reserved.
*
* Description:  服務(wù)端 Socket 信息接收
*/
package main

import (
    "fmt"
    "net"
    "os"
    "time"
    "protocol"
)

func main() {
    //監(jiān)聽服務(wù)聲明實例化
    netListent, err := net.Listen("tcp", "localhost:7373")
    CheckErr(err)
    defer netListent.Close()
    //標記開始服務(wù)
    Log(time.Now().Format("2006-01-02 15:04:05.0000000"),"Waiting for client ...")
    //服務(wù)監(jiān)聽
    for{
        conn, err := netListent.Accept()
        if err != nil{
            continue
        }
        //標記發(fā)生了一次連接
        Log(conn.RemoteAddr().String(), "tcp connect success")
        go handleConnection(conn)
    }
}
//客戶端連接處理
func handleConnection(conn net.Conn) {
    buffer := make([]byte, 2048)
    for{
        //接收客戶端信息
        msg, err := conn.Read(buffer)
        if err != nil{
            //接收錯誤,日志打印
            Log(conn.RemoteAddr().String(), "connection error: ", err)
            return
        }
        //接收正確矫户,日志打印
        tmpBuffer := make([]byte, 1024)
        tmpBuffer = protocol.Depack(append(tmpBuffer,buffer[:msg]...))
        Log(conn.RemoteAddr().String(), "receive data: ", string(tmpBuffer))
        //反饋給客戶端
        bufferReturn := "我收到了"
        msgR, err2 := conn.Write([]byte(bufferReturn))
        //確認客戶端未收到回執(zhí)
        if err2 != nil{
            Log(conn.RemoteAddr().String(), "沒有收到回執(zhí)")
            return
        }
        //確認客戶端收到回執(zhí)
        msg, err = conn.Read(buffer)
        Log(conn.RemoteAddr().String(), "客戶端收到回執(zhí)", string(buffer[:msg]), "客戶收到了", msgR, "片迅;實際發(fā)送了", len(bufferReturn))
    }
    defer conn.Close()
}
//日志記錄
func Log(i ...interface{}) {
    fmt.Println(i...)
    return
}
//錯誤處理
func CheckErr(err error) {
    if err != nil{
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

增加了封裝的客戶端代碼示例

/**
* MySocketProtocolClient
* @Author:  Jian Junbo
* @Email:   junbojian@qq.com
* @Create:  2017/9/15 10:44
* Copyright (c) 2017 Jian Junbo All rights reserved.
*
* Description:  客戶端 Socket
*/
package main

import (
    "net"
    "fmt"
    "os"
    "time"
    "strconv"
    "protocol"
)

func main() {
    //獲取服務(wù)器地址和端口
    server := "localhost:7373"
    tcpAddr, err := net.ResolveTCPAddr("tcp4", server)
    if err != nil{
        Log(os.Stderr, "Fatal error: ", err)
        os.Exit(1)
    }
    //建立服務(wù)器連接
    conn, err := net.DialTCP("tcp", nil, tcpAddr)
    if err != nil{
        Log(conn.RemoteAddr().String(), os.Stderr, "Fatal error: ", err)
        os.Exit(1)
    }
    Log("connection success")
    sender(conn)
    fmt.Println("send over")
}

func sender(conn *net.TCPConn) {
    myage := int(time.Now().Year())
    myage -= 1973
    words := "{\"ID\":\"i\",\"Name\":\"Joel\",\"Age\":\""+strconv.Itoa(myage)+"\",\"Programming Language\":\"go\"}"
    msgBack, err := conn.Write(protocol.Enpack([]byte(words)))
    //msgBack, err := conn.Write([]byte(words))
    if err != nil{
        Log(conn.RemoteAddr().String(), "Fatal error: ", err)
        os.Exit(1)
    }
    buffer := make([]byte, 1024)
    msg, err := conn.Read(buffer)
    Log(conn.RemoteAddr().String(), "服務(wù)器反饋: ", string(buffer[:msg]), msgBack,";實際發(fā)送了", len(protocol.Enpack([]byte(words))))
    conn.Write([]byte("ok"))
}
//日志打印
func Log(v ...interface{}) {
    fmt.Println(v...)
}

運行效果上只是客戶端這里顯示增加了幾個字節(jié)

connection success
127.0.0.1:7373 服務(wù)器反饋:  我收到了 74 皆辽;實際發(fā)送了 74
send over
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末柑蛇,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子驱闷,更是在濱河造成了極大的恐慌耻台,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件空另,死亡現(xiàn)場離奇詭異盆耽,居然都是意外死亡,警方通過查閱死者的電腦和手機扼菠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進店門摄杂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人循榆,你說我怎么就攤上這事析恢。” “怎么了秧饮?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵映挂,是天一觀的道長泽篮。 經(jīng)常有香客問我,道長袖肥,這世上最難降的妖魔是什么咪辱? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮椎组,結(jié)果婚禮上油狂,老公的妹妹穿的比我還像新娘。我一直安慰自己寸癌,他們只是感情好专筷,可當我...
    茶點故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蒸苇,像睡著了一般磷蛹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上溪烤,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天味咳,我揣著相機與錄音,去河邊找鬼檬嘀。 笑死槽驶,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的鸳兽。 我是一名探鬼主播掂铐,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼揍异!你這毒婦竟也來了全陨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤衷掷,失蹤者是張志新(化名)和其女友劉穎辱姨,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體戚嗅,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡炮叶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了渡处。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片镜悉。...
    茶點故事閱讀 38,683評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖医瘫,靈堂內(nèi)的尸體忽然破棺而出侣肄,到底是詐尸還是另有隱情,我是刑警寧澤醇份,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布稼锅,位于F島的核電站吼具,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏矩距。R本人自食惡果不足惜拗盒,卻給世界環(huán)境...
    茶點故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锥债。 院中可真熱鬧陡蝇,春花似錦、人聲如沸哮肚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽允趟。三九已至恼策,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間潮剪,已是汗流浹背涣楷。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抗碰,地道東北人狮斗。 一個月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像改含,于是被迫代替她去往敵國和親情龄。 傳聞我的和親對象是個殘疾皇子迄汛,可洞房花燭夜當晚...
    茶點故事閱讀 43,566評論 2 349

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

  • 說明 本文 翻譯自 realpython 網(wǎng)站上的文章教程 Socket Programming in Pytho...
    keelii閱讀 2,109評論 0 16
  • 網(wǎng)絡(luò)編程 一.楔子 你現(xiàn)在已經(jīng)學會了寫python代碼鞍爱,假如你寫了兩個python文件a.py和b.py鹃觉,分別去運...
    go以恒閱讀 1,999評論 0 6
  • 點擊查看原文 Web SDK 開發(fā)手冊 SDK 概述 網(wǎng)易云信 SDK 為 Web 應(yīng)用提供一個完善的 IM 系統(tǒng)...
    layjoy閱讀 13,702評論 0 15
  • 實時消息協(xié)議---流的分塊 版權(quán)聲明: 版權(quán)(c)2009 Adobe系統(tǒng)有限公司。全權(quán)所有睹逃。 摘要: 本備忘錄描...
    一個人zy閱讀 1,891評論 0 9
  • 今天在瀏覽簡友網(wǎng)頁的時候盗扇,發(fā)現(xiàn)自己一個問題,那就是不管出于什么原因沉填,總會錯過高分簡友疗隶,自己會不自覺地做到:不關(guān)注動...
    梅花開在春天閱讀 677評論 6 15