Go语言网络编程

使用的协议是tcp,由于tcp协议传输数据的时候会有粘包现象,所以为了解决消除这个现象,又编写了两个工具函数Encode和Decode

消除粘包现象的方法是在自定义一个应用层协议,他的内容为每次发送的数据包的前4个字节表示数据的长度,然后后面才是真正发送的数据

首先是工具包:

proto.go

package proto

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"net"
)

// 每次发送的数据包前4个字节用来记录数据的长度,后面才是记录数据,这样一方面可以准确获知应该读取的长度,并且可以防止粘包现象的发生
// 粘包现象
// 在tcp这种面相连接的字节流协议中,例如客户端发送数据给服务端,客户端在快速向发送缓冲区中写入数据时,就有可能发生粘包现象
// 例如首先客户端在向发送缓冲区中写入数据abc,然后紧接着又写入def,那么为了保证发送数据的效率,发送缓冲区中的数据并不会马上发送出去,而是会等待一段时间,如果这段时间
// 还有其他数据到达了发送缓冲区,那么发送缓冲区会将这一段时间中到达的数据都发送出去,而不是将每条数据单独发送
// 这就是粘包现象

// Encode ...
func Encode(msg string) ([]byte, error) {
	length := int32(len(msg))
	// 新建一个缓冲区,这里是获取这个缓冲区的指针
	pkg := new(bytes.Buffer)
	err := binary.Write(pkg, binary.BigEndian, length)
	// 这里一定要将msg转化为[]byte的切片
	err = binary.Write(pkg, binary.BigEndian, []byte(msg))
	if err != nil {
		fmt.Println("write msg fail,err:", err)
		return nil, err
	}
	return pkg.Bytes(), nil
}

// Decode ...
func Decode(conn net.Conn) (int32, string, error) {
	lengthSlice := make([]byte, 4)
	_, err := conn.Read(lengthSlice)
	if err != nil {
		fmt.Println("Get msg's length fail,err:", err)
		return 0, "", err
	}
	// 将切片转化为一个缓冲区,切片中的内容就是缓冲区中的内容,切片必须是[]byte的切片
	lengthBuffer := bytes.NewBuffer(lengthSlice)
	var length int32
	err = binary.Read(lengthBuffer, binary.BigEndian, &length)
	if err != nil {
		fmt.Println("Parse the lenth to int32 fail,err:", err)
		return 0, "", err
	}
	msgSlice := make([]byte, length)
	_, err = conn.Read(msgSlice)
	if err != nil {
		fmt.Println("Get the msg fail,err:", err)
		return 0, "", err
	}
	msgBuffer := bytes.NewBuffer(msgSlice)
	var msg string
	// 将缓冲区中的数据按照大端的方式重新放入切片中
	err = binary.Read(msgBuffer, binary.BigEndian, msgSlice)
	msg = string(msgSlice)
	if err != nil {
		fmt.Println("Parse the msg to string fail,err:", err)
		return 0, "", err
	}
	return length, msg, nil
}

  然后是server端

package main

import (
	"fmt"
	"learnGO/socket/proto"
	"net"
)

func main() {
	listener, err := net.Listen("tcp", "127.0.0.1:4396")
	if err != nil {
		fmt.Println("Listen the ip and port err:", err)
		return
	}
	defer listener.Close()
	// 循环建立连接
	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("Establish the connnect failed,err:", err)
			return
		}
		go process(conn)
	}
}

func process(conn net.Conn) {
	defer conn.Close()
	for {
		// recv := make([]byte, 1024)
		// n, err := conn.Read(recv)
		// if err == io.EOF {
		// 	continue
		// }
		// if err != nil {
		// 	fmt.Println("Read msg from read buffer fail,err:", err)
		// 	return
		// }
		// fmt.Printf("%d:%s", n, string(recv))
		length, msg, err := proto.Decode(conn)
		if err != nil {
			return
		}
		fmt.Printf("%d:%s", length, msg)
	}
}

  然后是client端

package main

import (
	"bufio"
	"fmt"
	"learnGO/socket/proto"
	"net"
	"os"
)

func main() {
	// 向ip和端口拨号进行连接
	conn, err := net.Dial("tcp", "127.0.0.1:4396")
	if err != nil {
		fmt.Println("Connect to the server fail,err:", err)
		return
	}
	reader := bufio.NewReader(os.Stdin)
	for {
		fmt.Println("plz input:")
		msg, err := reader.ReadString('\n')
		if err != nil {
			fmt.Println("Input msg fail,err:", err)
			return
		}
		b, err := proto.Encode(msg)
		if err != nil {
			return
		}
		n, err := conn.Write(b)
		if err != nil {
			fmt.Println("Send msg fail,err:", err)
			return
		}
		fmt.Println(n)
	}
}

  HTTP服务器与客户端

这里是使用GET方法,而POST等其他方法还没有写

首先是服务器,服务器主要就是开始监听,然后对每一个url编写指定的处理函数

服务器代码:

package main

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

func main() {
	// http.HandleFunc("/gyy/", homeFunc)
	http.HandleFunc("/", homeFunc) //url就是/,处理请求的这个函数必须在ListenAndServe之前
	http.ListenAndServe("127.0.0.1:8000", nil)
}

func homeFunc(w http.ResponseWriter, r *http.Request) {  //这就是处理/这个url的函数
	fmt.Println(r.URL)  //打印整个url
	fmt.Println(ioutil.ReadAll(r.Body))//读取请求中的body部分的内容
	fmt.Println(r.Method)//发起请求的方法
	params := r.URL.Query()//获取url中的参数部分,结果是一个map
	fmt.Println(params.Get("gyy"))//根据map的键获取键对应的值
	fmt.Println(params.Get("cyl"))
	w.Write([]byte("gyy"))//向回复的body中写入数据,一定是byte类型的切片
}

  然后是客户端部分:

客户端发起GET请求有两种方法,一种是直接调用http的GET方法,另一种就是自己构造url和客户端的一些参数

客户端代码:

package main

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

func main() {
	// 1.直接使用GET方法发送请求
	// resp, err := http.Get("http://127.0.0.1:8000/?gyy=1&cyl=2&abc=3")
	// if err != nil {
	// 	fmt.Println(err)
	// 	return
	// }
     // 2.自己构造url和客户端的一些参数 paramValues := url.Values{} //url.Values的类型是map,使用大括号初始化一个map paramValues.Set("gyy", "1") paramValues.Set("cyl", "2") paramValues.Set("abc", "好好学习") encodeParamsValues := paramValues.Encode() //将参数编码,例如参数中有汉字,那么汉字就会被编码,例如将好好学习编码为%E5%A5%BD%E5%A5%BD%E5%AD%A6%E4%B9%A0 fmt.Println(encodeParamsValues) newURL, _ := url.Parse("http://127.0.0.1:8000/") //创建一个包含ip端口的url newURL.RawQuery = encodeParamsValues //将参数传递给这个url req, _ := http.NewRequest("GET", newURL.String(), nil) //构造一个新的http请求 resp, _ := http.DefaultClient.Do(req) //使用默认的客户端发起一个http请求,使用的是默认的客户端 b, err := ioutil.ReadAll(resp.Body) defer resp.Body.Close() if err != nil { fmt.Println(err) return } fmt.Println(string(b)) }

  

posted @ 2020-06-01 12:25  胖胖咩  阅读(239)  评论(0)    收藏  举报