socket编程
Socket是BSD UNIX的进程通信机制,通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄。Socket可以理解为TCP/IP网络的API,它定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。电脑上运行的应用程序通常通过”套接字”向网络发出请求或者应答网络请求。
socket图解
Socket
是应用层与TCP/IP协议族通信的中间软件抽象层。在设计模式中,Socket
其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket
后面,对用户来说只需要调用Socket规定的相关函数,让Socket
去组织符合指定的协议数据然后进行通信。
Go语言实现TCP通信
1、TCP协议
TCP/IP(Transmission Control Protocol/Internet Protocol) 即传输控制协议/网间协议,是一种面向连接(连接导向)的、可靠的、基于字节流的传输层(Transport layer)通信协议,因为是面向连接的协议,数据像水流一样传输,会存在黏包问题。
2、TCP服务端
一个TCP服务端可以同时连接很多个客户端,例如世界各地的用户使用自己电脑上的浏览器访问淘宝网。因为Go语言中创建多个goroutine实现并发非常方便和高效,所以我们可以每建立一次链接就创建一个goroutine去处理。
TCP服务端程序的处理流程:
监听端口
接收客户端请求建立链接
创建goroutine处理链接。
我们使用Go语言的net包实现的TCP服务端代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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 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" ) 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) } }
将上面的代码保存之后编译成server
或server.exe
可执行文件。
3、TCP客户端
一个TCP客户端进行TCP通信的流程如下:
建立与服务端的链接
进行数据收发
关闭链接
使用Go语言的net包实现的TCP客户端代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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" { 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])) } }
将上面的代码编译成client
或client.exe
可执行文件,先启动server端再启动client端,在client端输入任意内容回车之后就能够在server端看到client端发送的数据,从而实现TCP通信。
4、TCP黏包
为什么会出现粘包?
主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。
“粘包”可发生在发送端也可发生在接收端:
由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。
【解决办法】
出现”粘包”的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作 。
封包 :封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入”包尾”内容)。包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。
Go语言实现UDP通信
1、UDP协议
UDP协议(User Datagram Protocol)中文名称是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接 的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠的、没有时序的通信,但是UDP协议的实时性比较好,通常用于视频直播相关领域。
2、UDP服务端
使用Go语言的net
包实现的UDP服务端代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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客户端
使用Go语言的net
包实现的UDP客户端代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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) }