一篇文章帶你瞭解 Go 語言基礎之網絡編程
前言
Hi,大家好呀,我是碼農,星期八,我們身處 21 世紀,我們的世界已經在不知不覺中,就像很多網一樣在互聯互通。
互聯網是一個統稱,目前比較常用的有TCP
,UDP
協議。
當然,還有很多其他的協議,但是本次主要講最常用的TCP
和UDP
協議。
socker 編程
我們所學的TCP
和UDP
,統稱爲Socker
編程,也叫做套接字編程。
多臺機器要實現互相通訊,其實是一個非常複雜的過程,底層從鋪設網線,網線接口,交換機,路由器,在到規定各種協議。
再到應用層QQ
,微信
等軟件。
如果沒有一套標準,每次使用都要自己去實現,可能每個程序員都不是掉頭髮那麼簡單了!
有了Socker
之後,Socker
會在應用層之前,將各種繁瑣的的底層操作隱藏,我們可能只需要Socker.TCP
就實現了TCP
協議的通訊。
Go 語言 TCP
TCP 屬於穩定的,可靠的長連接,
既然要涉及通訊,必然有兩個終端,最起碼一個是服務端,一個是客戶端,就像我們的淘寶,我們每次打開淘寶,都要去鏈接它,當然,淘寶可不直接是TCP
。
服務端
在 Go 中實現服務端,並且在服務端併發很簡單,只需要將每個連接讓一個協程處理即可!
代碼
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("數據讀取失敗", err)
return
}
recvStr := string(buf[:n])
fmt.Println("客戶端發送過來的值:", recvStr)
}
}
func main() {
lister, err := net.Listen("tcp", "0.0.0.0:8008")
if err != nil {
fmt.Println("連接失敗", err)
}
for {
fmt.Println("等待建立連接,此時會阻塞住")
conn, err := lister.Accept() //等待建立連接
fmt.Println("連接建立成功,繼續")
if err != nil {
fmt.Println("建立連接失敗", err)
//繼續監聽下次鏈接
continue
}
go process(conn)
}
}
客戶端
客戶端就很簡單了,相對來說是不需要併發的,只需要連接就行。
代碼
package main
import (
"bufio"
"fmt"
"net"
"os"
)
//客戶端
func main() {
conn, err := net.Dial("tcp", "192.168.10.148:8008")
if err != nil {
fmt.Println("連接服務器失敗",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("發送成功")
}
}
}
執行結果
就這樣,我們實現了服務端併發的處理所有客戶端的請求。
粘包
我們先看一下什麼是粘包。
服務端
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
}
//讀錯了
if err != nil {
fmt.Println("數據讀取失敗", err)
return
}
recvStr := string(buf[:n])
fmt.Println("客戶端發送過來的值:", 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("等待建立連接,此時會阻塞住")
conn, err := lister.Accept() //等待建立連接
fmt.Println("連接建立成功,繼續")
if err != nil {
fmt.Println("建立連接失敗", err)
//繼續監聽下次鏈接
continue
}
go process(conn)
}
}
客戶端
package main
import (
"fmt"
"net"
)
//客戶端
func main() {
conn, err := net.Dial("tcp", "192.168.10.148:8008")
if err != nil {
fmt.Println("連接服務器失敗", 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
執行結果
如果我註釋了第 18 行代碼
執行結果
直接都淦到一行了,what?這是啥情況,不應該跟原來一樣嗎???
每發送一個值,那邊就接收一下,這怎麼整到一塊了!!!
原因
主要原因是因爲我們是應用層軟件,是跑在操作系統之上的軟件,當我們向服務器發送一個數據時,是調用操作系統的相關接口發送的,操作系統再經過各種複雜的操作,發送到對方機器
但是操作系統有一個發送數據緩衝區,默認情況如果緩衝區是有大小的,如果緩衝區沒滿,是不會發送數據的,所以上述客戶端在發送數據時,系統的緩衝區都沒滿,一直壓在了操作系統的緩衝區中,最後發現沒數據了,才一次都發送到服務端
但是爲什麼sleep(1)
又管用了呢? 這是因爲緩衝區不止一個程序在用,1s 的時間足夠其他程序將緩衝區打滿,然後各自發各自的數據,這也是爲什麼第一次操作沒問題,第二次有問題,因爲第二次全部都是我們客戶端打滿的
解決粘包
工具函數
我們將解包封包的函數封裝一下
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)
//寫入消息頭
err := binary.Write(pkg, binary.LittleEndian, length)
if err != nil {
fmt.Println("寫入消息頭失敗", err)
return nil, err
}
//寫入消息實體
err = binary.Write(pkg, binary.LittleEndian, []byte(message))
if err != nil {
fmt.Println("寫入消息實體失敗", err)
return nil, err
}
return pkg.Bytes(), nil
}
//Decode解碼消息
func Decode(reader *bufio.Reader) (string, error) {
//讀取信息長度
lengthByte, _ := reader.Peek(4)
lengthBuff := bytes.NewBuffer(lengthByte)
var length int32
err := binary.Read(lengthBuff, binary.LittleEndian, &length)
if err != nil {
return "", err
}
//BuffRead 返回緩衝區現有的可讀的字節數
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
}
服務端
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
}
//讀錯了
if err != nil {
fmt.Println("數據讀取失敗", err)
return
}
fmt.Println("客戶端發送過來的值:", 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("等待建立連接,此時會阻塞住")
conn, err := lister.Accept() //等待建立連接
fmt.Println("連接建立成功,繼續")
if err != nil {
fmt.Println("建立連接失敗", err)
//繼續監聽下次鏈接
continue
}
go process(conn)
}
}
客戶端
package main
import (
"a3_course/socker_stick"
"fmt"
"net"
)
//客戶端
func main() {
conn, err := net.Dial("tcp", "192.168.10.148:8008")
if err != nil {
fmt.Println("連接服務器失敗", 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)
}
}
執行結果
這次真的不管執行幾次,都是這樣的結果
對了,只有TCP
纔有粘包
Go 語言 UDP
UDP
是一個無連接協議,客戶端不會在乎服務端有沒有問題,客戶端只管發,通常用於實時性比較高的領域
例如直播行業
服務端
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啓動失敗,err:%v", err))
}
defer listen.Close()
for{
var data = make([]byte,1024)
n, addr, err := listen.ReadFromUDP(data)
if err != nil {
panic(fmt.Sprintf("讀取數據失敗,err:%v", err))
}
fmt.Println(string(data[:n]),addr,n)
}
}
客戶端
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("連接服務器失敗,err:%v", err))
}
defer socker.Close()
sendStr:="你好呀"
_, err = socker.Write([]byte(sendStr))
if err != nil {
panic(fmt.Sprintf("數據發送失敗,err:%v", err))
}
}
執行結果
總結
本次章節我們講述了什麼是 TCP,什麼是 UDP。
並且編寫了代碼如何實現 TCP 服務端,TCP 客戶端,UDP 服務端,UDP 客戶端。
講述了爲什麼會出現粘包,該怎麼解決粘包。
逆水行舟,不進則退!
如果在操作過程中有任何問題,記得下面留言,我們看到會第一時間解決問題。
我是碼農星期八,如果覺得還不錯,記得動手點贊一下哈。
感謝你的觀看。
如果你覺得文章還可以,記得點贊留言支持我們哈。感謝你的閱讀,有問題請記得在下方留言噢~
想學習更多關於 Go 的知識,可以參考學習網址:http://pdcfighting.com/,點擊閱讀原文,可以直達噢~
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/FBTJ6Dqy-9xzjfbmHID7xA