【Go】五、网络编程

本文介绍了网络编程的基础知识,包括互联网协议、Socket编程的原理与实现,详细讲解了TCP和UDP的特性及Go语言中的实现示例。此外,还提及了HTTP协议的工作流程和WebSocket协议在全双工通信中的作用。
摘要由CSDN通过智能技术生成

网络编程

1、互联网协议介绍

null

2、Socket编程

2.1、socket图解

​        Socket是BSD UNIX进程通信机制,通常也称为“套接字”,用于描述IP地址和端口,是一个通信链的句柄。Socket可以理解为TCP/IP网络API,程序猿可以用其来开发TCP/IP网络上的应用。电脑上运行的应用程序通常通过“套接字”向网络发出请求或者应答请求。

null

1、socket又称套接字,应用程序通过套接字向网络发出请求或者应答网络请求

2、常用的socket类型有两种:流式socket和数据报socket,流式是一种面向连接(TCP)、数据报是一种无连接(UDP)

3、TCP:比较靠谱、面向连接、比较慢

4、UDP:不大靠谱、无连接、比较快

2.2、TCP编程

1、TCP协议:TCP/IP协议即传输控制协议/网络协议,是一种面向连接、可靠的、基于字节流的传输层通信协议,因为是面向连接的协议,数据像流水一样传输,存在粘包的问题

2、TCP服务端:一个TCP服务端可以同时连接多个客户端,例如世界各地的用户使用自己电脑上的浏览器访问淘宝网,因为Go语言中创建多个goroutine实现并发非常方便和高效,所以我们可以每建立一次连接久创建一个goroutine去处理。

处理流程:1)监听端口;2)接收客户端请求建立连接;3)创建goroutine处理链接

使用Go语言的net包实现TCP服务端代码
// tcp/server/main.go
// TCP server端
// 处理函数
func process(conn net.Conn) {
    defer conn.Close() // 关闭连接:为什么要写在前面,避免忘记,defer使得我们可以在方法最后执行关闭链接操作
    for {
        reader := bufio.NewReader(conn)
        var buf [128]byte
        n, err := reader.Read(buf[:]) // 读取数据
        if err != nil {
            fmt.Println("read from client failed, err:", err)
            break
        }
        recvStr := string(buf[:n])
        fmt.Println("收到client端发来的数据:", recvStr)
        conn.Write([]byte(recvStr)) // 发送数据
    }
}

func main() {
	  listen, err := net.Listen("tcp", "127.0.0.1:20000") // 对127.0.0.1:20000地址进行监听
    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)建立与服务端的连接;2)进行数据收发;3)关闭连接
// 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() // 关闭连接
    inputReader := bufio.NewReader(os.Stdin) // 获取键盘输入的数据
    for {
        input, _ := inputReader.ReadString('\n') // 读取用户输入
        inputInfo := strings.Trim(input, "\r\n")
        if strings.ToUpper(inputInfo) == "Q" { // 如果输入q就退出
            return
        }
        _, err = conn.Write([]byte(inputInfo)) // 发送数据
        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协议:用户数据报协议,是OSI参考模型中一种无连接的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠,没有时序的通信,但UDP协议的实时性比较好,通常用于视频直播相关领域。

2、UDP服务端

// 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[:]) // 接收数据
        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) // 发送数据
        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("连接服务端失败,err:", err)
        return
    }
    defer socket.Close()
    sendData := []byte("Hello server")
    _, err = socket.Write(sendData) // 发送数据
    if err != nil {
        fmt.Println("发送数据失败,err:", err)
        return
    }
    data := make([]byte, 4096)
    n, remoteAddr, err := socket.ReadFromUDP(data) // 接收数据
    if err != nil {
        fmt.Println("接收数据失败,err:", err)
        return
    }
    fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
}

2.4、粘包(❌没有详细看代码,但是大概知道怎么实现)

1、举一个例子:

​        1)服务端代码

// go_base/server/main.go
// 同上面TCP编程的服务端代码

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发来的数据:", 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?` // 往这个俩节写入20条msg
        conn.Write([]byte(msg))
    }
}

​        3)结果:不会按照我们最初的想法,客户端一次发送20条信息,服务端一次性接收,多条数据粘在一起

收到client发来的数据: 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发来的数据: 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发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client发来的数据: 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发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?

2、为什么会出现粘包?

​        1)tcp数据传输模式是流模式,在保持长连接的时候可以进行多次收和发,可能发生在接收方、也可能发生在接收端

​        2)我们提交一段数据给TCP发送的时候,TCP没有立即发送此段数据,而是等待一小段时间看看是否还有要发送的数据,若有则会把这两段数据发送出去

​        3)接受端不及时接收数据,导致粘包

3、解决办法:关键在于接收方不知道传输的数据包大小,因此我们可以对数据包进行封包和拆包的操作(🌟建议手敲一遍,不要只是看)

​        封包:给一段数据加上包头,这样以来数据包久分为包头和包体两个部分内容,包头长度是固定的,并且存储了包体的长度,根据包头长度以及包头中包体长度的变量就可以正确的拆分出一个完整的数据包。 => 自己定一个协议(数据包前4个字节为包头,里面存储的是发送的数据长度)

// 1、编码和解码
// 文件位置gobase/proto/proto.go
package proto

import (
	"bufio"
	"bytes"
	"encoding/binary"
)

// Encode Encode消息编码
func Encode(message string) ([]byte, error) {
	// 读取消息长度,转换成为int32类型(4个字节)
	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返回缓冲中现有可读取的字节数
	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
}


// 2、服务端代码
// 文件位置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发来的数据", 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)
	}
}



// 运行结果
收到client发来的数据 hello,go
收到client发来的数据 hello,go
收到client发来的数据 hello,go
收到client发来的数据 hello,go
收到client发来的数据 hello,go
收到client发来的数据 hello,go

3、Http编程

1、web工作流程

1、客户机通过TCP/IP协议建立服务器到TCP连接
2、客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档
3、服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端
4、客户机与服务器断开,由客户端解释HTML文档,在客户端屏幕上渲染图形结果

2、HTTP协议

​        称为超文本传输协议,是互联网上应用最为广泛的一种网络协议,它详细的规定了浏览器和万维网服务器之间相互通信的规则,通过因特网传送万维网文档的数据传送协议;HTTP协议通常承载于TCP协议之上

4、WebSocket编程

1、webScoket编程是什么?

1、WebSocket是一种单个TCP连接上进行全双工通信的协议
2、WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据
3、WebSocket API中,浏览器和服务器只需要一次握手,二者之间就可以直接创建持久性连接,并进行简单双向数据传输
4、需要安装第三方包: go get -u -v github.com/gorilla/websocket -> 11之后的版本 使用 go install

2、这个有一个聊天室的小例子,后续可以单独的出一块提供大家理解(todo)

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Coder陈、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值