網(wǎng)絡(luò)編程
1、互聯(lián)網(wǎng)協(xié)議介紹
2、Socket編程
2.1、socket圖解
?????????Socket是BSD UNIX進(jìn)程通信機(jī)制,通常也稱為“套接字”,用于描述IP地址和端口,是一個通信鏈的句柄。Socket可以理解為TCP/IP網(wǎng)絡(luò)API,程序猿可以用其來開發(fā)TCP/IP網(wǎng)絡(luò)上的應(yīng)用。電腦上運行的應(yīng)用程序通常通過“套接字”向網(wǎng)絡(luò)發(fā)出請求或者應(yīng)答請求。
1、socket又稱套接字,應(yīng)用程序通過套接字向網(wǎng)絡(luò)發(fā)出請求或者應(yīng)答網(wǎng)絡(luò)請求
2、常用的socket類型有兩種:流式socket和數(shù)據(jù)報socket,流式是一種面向連接(TCP)、數(shù)據(jù)報是一種無連接(UDP)
3、TCP:比較靠譜、面向連接、比較慢
4、UDP:不大靠譜、無連接、比較快
2.2、TCP編程
1、TCP協(xié)議:TCP/IP協(xié)議即傳輸控制協(xié)議/網(wǎng)絡(luò)協(xié)議,是一種面向連接、可靠的、基于字節(jié)流的傳輸層通信協(xié)議,因為是面向連接的協(xié)議,數(shù)據(jù)像流水一樣傳輸,存在粘包的問題
2、TCP服務(wù)端:一個TCP服務(wù)端可以同時連接多個客戶端,例如世界各地的用戶使用自己電腦上的瀏覽器訪問淘寶網(wǎng),因為Go語言中創(chuàng)建多個goroutine實現(xiàn)并發(fā)非常方便和高效,所以我們可以每建立一次連接久創(chuàng)建一個goroutine去處理。
處理流程:1)監(jiān)聽端口;2)接收客戶端請求建立連接;3)創(chuàng)建goroutine處理鏈接
使用Go語言的net包實現(xiàn)TCP服務(wù)端代碼
// tcp/server/main.go
// TCP server端
// 處理函數(shù)
func process(conn net.Conn) {
defer conn.Close() // 關(guān)閉連接:為什么要寫在前面,避免忘記,defer使得我們可以在方法最后執(zhí)行關(guān)閉鏈接操作
for {
reader := bufio.NewReader(conn)
var buf [128]byte
n, err := reader.Read(buf[:]) // 讀取數(shù)據(jù)
if err != nil {
fmt.Println("read from client failed, err:", err)
break
}
recvStr := string(buf[:n])
fmt.Println("收到client端發(fā)來的數(shù)據(jù):", recvStr)
conn.Write([]byte(recvStr)) // 發(fā)送數(shù)據(jù)
}
}
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:20000") // 對127.0.0.1:20000地址進(jìn)行監(jiān)聽
if err != nil {
fmt.Println("listen failed, err:", err)
return
}
for { // 保持鏈接
conn, err := listen.Accept() // 建立連接
if err != nil {
fmt.Println("accept failed, err:", err)
continue
}
go process(conn) // 啟動一個goroutine處理連接
}
}
3、TCP客戶端
1)建立與服務(wù)端的連接;2)進(jìn)行數(shù)據(jù)收發(fā);3)關(guān)閉連接
// tcp/client/main.go
// 客戶端
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:20000")
if err != nil {
fmt.Println("err :", err)
return
}
defer conn.Close() // 關(guān)閉連接
inputReader := bufio.NewReader(os.Stdin) // 獲取鍵盤輸入的數(shù)據(jù)
for {
input, _ := inputReader.ReadString('\n') // 讀取用戶輸入
inputInfo := strings.Trim(input, "\r\n")
if strings.ToUpper(inputInfo) == "Q" { // 如果輸入q就退出
return
}
_, err = conn.Write([]byte(inputInfo)) // 發(fā)送數(shù)據(jù)
if err != nil {
return
}
buf := [512]byte{}
n, err := conn.Read(buf[:])
if err != nil {
fmt.Println("recv failed, err:", err)
return
}
fmt.Println(string(buf[:n]))
}
}
2.3、UDP編程
1、UDP協(xié)議:用戶數(shù)據(jù)報協(xié)議,是OSI參考模型中一種無連接的傳輸層協(xié)議,不需要建立連接就能直接進(jìn)行數(shù)據(jù)發(fā)送和接收,屬于不可靠,沒有時序的通信,但UDP協(xié)議的實時性比較好,通常用于視頻直播相關(guān)領(lǐng)域。
2、UDP服務(wù)端
// UDP/server/main.go
// UDP server端
func main() {
listen, err := net.ListenUDP("udp", &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 30000,
})
if err != nil {
fmt.Println("listen failed, err:", err)
return
}
defer listen.Close()
for {
var data [1024]byte
n, addr, err := listen.ReadFromUDP(data[:]) // 接收數(shù)據(jù)
if err != nil {
fmt.Println("read udp failed, err:", err)
continue
}
fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n)
_, err = listen.WriteToUDP(data[:n], addr) // 發(fā)送數(shù)據(jù)
if err != nil {
fmt.Println("write to udp failed, err:", err)
continue
}
}
}
3、UDP客戶端
// UDP 客戶端
func main() {
socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 30000,
})
if err != nil {
fmt.Println("連接服務(wù)端失敗,err:", err)
return
}
defer socket.Close()
sendData := []byte("Hello server")
_, err = socket.Write(sendData) // 發(fā)送數(shù)據(jù)
if err != nil {
fmt.Println("發(fā)送數(shù)據(jù)失敗,err:", err)
return
}
data := make([]byte, 4096)
n, remoteAddr, err := socket.ReadFromUDP(data) // 接收數(shù)據(jù)
if err != nil {
fmt.Println("接收數(shù)據(jù)失敗,err:", err)
return
}
fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
}
2.4、粘包(?沒有詳細(xì)看代碼,但是大概知道怎么實現(xiàn))
1、舉一個例子:
?????????1)服務(wù)端代碼
// go_base/server/main.go
// 同上面TCP編程的服務(wù)端代碼
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
var buf [1024]byte
for {
n, err := reader.Read(buf[:])
if err == io.EOF {
break
}
if err != nil {
fmt.Println("read from client failed, err:", err)
break
}
recvStr := string(buf[:n])
fmt.Println("收到client發(fā)來的數(shù)據(jù):", recvStr)
}
}
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:30000")
if err != nil {
fmt.Println("listen failed, err:", err)
return
}
defer listen.Close()
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("accept failed, err:", err)
continue
}
go process(conn)
}
}
????????2)客戶端代碼
// socket_stick/client/main.go
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:30000")
if err != nil {
fmt.Println("dial failed, err", err)
return
}
defer conn.Close()
for i := 0; i < 20; i++ {
msg := `Hello, Hello. How are you?` // 往這個倆節(jié)寫入20條msg
conn.Write([]byte(msg))
}
}
?????????3)結(jié)果:不會按照我們最初的想法,客戶端一次發(fā)送20條信息,服務(wù)端一次性接收,多條數(shù)據(jù)粘在一起
收到client發(fā)來的數(shù)據(jù): Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client發(fā)來的數(shù)據(jù): Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client發(fā)來的數(shù)據(jù): Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client發(fā)來的數(shù)據(jù): Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client發(fā)來的數(shù)據(jù): Hello, Hello. How are you?Hello, Hello. How are you?
2、為什么會出現(xiàn)粘包?
?????????1)tcp數(shù)據(jù)傳輸模式是流模式,在保持長連接的時候可以進(jìn)行多次收和發(fā),可能發(fā)生在接收方、也可能發(fā)生在接收端
?????????2)我們提交一段數(shù)據(jù)給TCP發(fā)送的時候,TCP沒有立即發(fā)送此段數(shù)據(jù),而是等待一小段時間看看是否還有要發(fā)送的數(shù)據(jù),若有則會把這兩段數(shù)據(jù)發(fā)送出去
?????????3)接受端不及時接收數(shù)據(jù),導(dǎo)致粘包
3、解決辦法:關(guān)鍵在于接收方不知道傳輸?shù)臄?shù)據(jù)包大小,因此我們可以對數(shù)據(jù)包進(jìn)行封包和拆包的操作
(??建議手敲一遍,不要只是看)
?????????封包:給一段數(shù)據(jù)加上包頭,這樣以來數(shù)據(jù)包久分為包頭和包體兩個部分內(nèi)容,包頭長度是固定的,并且存儲了包體的長度,根據(jù)包頭長度以及包頭中包體長度的變量就可以正確的拆分出一個完整的數(shù)據(jù)包。 => 自己定一個協(xié)議(數(shù)據(jù)包前4個字節(jié)為包頭,里面存儲的是發(fā)送的數(shù)據(jù)長度)
// 1、編碼和解碼
// 文件位置gobase/proto/proto.go
package proto
import (
"bufio"
"bytes"
"encoding/binary"
)
// Encode Encode消息編碼
func Encode(message string) ([]byte, error) {
// 讀取消息長度,轉(zhuǎn)換成為int32類型(4個字節(jié))
var length = int32(len(message))
var pkg = new(bytes.Buffer)
// 寫入消息頭
err := binary.Write(pkg, binary.LittleEndian, length)
if err != nil {
return nil, err
}
// 寫入消息實體
err = binary.Write(pkg, binary.LittleEndian, []byte(message))
if err != nil {
return nil, err
}
return pkg.Bytes(), nil
}
// Decode 解碼消息
func Decode(reader *bufio.Reader) (string, error) {
// 讀取消息長度
lengthByte, _ := reader.Peek(4)
lengthBuffer := bytes.NewBuffer(lengthByte)
var length int32
err := binary.Read(lengthBuffer, binary.LittleEndian, &length)
if err != nil {
return "", err
}
// Buffered返回緩沖中現(xiàn)有可讀取的字節(jié)數(shù)
if int32(reader.Buffered()) < length+4 {
return "", err
}
// 讀取真正的消息數(shù)據(jù)
pack := make([]byte, int(4+length))
_, err = reader.Read(pack)
if err != nil {
return "", err
}
return string(pack[4:]), nil
}
// 2、服務(wù)端代碼
// 文件位置gobase/server/main.go
package main
import (
"../proto"
"bufio"
"fmt"
"io"
"net"
)
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for {
msg, err := proto.Decode(reader)
if err == io.EOF {
return
}
if err != nil {
fmt.Println("decode msg faild, err = ", err)
return
}
fmt.Println("收到client發(fā)來的數(shù)據(jù)", msg)
}
}
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:30000")
if err != nil {
fmt.Println("listen faild, err = ", err)
return
}
defer listen.Close()
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("accept failed, err = ", err)
continue
}
go process(conn)
}
}
// 3、客戶端代碼
// 文件位置gobase/client/main.go
package main
import (
"../proto"
"fmt"
"net"
)
func main() {
conn, err := net.Dial("tcp","127.0.0.1:30000")
if err != nil {
fmt.Println("dail failed, err = " , err)
return
}
defer conn.Close()
for i := 0; i < 6; i++ {
msg := "hello,go"
data, err := proto.Encode(msg)
if err != nil {
fmt.Println("encode msg failed, err", err)
return
}
conn.Write(data)
}
}
// 運行結(jié)果
收到client發(fā)來的數(shù)據(jù) hello,go
收到client發(fā)來的數(shù)據(jù) hello,go
收到client發(fā)來的數(shù)據(jù) hello,go
收到client發(fā)來的數(shù)據(jù) hello,go
收到client發(fā)來的數(shù)據(jù) hello,go
收到client發(fā)來的數(shù)據(jù) hello,go
3、Http編程
1、web工作流程
1、客戶機(jī)通過TCP/IP協(xié)議建立服務(wù)器到TCP連接
2、客戶端向服務(wù)器發(fā)送HTTP協(xié)議請求包,請求服務(wù)器里的資源文檔
3、服務(wù)器向客戶機(jī)發(fā)送HTTP協(xié)議應(yīng)答包,如果請求的資源包含有動態(tài)語言的內(nèi)容,那么服務(wù)器會調(diào)用動態(tài)語言的解釋引擎負(fù)責(zé)處理“動態(tài)內(nèi)容”,并將處理得到的數(shù)據(jù)返回給客戶端
4、客戶機(jī)與服務(wù)器斷開,由客戶端解釋HTML文檔,在客戶端屏幕上渲染圖形結(jié)果
2、HTTP協(xié)議
?????????稱為超文本傳輸協(xié)議,是互聯(lián)網(wǎng)上應(yīng)用最為廣泛的一種網(wǎng)絡(luò)協(xié)議,它詳細(xì)的規(guī)定了瀏覽器和萬維網(wǎng)服務(wù)器之間相互通信的規(guī)則,通過因特網(wǎng)傳送萬維網(wǎng)文檔的數(shù)據(jù)傳送協(xié)議;HTTP協(xié)議通常承載于TCP協(xié)議之上
4、WebSocket編程
1、webScoket編程是什么?文章來源:http://www.zghlxwxcb.cn/news/detail-438288.html
1、WebSocket是一種單個TCP連接上進(jìn)行全雙工通信的協(xié)議
2、WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,允許服務(wù)端主動向客戶端推送數(shù)據(jù)
3、WebSocket API中,瀏覽器和服務(wù)器只需要一次握手,二者之間就可以直接創(chuàng)建持久性連接,并進(jìn)行簡單雙向數(shù)據(jù)傳輸
4、需要安裝第三方包: go get -u -v github.com/gorilla/websocket -> 11之后的版本 使用 go install
2、這個有一個聊天室的小例子,后續(xù)可以單獨的出一塊提供大家理解(todo)文章來源地址http://www.zghlxwxcb.cn/news/detail-438288.html
到了這里,關(guān)于【Go】五、網(wǎng)絡(luò)編程的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!