Go笔记day6 | 青训营

54 阅读8分钟

Tcp服务端客户端go编写

服务器端(S端)

  1. process函数用于处理与客户端的通信。通过创建带有缓冲区的读取器(bufio.NewReader)来读取客户端发送的数据,并将其存储到缓冲区中。
  2. 通过调用读取器的Read方法,从缓冲区中读取数据,并检查是否发生错误。如果没有错误,则将接收到的数据转换为字符串并打印出来。
  3. 使用连接对象的Write方法,将接收到的数据发送回客户端。

主函数(main)负责监听客户端连接请求并创建新的处理协程。通过使用net.Listen函数监听指定的IP地址和端口号,然后在无限循环中接受客户端连接请求。 当有新的连接请求到达时,调用accept方法接受连接,并使用go关键字创建一个新的协程来处理该连接。

客户端端(C端)

  1. main函数中的net.Dial方法用于与服务器建立TCP连接。
  2. 通过创建一个bufio.NewReader对象来读取用户的输入。
  3. 使用一个无限循环,读取用户的输入并将其发送到服务器端。
  4. 如果用户输入了字母"q"(不区分大小写),则退出循环。
  5. 使用conn.Write方法将输入数据发送到服务器端,并通过conn.Read方法接收服务器的响应数据。
  6. 将接收到的数据转换为字符串并打印出来。

UDP服务器和客户端代码解析: UDP服务器和客户端采用类似的方式实现消息的收发功能,使用net.ListenUDPnet.DialUDP函数创建UDP监听与连接。 服务器端通过循环调用listen.ReadFromUDP方法从UDP连接读取数据,并使用listen.WriteToUDP方法向客户端发送数据。 客户端通过调用socket.Write方法向服务器发送数据,并使用socket.ReadFromUDP方法从服务器接收数据。

TCP粘包处理代码解析: 为了处理TCP粘包问题,引入了EncodeDecode两个函数。 Encode函数将输入的消息字符串转换为字节切片,并在切片开头添加表示消息长度的4个字节。 Decode函数从给定的bufio.Reader对象中读取前4个字节,解析出消息长度,然后从缓冲区中读取完整的消息数据。 如果缓冲区中可读取的字节数不足以读取完整的消息,则等待更多数据到达。

在服务器端,使用Decode函数解析从客户端接收到的数据。 在客户端端,使用Encode函数将要发送的数据进行编码,然后通过conn.Write方法发送。

以上是对提供的代码进行的简要解析。这段代码展示了如何使用Go语言实现TCP和UDP的基本通信功能,并且包含了TCP粘包处理的逻辑。

S端
func process(conn net.Conn) {
	defer conn.Close()
	for {
		reader := bufio.NewReader(conn) //创建一个带有缓冲区的读取器
		var buf [128]byte
		n, err := reader.Read(buf[:]) //读取数据
		if err != nil {
			fmt.Println("read from C failed : ", err)
			break
		}
		recvstr := string(buf[:n])
		fmt.Println("收到clinet发来的数据:", recvstr)
		conn.Write([]byte(recvstr)) //发送数据
	}
}

func main() {
	listen, err := net.Listen("tcp", "127.0.0.1:20000")
	if err != nil {
		fmt.Println("listen failed :", err)
		return
	}
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("accept failed :", err)
			continue
		}
		go process(conn)
	}
}
C端
package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)

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]))
	}
}

UDP服务器客户端go编写

func main() {
	listen, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 30000,
	})
	if err != nil {
		fmt.Println("listen is failed:", 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)
			continue
		}
		fmt.Printf("data:%v addr:%v count:%v\n", string(data[:]), addr, n)
		_, err = listen.WriteToUDP(data[:], addr) //发送数据
		if err != nil {
			fmt.Println("write to udp failed:", err)
			continue
		}
	}
}

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

TCP粘包处理

func Encode(message string) ([]byte, error) {
	var length = int32(len(message))
	var pkg = new(bytes.Buffer) //创建存储二进制的字节缓冲区

	//将给定的数据按照给定的字节序写入到缓冲区中
	err := binary.Write(pkg, binary.LittleEndian, length)
	if err != nil {
		return nil, err
	}
	return pkg.Bytes(), nil //缓冲区内容以字节数组返回
}

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
	}

	//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[:]), nil
}

针对上面的解码编码的S和C如下

S:

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	for {
		msg, err := Decode(reader)
		if err == io.EOF {
			return
		}
		if err != nil {
			fmt.Println("decode msg failed :", err)
			return
		}
		fmt.Println("收到Client端发送的数据:", msg)
	}
}

func main() {
	listen, err := net.Listen("tcp", "127.0.0.1:30000")
	if err != nil {
		fmt.Println("listen failed : ", err)
		return
	}
	defer listen.Close()

	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("accept is failed", err)
			return
		}
		go process(conn)
	}
}

c:

func main(){
	conn,err :=net.Dial("tcp","127.0.0.1:30000")
	if err!=nil{
		fmt.Println("dial failed :",err)
		return
	}
	defer conn.Close()

	for i:=0;i<20;i++{
		msg:="Hello"
		data,err:=Encode(msg)
		if err!=nil{
			fmt.Println("encode msg failed :",err)
			return
		}
		conn.Write(data)
	}
}

Http编程

go的包实在是太多了 刚接触的go现在是不知道就学一点还是整体都学 主要也是没时间

S端

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/go", my_handler)
	http.ListenAndServe("127.0.0.1:8000", nil)
}

/*
http.ResponseWriter表示一个用于写入HTTP
响应的接口类型参数
*/

func my_handler(w http.ResponseWriter, r *http.Request) {
	fmt.Println(r.RemoteAddr, "连接成功")
	//请求方式:GET POST DELETE PUT UPDATE
	fmt.Println("method:", r.Method)
	// /go
	fmt.Println("url:", r.URL)
	fmt.Println("header:", r.Header)
	fmt.Println("body:", r.Body)
	//回复
	w.Write([]byte("的好坏都啊哇好多啊who打完后段和我udahwdwoaud"))
}

C端

package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	resp, _ := http.Get("http://127.0.0.1:8000/go")
	defer resp.Body.Close()
	//关闭响应的主体内容

	//200 OK
	fmt.Println(resp.Status)

	fmt.Println(resp.Header)

	buf := make([]byte, 1024)
	for {
		//接收服务端信息
		n, err := resp.Body.Read(buf)
		if err != nil && err != io.EOF {
			fmt.Println(err)
			return
		} else {
			fmt.Println("读取完毕")
			res := string(buf[:n])
			fmt.Println(res)
			break
		}
	}
}

go中的反射 reflect例子

package main

import (
	"fmt"
	"reflect"
)

type a struct {
	Num int
	Str string
}

func (val a) fun() {
	fmt.Println("函数")
}

func main() {
	// var test a
	// test = a{12, "dadw"}
	test := a{12, "dad"}
	test.fun()
	Do(test)
}

func Do(input interface{}) {
	intype := reflect.TypeOf(input)
	invalue := reflect.ValueOf(input)

	//通过type获取里面的字段
	for i := 0; i < intype.NumField(); i++ {
		field := intype.Field(i)
		value := invalue.Field(i) //.Interface()
		fmt.Printf("%s : %v = %v\n", field.Name, field.Type, value)
	}

	for i := 0; i < intype.NumMethod(); i++ {
		m := intype.Method(i)
		fmt.Printf("%s : %v\n", m.Name, m.Type)
	}

}

结构体标签Tag

package main

import (
	"fmt"
	"reflect"
)

type resume struct {
	Name string `info:"name" doc:"我的名字"`
	Sex  string `info:"sex"`
}

func fundTag(str interface{}) {
	t := reflect.TypeOf(str).Elem() //得到当前结构体的所有的元素

	for i := 0; i < t.NumField(); i++ {
		tagstring := t.Field(i).Tag.Get("info")
		fmt.Println("info", tagstring)
	}
}

func main() {
	var s resume
	fundTag(&s)
}

channel

无缓冲区

package main

import (
	"fmt"
)

//channel是两个goroutine之间通信的机制   channel具有同步两个不同go之间的能力

func main() {

	c := make(chan int)
	go func() {
		defer fmt.Println("goroutine结束")

		fmt.Println("goroutine正在运行...")
		c <- 666
	}()

	//	num := <-c
	//	fmt.Println("num:", num)

}

缓冲channel

package main

import (
	"fmt"
	"time"
)

func main() {
	c := make(chan int, 3) //带有缓冲的channel  有缓存不会发送阻塞

	fmt.Println("len(c)= ", len(c), "cap(c)= ", cap(c)) //len 当前元素数量  cap 当前容量

	go func() {
		defer fmt.Println("子go结束")

		for i := 0; i < 6; i++ {
			c <- i
			fmt.Println("子go正在运行: len(c)= ", len(c), "cap(c)= ", cap(c), "元素:", i)
		}
		close(c)
	}()

	time.Sleep(2 * time.Second)

	for {
		if data, ok := <-c; ok { //ok为true表示channel没有关闭  false表示channel已关闭
			fmt.Println("数据=", data)
		} else {
			break
		}
	}

	time.Sleep(2 * time.Second)
}

首先,关于Go语言的HTTP编程部分,S端和C端的代码都是正确的。S端使用net/http包创建了一个简单的HTTP服务器,并在/go路径上注册了处理函数my_handler。C端则使用http.Get方法发送一个GET请求到S端的地址,并获取响应内容。

关于反射(reflect)的例子,代码中定义了一个结构体a,并为其定义了一个方法fun。在main函数中,创建了一个test对象,并调用了其fun方法,然后通过Do函数对test对象进行反射处理。Do函数使用reflect.TypeOf获取输入的类型信息,使用reflect.ValueOf获取输入的值信息,然后通过遍历字段和方法的方式打印出相关信息。

关于结构体标签(Tag)的例子,代码中定义了一个结构体resume,并为其字段添加了标签。在main函数中,通过反射获取结构体的标签信息,并打印出来。

关于通道(channel)的例子,代码中展示了无缓冲通道和带缓冲通道的用法。无缓冲通道在发送和接收数据时会阻塞,直到有goroutine准备好接收或发送。带缓冲通道可以存储一定数量的数据,在缓冲区未满时发送不会阻塞,在缓冲区未空时接收不会阻塞。代码中展示了如何创建通道、向通道发送数据、从通道接收数据以及关闭通道。