TCP网络编程#
存在的问题:
- 拆包:
- 对发送端来说应用程序写入的数据远大于socket缓冲区大小,不能一次性将这些数据发送到server端就会出现拆包的情况。
- 通过网络传输的数据包最大是1500字节,当
TCP报文的长度 - TCP头部的长度 > MSS(最大报文长度时)
将会发生拆包,MSS一般长(1460~1480)字节。
- 粘包:
- 对发送端来说:应用程序发送的数据很小,远小于socket的缓冲区的大小,导致一个数据包里面有很多不通请求的数据。
- 对接收端来说:接收数据的方法不能及时的读取socket缓冲区中的数据,导致缓冲区中积压了不同请求的数据。
解决方法:
- 使用带消息头的协议,在消息头中记录数据的长度。
- 使用定长的协议,每次读取定长的内容,不够的使用空格补齐。
- 使用消息边界,比如使用 \n 分隔 不同的消息。
- 使用诸如 xml json protobuf这种复杂的协议。
实验:使用自定义协议
整体的流程:
客户端:发送端连接服务器,将要发送的数据通过编码器编码,发送。
服务端:启动、监听端口、接收连接、将连接放在协程中处理、通过解码器解码数据。
//########################### //###### Server端代码 ###### //########################### func main() { // 1. 监听端口 2.accept连接 3.开goroutine处理连接 listen, err := net.Listen("tcp", "0.0.0.0:9090") if err != nil { fmt.Printf("error : %v", err) return } for{ conn, err := listen.Accept() if err != nil { fmt.Printf("Fail listen.Accept : %v", err) continue } go ProcessConn(conn) } } // 处理网络请求 func ProcessConn(conn net.Conn) { defer conn.Close() for { bt,err:=coder.Decode(conn) if err != nil { fmt.Printf("Fail to decode error [%v]", err) return } s := string(bt) fmt.Printf("Read from conn:[%v]\n",s) } } //########################### //###### Clinet端代码 ###### //########################### func main() { conn, err := net.Dial("tcp", ":9090") defer conn.Close() if err != nil { fmt.Printf("error : %v", err) return } // 将数据编码并发送出去 coder.Encode(conn,"hi server i am here"); } //########################### //###### 编解码器代码 ###### //########################### /** * 解码: */ func Decode(reader io.Reader) (bytes []byte, err error) { // 先把消息头读出来 headerBuf := make([]byte, len(msgHeader)) if _, err = io.ReadFull(reader, headerBuf); err != nil { fmt.Printf("Fail to read header from conn error:[%v]", err) return nil, err } // 检验消息头 if string(headerBuf) != msgHeader { err = errors.New("msgHeader error") return nil, err } // 读取实际内容的长度 lengthBuf := make([]byte, 4) if _, err = io.ReadFull(reader, lengthBuf); err != nil { return nil, err } contentLength := binary.BigEndian.Uint32(lengthBuf) contentBuf := make([]byte, contentLength) // 读出消息体 if _, err := io.ReadFull(reader, contentBuf); err != nil { return nil, err } return contentBuf, err } /** * 编码 * 定义消息的格式: msgHeader + contentLength + content * conn 本身实现了 io.Writer 接口 */ func Encode(conn io.Writer, content string) (err error) { // 写入消息头 if err = binary.Write(conn, binary.BigEndian, []byte(msgHeader)); err != nil { fmt.Printf("Fail to write msgHeader to conn,err:[%v]", err) } // 写入消息体长度 contentLength := int32(len([]byte(content))) if err = binary.Write(conn, binary.BigEndian, contentLength); err != nil { fmt.Printf("Fail to write contentLength to conn,err:[%v]", err) } // 写入消息 if err = binary.Write(conn, binary.BigEndian, []byte(content)); err != nil { fmt.Printf("Fail to write content to conn,err:[%v]", err) } return err
客户端的conn一直不被Close 有什么表现?
四次挥手各个状态的如下:
主从关闭方 被动关闭方 established established Fin-wait1 closeWait Fin-wait2 Tiem-wait lastAck Closed Closed
如果客户端的连接手动的关闭,它和服务端的状态会一直保持established建立连接中的状态。
MacBook-Pro% netstat -aln | grep 9090 tcp4 0 0 127.0.0.1.9090 127.0.0.1.62348 ESTABLISHED tcp4 0 0 127.0.0.1.62348 127.0.0.1.9090 ESTABLISHED tcp46 0 0 *.9090 *.* LISTEN
服务端的conn一直不被关闭 有什么表现?
客户端的进程结束后,会发送fin数据包给服务端,向服务端请求断开连接。
服务端的conn不关闭的话,服务端就会停留在四次挥手的close_wait阶段(我们不手动Close,服务端就任务还有数据/任务没处理完,因此它不关闭)。
客户端停留在 fin_wait2的阶段(在这个阶段等着服务端告诉自己可以真正断开连接的消息)。
MacBook-Pro% netstat -aln | grep 9090 tcp4 0 0 127.0.0.1.9090 127.0.0.1.62888 CLOSE_WAIT tcp4 0 0 127.0.0.1.62888 127.0.0.1.9090 FIN_WAIT_2 tcp46 0 0 *.9090 *.* LISTEN
什么是binary.BigEndian?什么是binary.LittleEndian?
对计算机来说一切都是二进制的数据,BigEndian和LittleEndian描述的就是二进制数据的字节顺序。计算机内部,小端序被广泛应用于现代性 CPU 内部存储数据;大端序常用于网络传输和文件存储。
比如:
一个数的二进制表示为 0x12345678 BigEndian 表示为: 0x12 0x34 0x56 0x78 LittleEndian表示为: 0x78 0x56 0x34 0x12
UDP网络编程#
思路:
UDP服务器:1、监听 2、循环读取消息 3、回复数据。
UDP客户端:1、连接服务器 2、发送消息 3、接收消息。
// ################################ // ######## UDPServer ######### // ################################ func main() { // 1. 监听端口 2.accept连接 3.开goroutine处理连接 listen, err := net.Listen("tcp", "0.0.0.0:9090") if err != nil { fmt.Printf("error : %v", err) return } for{ conn, err := listen.Accept() if err != nil { fmt.Printf("Fail listen.Accept : %v", err) continue } go ProcessConn(conn) } } // 处理网络请求 func ProcessConn(conn net.Conn) { defer conn.Close() for { bt,err:= coder.Decode(conn) if err != nil { fmt.Printf("Fail to decode error [%v]", err) return } s := string(bt) fmt.Printf("Read from conn:[%v]\n",s) } } // ################################ // ######## UDPClient ######### // ################################ func main() { udpConn, err := net.DialUDP("udp", nil, &net.UDPAddr{ IP: net.IPv4(127, 0, 0, 1), Port: 9091, }) if err != nil { fmt.Printf("error : %v", err) return } _, err = udpConn.Write([]byte("i am udp client")) if err != nil { fmt.Printf("error : %v", err) return } bytes:=make([]byte,1024) num, addr, err := udpConn.ReadFromUDP(bytes) if err != nil { fmt.Printf("Fail to read from udp error: [%v]", err) return } fmt.Printf("Recieve from udp address:[%v], bytes:[%v], content:[%v]",addr,num,string(bytes)) }
Http网络编程#
思路整理:
HttpServer:1、创建路由器。2、为路由器绑定路由规则。3、创建服务器、监听端口。 4启动读服务。
HttpClient: 1、创建连接池。2、创建客户端,绑定连接池。3、发送请求。4、读取响应。
func main() { mux := http.NewServeMux() mux.HandleFunc("/login", doLogin) server := &http.Server{ Addr: ":8081", WriteTimeout: time.Second * 2, Handler: mux, } log.Fatal(server.ListenAndServe()) } func doLogin(writer http.ResponseWriter,req *http.Request){ _, err := writer.Write([]byte("do login")) if err != nil { fmt.Printf("error : %v", err) return } }
HttpClient端
func main() { transport := &http.Transport{ // 拨号的上下文 DialContext: (&net.Dialer{ Timeout: 30 * time.Second, // 拨号建立连接时的超时时间 KeepAlive: 30 * time.Second, // 长连接存活的时间 }).DialContext, // 最大空闲连接数 MaxIdleConns: 100, // 超过最大的空闲连接数的连接,经过 IdleConnTimeout时间后会失效 IdleConnTimeout: 10 * time.Second, // https使用了SSL安全证书,TSL是SSL的升级版 // 当我们使用https时,这行配置生效 TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, // 100-continue 状态码超时时间 } // 创建客户端 client := &http.Client{ Timeout: time.Second * 10, //请求超时时间 Transport: transport, } // 请求数据 res, err := client.Get("http://localhost:8081/login") if err != nil { fmt.Printf("error : %v", err) return } defer res.Body.Close() bytes, err := ioutil.ReadAll(res.Body) if err != nil { fmt.Printf("error : %v", err) return } fmt.Printf("Read from http server res:[%v]", string(bytes)) }
理解函数是一等公民#
在golang中函数是一等公民,我们可以把一个函数当作普通变量一样使用。
比如我们有个函数HelloHandle,我们可以直接使用它。
func HelloHandle(name string, age int) { fmt.Printf("name:[%v] age:[%v]", name, age) } func main() { HelloHandle("tom",12) }
闭包
如何理解闭包:闭包本质上是一个函数,而且这个函数会引用它外部的变量,如下例子中的f3中的匿名函数本身就是一个闭包。 通常我们使用闭包起到一个适配的作用。
例1:
// f2是一个普通函数,有两个入参数 func f2() { fmt.Printf("f2222") } // f1函数的入参是一个f2类型的函数 func f1(f2 func()) { f2() } func main() { // 由于golang中函数是一等公民,所以我们可以把f2同普通变量一般传递给f1 f1(f2) }
例2: 在上例中更进一步。f2有了自己的参数, 这时就不能直接把f2传递给f1了。
总不能傻傻的这样吧f1(f2(1,2))
???
而闭包就能解决这个问题。
// f2是一个普通函数,有两个入参数 func f2(x int, y int) { fmt.Println("this is f2 start") fmt.Printf("x: %d y: %d \n", x, y) fmt.Println("this is f2 end") } // f1函数的入参是一个f2类型的函数 func f1(f2 func()) { fmt.Println("this is f1 will call f2") f2() fmt.Println("this is f1 finished call f2") } // 接受一个两个参数的函数, 返回一个包装函数 func f3(f func(int,int) ,x,y int) func() { fun := func() { f(x,y) } return fun } func main() { // 目标是实现如下的传递与调用 f1(f3(f2,6,6)) }
实现方法的回调:
下面的例子中实现这样的功能:就好像是我设计了一个框架,定好了整个框架运转的流程(或者说是提供了一个编程模版),框架具体做事的函数你根据自己的需求自己实现,我的框架只是负责帮你回调你具体的方法。
// 自定义类型,handler本质上是一个函数 type HandlerFunc func(string, int) // 闭包 func (f HandlerFunc) Serve(name string, age int) { f(name, age) } // 具体的处理函数 func HelloHandle(name string, age int) { fmt.Printf("name:[%v] age:[%v]", name, age) } func main() { // 把HelloHandle转换进自定义的func中 handlerFunc := HandlerFunc(HelloHandle) // 本质上会去回调HelloHandle方法 handlerFunc.Serve("tom", 12) // 上面两行效果 == 下面这行 // 只不过上面的代码是我在帮你回调,下面的是你自己主动调用 HelloHandle("tom",12) }