golang利用socket封裝數(shù)據(jù)

什么是socket?

網(wǎng)絡(luò)上的兩個(gè)程序通過(guò)一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)連接的一端稱為一個(gè)socket。

我們知道兩個(gè)進(jìn)程如果需要進(jìn)行通訊最基本的一個(gè)前提能能夠唯一的標(biāo)示一個(gè)進(jìn)程,在本地進(jìn)程通訊中我們可以使用PID來(lái)唯一標(biāo)示一個(gè)進(jìn)程,但PID只在本地唯一,網(wǎng)絡(luò)中的兩個(gè)進(jìn)程PID沖突幾率很大,這時(shí)候我們需要另辟它徑了,我們知道IP層的ip地址可以唯一標(biāo)示主機(jī),而TCP層協(xié)議和端口號(hào)可以唯一標(biāo)示主機(jī)的一個(gè)進(jìn)程,這樣我們可以利用ip地址+協(xié)議+端口號(hào)唯一標(biāo)示網(wǎng)絡(luò)中的一個(gè)進(jìn)程,能夠唯一標(biāo)示網(wǎng)絡(luò)中的進(jìn)程后,它們就可以利用socket進(jìn)行通信。

建立網(wǎng)絡(luò)通信連接至少要一對(duì)端口號(hào)(socket)。socket本質(zhì)是編程接口(API),對(duì)TCP/IP的封裝,TCP/IP也要提供可供程序員做網(wǎng)絡(luò)開(kāi)發(fā)所用的接口,這就是Socket編程接口;HTTP是轎車,提供了封裝或者顯示數(shù)據(jù)的具體形式;Socket是發(fā)動(dòng)機(jī),提供了網(wǎng)絡(luò)通信的能力。

socket與http的區(qū)別

在網(wǎng)絡(luò)七層架構(gòu)中,http屬于應(yīng)用層協(xié)議,如圖:圖

而socket是位于應(yīng)用層和傳輸層之間的一個(gè)抽象層,是本來(lái)不存在與七層架構(gòu)中的:圖


socket通信流程是怎樣進(jìn)行的?

socket的通信流程:


流程解讀:

1. server端創(chuàng)建socket

2. server端綁定socket和端口號(hào)

3. server端監(jiān)聽(tīng)該端口號(hào)

4. server端啟動(dòng)accept()接收來(lái)自client端的連接請(qǐng)求,此時(shí)有連接進(jìn)入時(shí)會(huì)往后續(xù)執(zhí)行,沒(méi)有鏈接則會(huì)阻塞在此

5. client端創(chuàng)建socket

6. client端根據(jù)server端的ip和port連接server端(tcp的三次握手)

7. 如果第6步連接成功,在server端將會(huì)收到一個(gè)連接,假如這個(gè)連接名叫conn

8. client端向server端發(fā)送數(shù)據(jù)

9. server端從conn中讀取到client端發(fā)送過(guò)來(lái)的數(shù)據(jù)

10. 任何一端都可以主動(dòng)斷開(kāi)連接

socket中傳輸數(shù)據(jù)出現(xiàn)的問(wèn)題

粘包:當(dāng)server端用一個(gè)buff接收數(shù)據(jù),發(fā)現(xiàn)buff中除了一個(gè)完整的包之外還有其他的數(shù)據(jù)。

半包:server端未接收到一個(gè)完整的包,只接收到了一部分。

socket的封包與拆包

在實(shí)際開(kāi)發(fā)中往往會(huì)封裝自己的數(shù)據(jù),一般分為Head和Body,Head中除了封裝額外的信息之外,還會(huì)封裝Body的長(zhǎng)度在里面。這樣就形成了如下數(shù)據(jù):

Head{ ? ? ? ?//頭信息 ?(總共占用6個(gè)字節(jié))

One byte ? ? ?//第一個(gè)標(biāo)志 ?(1個(gè)字節(jié))

Two byte ? ? ?//第二個(gè)標(biāo)志 ?(1個(gè)字節(jié))

Length int32 ? ?//存放Body的數(shù)據(jù)長(zhǎng)度 (4個(gè)字節(jié))?

}

Body{ ? ? ? //消息體 (總共占用?+4個(gè)字節(jié))

Name []byte ? //名字 ? (占用?個(gè)字節(jié))

Age int32 ? //年齡 ? (占用4個(gè)字節(jié))

}

Data{

Head

Body

}

server端要對(duì)接收到的數(shù)據(jù)[]byte進(jìn)行拆包以防止粘包和半包情況的發(fā)生。

注:Data這個(gè)數(shù)據(jù)結(jié)構(gòu)在server端和client端都應(yīng)保持一致

封包過(guò)程:

將一個(gè)封裝好的Data數(shù)據(jù)轉(zhuǎn)化成[]byte,然后發(fā)送。

拆包過(guò)程:

1. 循環(huán)從conn中讀取數(shù)據(jù),每次循環(huán)都判斷接收到的數(shù)據(jù)是否>=6,若是,則表明接收完了Head,若否,繼續(xù)執(zhí)行下一次循環(huán),知道滿足條件

2. 接收完Head后,將前6個(gè)字節(jié)的數(shù)據(jù)解析到Data的Head中,對(duì)于從conn接收到的數(shù)據(jù)長(zhǎng)度減去6(得到的是接收到的Body數(shù)據(jù)的長(zhǎng)度),判斷這個(gè)結(jié)果是否>=Head中Length,若否,循環(huán)繼續(xù)接收數(shù)據(jù);若是,則解析出Length長(zhǎng)度的數(shù)據(jù)放入Data的Body,自此之后的數(shù)據(jù)又重新用一個(gè)Data來(lái)解析并存放

3. 關(guān)閉連接最優(yōu)方案應(yīng)該是server端判斷數(shù)據(jù)接收完時(shí)關(guān)閉連接

這樣拆包就解決了粘包和半包的問(wèn)題。

golang代碼實(shí)現(xiàn)

首先分為3個(gè)包,1:Data包;2:client包;3:server包

1:Data包的代碼

//socketDataprojectData.go

package Data

import(

? ? "bytes"

)

//Head總共占用6個(gè)字節(jié)

type Head struct{

? ? One byte//第一個(gè)數(shù)據(jù)(1個(gè)字節(jié))

? ? Two byte//第二個(gè)數(shù)據(jù)(1個(gè)字節(jié))

? ? Length int32//存放Body數(shù)據(jù)的長(zhǎng)度(4個(gè)字節(jié))

}

//Body總共占用?+4個(gè)字節(jié)

type Body struct{

? ? Name string//名字(?個(gè)字節(jié))

? ? Age ? ?int32//年齡(4個(gè)字節(jié))

}

//傳輸數(shù)據(jù)

type Data struct{

? ? Head

? ? Body

}

//初始化一個(gè)Data包

func NewData(one,twobyte,namestring,ageint32) *Data{

? ? data := &Data{}

? ? data.One = one

? ? data.Two = two

? ? data.Length = int32(len([]byte(name))+1)

? ? data.Name = name

? ? data.Age = age

? ? return data

}

//將Data轉(zhuǎn)化成[]byte(封包)

func (d*Data)ToByte() []byte{

? ? var bb bytes.Buffer

? ? bb.WriteByte(d.One)

? ? bb.WriteByte(d.Two)

? ? bb.WriteByte(byte(d.Length))//注意這里是將length轉(zhuǎn)化成byte,所以之后解析頭的時(shí)候長(zhǎng)度是3

? ? bb.Write([]byte(d.Name))

? ? bb.WriteByte(byte(d.Age))//這里是將age轉(zhuǎn)化成byte,所以之后解析body中的name時(shí)-1

? ? return ?bb.Bytes()

}

//將[]byte解析到d中(拆包)

func (d*Data)FromBytes (buff []byte){

? ? ?bb := bytes.NewBuffer(buff)

? ? var err error

? ? d.One,err = bb.ReadByte()

? ? checkErr(err)

? ? d.Two,err = bb.ReadByte()

? ? checkErr(err)

? ? length,_,err := bb.ReadRune()

? ? checkErr(err)

? ? d.Length = int32(length)

? ? if len(buff)-3 >= int(d.Length){ ?//為什么-3,查看ToByte()中的注釋

? ? ? ? ? ?//buff包含了一個(gè)完整的body體

? ? ? ? d.Name = string(bb.Next(int(d.Length-1))) ?//為什么-1,看ToByte()中的注釋

? ? ? ? length,_,err = bb.ReadRune()

? ? ? ? checkErr(err)

? ? ? ? d.Age=int32(length)

? ? }

}

func ?checkErr(errerror){

? ? if err!=nil {

? ? ? ? panic(err)

? ? }

}

2.client包的代碼

//socketDataClientprojectmain.go

packagemain

import(

? ? "fmt"

? ? "net"

? ? . "socketData"

)

func main(){

? ? service := ":7777"

? ? tcpAddr,err := net.ResolveTCPAddr("tcp4",service)

? ? checkErr(err)

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

? ? conn.SetKeepAlive(true)

? ? data := NewData('1','0',"初級(jí)賽亞人",117)

? ? data2 := NewData('2','3',"中級(jí)賽亞人",116)

? ? data3 := NewData('3','4',"超級(jí)賽亞人",115)

? ? //向server發(fā)送數(shù)據(jù)

? ? conn.Write(data.ToByte())

? ? conn.Write(data2.ToByte())

? ? conn.Write(data3.ToByte())

? ? //向server發(fā)送結(jié)束符

? ? conn.Write([]byte("#"))

? ? fmt.Println([]byte("#"))

? ? fmt.Println("發(fā)送消息為data:",data)

? ? fmt.Println("發(fā)送消息為data.toByte=",len(data.ToByte()),data.ToByte())

? ? fmt.Println("發(fā)送消息為data2:",data2)

? ? fmt.Println("發(fā)送消息為data2.toByte=",len(data2.ToByte()),data2.ToByte())

? ? fmt.Println("發(fā)送消息為data3:",data2)

? ? fmt.Println("發(fā)送消息為data3.toByte=",len(data2.ToByte()),data2.ToByte())

? ? var buf []byte=make([]byte,64,64)

? ? conn.CloseWrite()

? ? conn.Read(buf)

? ? fmt.Println("客戶端從服務(wù)端接收到的數(shù)據(jù):",buf)

? ? conn.Close()

}

func checkErr (err error){

? ? if err != nil {

? ? ? ? panic(err)

? ? }

}

3. server包的代碼

//socketDataServerprojectmain.go

packagemain

import(

? ? "bytes"

? ? "fmt"

? ? "net"

? ? ."socketData"

? ? "time"

)

func main(){

? ? tcpAddr,err := net.ResolveTCPAddr("tcp4",":7777")

? ? checkErr(err)

? ? listener,err := net.ListenTCP("tcp",tcpAddr)

? ? checkErr(err)

? ? for{

? ? ? ? conn,err := listener.Accept()

? ? ? ? checkErr(err)

? ? ? ? //開(kāi)啟一個(gè)goroutine處理這個(gè)連接

? ? ? ? go handleConn(conn) ?// 這是典型的BIO方式處理連接,之后會(huì)有一篇專門講解如何用go和chan來(lái)優(yōu)化接收方式

? ? }

}

func handleConn(conn net.Conn){

? ? defer ?conn.Close()

? ? var buf []byte = make([]byte,64,64)

? ? var newBuff []byte

? ? for{

? ? ? ? conn.Read(buf) ? //從conn中讀取字符

? ? ? ? buf = bytes.TrimRight(buf,"\x00") ?//去除buf尾部的所有0

? ? ? ? newBuff = append(newBuff,buf...)

? ? ? ? if len(newBuff)<3{

? ? ? ? ? ? //buf中未包含一個(gè)完整的Head信息

? ? ? ? ? ? fmt.Println(newBuff)

? ? ? ? ? ? continue

? ? ? ? }else{

? ? ? ? ? ? var data *Data = &Data{}

? ? ? ? ? ? //取出包頭

? ? ? ? ? ? data.FromBytes(newBuff)

? ? ? ? ? ? //判斷去掉包頭剩下的長(zhǎng)度是否可以取出body體

? ? ? ? ? ? for len(newBuff)-3 >= int(data.Length){

? ? ? ? ? ? ? ? data.FromBytes(newBuff[:(3+data.Length)])

? ? ? ? ? ? ? ? newBuff=newBuff[(3+data.Length):] ? //3+data.Length之前的數(shù)據(jù)已經(jīng)存放到data中

? ? ? ? ? ? ? ? fmt.Println("data=",data) ? //此時(shí)接收到data,可以選擇將data存入一個(gè)list中

? ? ? ? ? ? ? ? iflen(newBuff)>=3{

? ? ? ? ? ? ? ? ? ? ? ? ? //這里重新解析出包頭,會(huì)覆蓋之前的數(shù)據(jù)

? ? ? ? ? ? ? ? ? ? ? ? ? data.FromBytes(newBuff)

? ? ? ? ? ? ? ? ? ? ? ? ? continue

? ? ? ? ? ? ? ? ? ? ?}else{

? ? ? ? ? ? ? ? ? ? ? ? ? break

? ? ? ? ? ? ? ? ? ? ?}

? ? ? ? ? ? ?}

? ? ? ?}

//收到結(jié)束符,表示client端已發(fā)送完數(shù)據(jù)且不會(huì)再發(fā)送數(shù)據(jù)

? ? ? ? if bytes.Contains(newBuff,[]byte("#")){

? ? ? ? ? ? fmt.Println("get#")

? ? ? ? ? ? break

? ? ? ? }

? ? }

//向client寫入數(shù)據(jù)(隨便寫點(diǎn)什么)

? ? daytime:=time.Now().String()

? ? fmt.Println(daytime)

? ? conn.Write([]byte(daytime))

}

funccheckErr(errerror){

iferr!=nil{

panic(err)

}

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容