一篇文章帶你了解Go語(yǔ)言基礎(chǔ)之網(wǎng)絡(luò)編程
前言
Hi,大家好呀,我是碼農(nóng),星期八,我們身處21世紀(jì),我們的世界已經(jīng)在不知不覺(jué)中,就像很多網(wǎng)一樣在互聯(lián)互通。
互聯(lián)網(wǎng)是一個(gè)統(tǒng)稱(chēng),目前比較常用的有TCP,UDP協(xié)議。
當(dāng)然,還有很多其他的協(xié)議,但是本次主要講最常用的TCP和UDP協(xié)議。
socker編程
我們所學(xué)的TCP和UDP,統(tǒng)稱(chēng)為Socker編程,也叫做套接字編程。
多臺(tái)機(jī)器要實(shí)現(xiàn)互相通訊,其實(shí)是一個(gè)非常復(fù)雜的過(guò)程,底層從鋪設(shè)網(wǎng)線,網(wǎng)線接口,交換機(jī),路由器,在到規(guī)定各種協(xié)議。
再到應(yīng)用層QQ,微信等軟件。
如果沒(méi)有一套標(biāo)準(zhǔn),每次使用都要自己去實(shí)現(xiàn),可能每個(gè)程序員都不是掉頭發(fā)那么簡(jiǎn)單了!
有了Socker之后,Socker會(huì)在應(yīng)用層之前,將各種繁瑣的的底層操作隱藏,我們可能只需要Socker.TCP就實(shí)現(xiàn)了TCP協(xié)議的通訊。
Go語(yǔ)言TCP
TCP屬于穩(wěn)定的,可靠的長(zhǎng)連接,
既然要涉及通訊,必然有兩個(gè)終端,最起碼一個(gè)是服務(wù)端,一個(gè)是客戶(hù)端,就像我們的淘寶,我們每次打開(kāi)淘寶,都要去鏈接它,當(dāng)然,淘寶可不直接是TCP。
服務(wù)端
在Go中實(shí)現(xiàn)服務(wù)端,并且在服務(wù)端并發(fā)很簡(jiǎn)單,只需要將每個(gè)連接讓一個(gè)協(xié)程處理即可!
代碼
- package main
- import (
- "bufio"
- "fmt"
- "net"
- )
- func process(conn net.Conn) {
- defer conn.Close()
- for {
- reader := bufio.NewReader(conn)
- buf := make([]byte, 128)
- n, err := reader.Read(buf)
- if err != nil {
- fmt.Println("數(shù)據(jù)讀取失敗", err)
- return
- }
- recvStr := string(buf[:n])
- fmt.Println("客戶(hù)端發(fā)送過(guò)來(lái)的值:", recvStr)
- }
- }
- func main() {
- lister, err := net.Listen("tcp", "0.0.0.0:8008")
- if err != nil {
- fmt.Println("連接失敗", err)
- }
- for {
- fmt.Println("等待建立連接,此時(shí)會(huì)阻塞住")
- conn, err := lister.Accept() //等待建立連接
- fmt.Println("連接建立成功,繼續(xù)")
- if err != nil {
- fmt.Println("建立連接失敗", err)
- //繼續(xù)監(jiān)聽(tīng)下次鏈接
- continue
- }
- go process(conn)
- }
- }
客戶(hù)端
客戶(hù)端就很簡(jiǎn)單了,相對(duì)來(lái)說(shuō)是不需要并發(fā)的,只需要連接就行。
代碼
- package main
- import (
- "bufio"
- "fmt"
- "net"
- "os"
- )
- //客戶(hù)端
- func main() {
- conn, err := net.Dial("tcp", "192.168.10.148:8008")
- if err != nil {
- fmt.Println("連接服務(wù)器失敗",err)
- }
- defer conn.Close()
- inputReader:=bufio.NewReader(os.Stdin)
- for{
- fmt.Println(":")
- input,_:=inputReader.ReadString('\n')
- _, err = conn.Write([]byte(input))
- if err != nil {
- fmt.Println("發(fā)送成功")
- }
- }
- }
執(zhí)行結(jié)果
就這樣,我們實(shí)現(xiàn)了服務(wù)端并發(fā)的處理所有客戶(hù)端的請(qǐng)求。

粘包
我們先看一下什么是粘包。
服務(wù)端
- package main
- import (
- "bufio"
- "fmt"
- "io"
- "net"
- )
- func process(conn net.Conn) {
- defer conn.Close()
- reader := bufio.NewReader(conn)
- buf := make([]byte, 1024)
- for {
- n, err := reader.Read(buf)
- //讀完了
- if err == io.EOF {
- fmt.Println("讀完了")
- break
- }
- //讀錯(cuò)了
- if err != nil {
- fmt.Println("數(shù)據(jù)讀取失敗", err)
- return
- }
- recvStr := string(buf[:n])
- fmt.Println("客戶(hù)端發(fā)送過(guò)來(lái)的值:", recvStr)
- }
- }
- func main() {
- lister, err := net.Listen("tcp", "0.0.0.0:8008")
- if err != nil {
- fmt.Println("連接失敗", err)
- return
- }
- defer lister.Close()
- for {
- fmt.Println("等待建立連接,此時(shí)會(huì)阻塞住")
- conn, err := lister.Accept() //等待建立連接
- fmt.Println("連接建立成功,繼續(xù)")
- if err != nil {
- fmt.Println("建立連接失敗", err)
- //繼續(xù)監(jiān)聽(tīng)下次鏈接
- continue
- }
- go process(conn)
- }
- }
客戶(hù)端
- package main
- import (
- "fmt"
- "net"
- )
- //客戶(hù)端
- func main() {
- conn, err := net.Dial("tcp", "192.168.10.148:8008")
- if err != nil {
- fmt.Println("連接服務(wù)器失敗", err)
- }
- defer conn.Close()
- for i := 0; i < 10; i++ {
- sendStr := "hello world ads asdf asd fads fadsf ads ads asd asd ads "
- conn.Write([]byte(sendStr))
- time.Sleep(time.Second)
- }
- }
注意:18行代碼睡眠了1s
執(zhí)行結(jié)果

如果我注釋了第18行代碼

執(zhí)行結(jié)果

直接都淦到一行了,what?這是啥情況,不應(yīng)該跟原來(lái)一樣嗎???
每發(fā)送一個(gè)值,那邊就接收一下,這怎么整到一塊了!!!
原因
主要原因是因?yàn)槲覀兪菓?yīng)用層軟件,是跑在操作系統(tǒng)之上的軟件,當(dāng)我們向服務(wù)器發(fā)送一個(gè)數(shù)據(jù)時(shí),是調(diào)用操作系統(tǒng)的相關(guān)接口發(fā)送的,操作系統(tǒng)再經(jīng)過(guò)各種復(fù)雜的操作,發(fā)送到對(duì)方機(jī)器
但是操作系統(tǒng)有一個(gè)發(fā)送數(shù)據(jù)緩沖區(qū),默認(rèn)情況如果緩沖區(qū)是有大小的,如果緩沖區(qū)沒(méi)滿(mǎn),是不會(huì)發(fā)送數(shù)據(jù)的,所以上述客戶(hù)端在發(fā)送數(shù)據(jù)時(shí),系統(tǒng)的緩沖區(qū)都沒(méi)滿(mǎn),一直壓在了操作系統(tǒng)的緩沖區(qū)中,最后發(fā)現(xiàn)沒(méi)數(shù)據(jù)了,才一次都發(fā)送到服務(wù)端
但是為什么sleep(1)又管用了呢?這是因?yàn)榫彌_區(qū)不止一個(gè)程序在用,1s的時(shí)間足夠其他程序?qū)⒕彌_區(qū)打滿(mǎn),然后各自發(fā)各自的數(shù)據(jù),這也是為什么第一次操作沒(méi)問(wèn)題,第二次有問(wèn)題,因?yàn)榈诙稳慷际俏覀兛蛻?hù)端打滿(mǎn)的

解決粘包
工具函數(shù)
我們將解包封包的函數(shù)封裝一下
- socker_sitck/stick.go
- package socker_stick
- import (
- "bufio"
- "bytes"
- "encoding/binary"
- "fmt"
- )
- //Encode 將消息編碼
- func Encode(message string) ([]byte, error) {
- length := int32(len(message))
- var pkg = new(bytes.Buffer)
- //寫(xiě)入消息頭
- err := binary.Write(pkg, binary.LittleEndian, length)
- if err != nil {
- fmt.Println("寫(xiě)入消息頭失敗", err)
- return nil, err
- }
- //寫(xiě)入消息實(shí)體
- err = binary.Write(pkg, binary.LittleEndian, []byte(message))
- if err != nil {
- fmt.Println("寫(xiě)入消息實(shí)體失敗", err)
- return nil, err
- }
- return pkg.Bytes(), nil
- }
- //Decode解碼消息
- func Decode(reader *bufio.Reader) (string, error) {
- //讀取信息長(zhǎng)度
- lengthByte, _ := reader.Peek(4)
- lengthBuff := bytes.NewBuffer(lengthByte)
- var length int32
- err := binary.Read(lengthBuff, binary.LittleEndian, &length)
- if err != nil {
- return "", err
- }
- //BuffRead 返回緩沖區(qū)現(xiàn)有的可讀的字節(jié)數(shù)
- if int32(reader.Buffered()) < length+4 {
- return "", err
- }
- pack := make([]byte, int(4+length))
- _, err = reader.Read(pack)
- if err != nil {
- return "", err
- }
- return string(pack[4:]), nil
- }
服務(wù)端
- package main
- import (
- "a3_course/socker_stick"
- "bufio"
- "fmt"
- "io"
- "net"
- )
- func process(conn net.Conn) {
- defer conn.Close()
- reader := bufio.NewReader(conn)
- for {
- msg, err := socker_stick.Decode(reader)
- //讀完了
- if err == io.EOF {
- fmt.Println("讀完了")
- break
- }
- //讀錯(cuò)了
- if err != nil {
- fmt.Println("數(shù)據(jù)讀取失敗", err)
- return
- }
- fmt.Println("客戶(hù)端發(fā)送過(guò)來(lái)的值:", msg)
- }
- }
- func main() {
- lister, err := net.Listen("tcp", "0.0.0.0:8008")
- if err != nil {
- fmt.Println("連接失敗", err)
- return
- }
- defer lister.Close()
- for {
- fmt.Println("等待建立連接,此時(shí)會(huì)阻塞住")
- conn, err := lister.Accept() //等待建立連接
- fmt.Println("連接建立成功,繼續(xù)")
- if err != nil {
- fmt.Println("建立連接失敗", err)
- //繼續(xù)監(jiān)聽(tīng)下次鏈接
- continue
- }
- go process(conn)
- }
- }
客戶(hù)端
- package main
- import (
- "a3_course/socker_stick"
- "fmt"
- "net"
- )
- //客戶(hù)端
- func main() {
- conn, err := net.Dial("tcp", "192.168.10.148:8008")
- if err != nil {
- fmt.Println("連接服務(wù)器失敗", err)
- }
- defer conn.Close()
- for i := 0; i < 10; i++ {
- sendStr := "hello world ads asdf asd fads fadsf ads ads asd asd ads "
- data, err := socker_stick.Encode(sendStr)
- if err != nil {
- fmt.Println("編碼失敗",err)
- return
- }
- conn.Write(data)
- //time.Sleep(time.Second)
- }
- }
執(zhí)行結(jié)果

這次真的不管執(zhí)行幾次,都是這樣的結(jié)果
對(duì)了,只有TCP才有粘包
Go語(yǔ)言UDP
UDP是一個(gè)無(wú)連接協(xié)議,客戶(hù)端不會(huì)在乎服務(wù)端有沒(méi)有問(wèn)題,客戶(hù)端只管發(fā),通常用于實(shí)時(shí)性比較高的領(lǐng)域
例如直播行業(yè)
服務(wù)端
- package main
- import (
- "fmt"
- "net"
- )
- func main() {
- listen, err := net.ListenUDP("udp", &net.UDPAddr{
- IP: net.IPv4(0, 0, 0, 0),
- Port: 8009,
- })
- if err != nil {
- panic(fmt.Sprintf("udp啟動(dòng)失敗,err:%v", err))
- }
- defer listen.Close()
- for{
- var data = make([]byte,1024)
- n, addr, err := listen.ReadFromUDP(data)
- if err != nil {
- panic(fmt.Sprintf("讀取數(shù)據(jù)失敗,err:%v", err))
- }
- fmt.Println(string(data[:n]),addr,n)
- }
- }
客戶(hù)端
- package main
- import (
- "fmt"
- "net"
- )
- func main() {
- socker, err := net.DialUDP("udp", nil, &net.UDPAddr{
- IP: net.IPv4(0, 0, 0, 0),
- Port: 8009,
- })
- if err != nil {
- panic(fmt.Sprintf("連接服務(wù)器失敗,err:%v", err))
- }
- defer socker.Close()
- sendStr:="你好呀"
- _, err = socker.Write([]byte(sendStr))
- if err != nil {
- panic(fmt.Sprintf("數(shù)據(jù)發(fā)送失敗,err:%v", err))
- }
- }
執(zhí)行結(jié)果

總結(jié)
本次章節(jié)我們講述了什么是TCP,什么是UDP。
并且編寫(xiě)了代碼如何實(shí)現(xiàn)TCP服務(wù)端,TCP客戶(hù)端,UDP服務(wù)端,UDP客戶(hù)端。
講述了為什么會(huì)出現(xiàn)粘包,該怎么解決粘包。
逆水行舟,不進(jìn)則退!