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))
}
浙公网安备 33010602011771号