一、说明
1.1、什么是socket
Socket起源于Unix,而Unix基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。
1.2、socket种类
Socket有两种:TCP Socket和UDP Socket,TCP和UDP是协议,而要确定一个进程的需要三元组,需要IP地址、协议和端口,具体看下面示例部分。
二、实战
2.1、tcp socket实战
(1) tcp socket 服务端实现
package main import ( "bufio" "fmt" "net" ) func process(conn net.Conn) { // 5. 定义defer关闭连接 defer conn.Close() // 6.无限循环 for { // 7.创建一个带缓存的读读取对象,读取tcp连接里的内容 reader := bufio.NewReader(conn) // 8.定义一个128字节的数组 var buf [128]byte // 9.调用从当前tcp连接里读取内容的对象的Read方法将从tcp里读取的数据存入buf[:]切片里,有两个返回值,一个是读取数据总数,另一个是报错 n, err := reader.Read(buf[:]) if err != nil { // 处理报错 fmt.Println("read data error:", err) break } // 10.打印切片里的内容(下标从0到读取的总数) fmt.Printf(string(buf[0:n])) // 11.回复客户端消息(发送的消息一定要是byte类型的切片) conn.Write([]byte("Welcome to Golang")) } } func main() { // 1.调用net包下的Listen()方法启动tcp的端口监听 listen, err := net.Listen("tcp", "0.0.0.0:8088") if err != nil { // 判断是否有错误 fmt.Println("listen failed error", err) return } // 2.无限循环,处理请求 for { // 3.等待链接(没有客户端连接时在这里阻塞,有客户端连接时处理连接) conn, err := listen.Accept() if err != nil { // 判断错误(打印错误,退出当前循环进入下次循环) fmt.Println("accept failed err", err) continue } // 4.启动一个携程处理连接 go process(conn) // 5.启用一个goroutine处理当前的连接 } }
(2) tcp socket 客户端实现
package main import ( "fmt" "net" "bufio" "os" "strings" ) func main() { // 1.调用net.Dial()方法以tcp协议的方式连接localhost的8088端口 conn, err := net.Dial("tcp","localhost:8088") if err != nil { // 判断错误 fmt.Println("Error dialing",err.Error()) return } // 2.定义defer,在结束时关闭连接 defer conn.Close() // 3.从标准输入里创建一个读的对象 inputReader := bufio.NewReader(os.Stdin) // 4.无限循环 for { // 5.调用读的对象的ReadString方法判断,如果有'\n'(也就是换行符)就认为输入结束了,返回两个值,一个是字符串,另一个是报错 input, _ := inputReader.ReadString('\n') // 6.利用strings.Trim方法给要发送的数据去掉空格 trimmedInput := strings.Trim(input,"\r\n") // 7.如果输入内容是q就退出了 if trimmedInput == "Q" { return } // 8.发送数据,发送的数据必须要是byte类型的切片,返回值是发送的总数据和错误 _, err = conn.Write([]byte(trimmedInput)) if err != nil { // 错误处理 return } // 9.读取服务端发来的消息 var buf [1024]byte n, err := conn.Read(buf[:]) // 获取到一个读取的总数量和错误 if err != nil { fmt.Println("read server data err", err) return } // 10.打印服务端返回的消息 fmt.Println(string(buf[:n])) } }
2.2、udp socket实战
(1)udp socket 服务端实现
package main import ( "net" "fmt" ) func main() { // 1.使用net.ListenUDP()启动一个UDP的监听 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 } // 2.使用defer关闭连接 defer listen.Close() // 3.无限循环 for { // 4.定义存储从连接里读取消息的变量 var data [1024]byte // 5.从udp连接里读取消息到data变量里,返回值有:接收的数据总数量,谁发过来的数据,错误 n, addr, err := listen.ReadFromUDP(data[:]) if err != nil { // 错误处理 fmt.Println("read udp failed, err:", err) continue } // 6.打印接收到的消息、发消息的地址和消息长度 fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n) // 7.给客户端返回消息,谁发过来的消息就返回给谁,接收到的消息时什么就返回什么消息 _, err = listen.WriteToUDP(data[:n], addr) if err != nil { // 错误处理 fmt.Println("write to udp failed, err:", err) continue } } }
(2)udp socket 客户端实现
package main import ( "net" "fmt" "os" "bufio" ) func main() { // 1.调用net.DialUDP()方法以udp协议连接到0.0.0.0:30000地址 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 } // 2.定义defer关闭连接 defer socket.Close() // 3.无限循环 for { // 4.实例化从终端输入对象 input := bufio.NewReader(os.Stdin) // 5.没有输入内容时会等待,输入的内容遇到'\n'就认为输入结束,进入下一次循环。 s, _ := input.ReadString('\n') // 6.发送数据,发送的数据必须转为byte类型的切片 _, err = socket.Write([]byte(s)) if err != nil { // 判断错误 fmt.Println("发送数据失败,err:", err) return } // 7.定义存储服务端返回消息的变量 data := make([]byte, 4096) // 8.接收服务端返回的消息存储到变量data里,返回值有:接收数据的总大小,谁发来的,错误 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) } }
2.3、tcp客户端请求百度实现
package main import ( "fmt" "net" "io" ) func main() { conn, err := net.Dial("tcp","www.baidu.com:80") if err != nil { fmt.Println("Error dialing",err.Error()) return } defer conn.Close() msg := "GET / HTTP/1.1\r\n" msg += "Host: www.baidu.com\r\n" msg += "Connection: close\r\n" msg += "\r\n\r\n" _, err = io.WriteString(conn, msg) if err != nil { fmt.Println("write string failed",err) return } buf := make([]byte,4096) for { count, err := conn.Read(buf) if err != nil { break } fmt.Println(string(buf[0:count])) } }
三、tcp secket粘包问题解决
说明:
一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据。TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消息数据,所以就会引发一次接收的数据无法满足消息的需要,导致粘包的存在。处理粘包的唯一方法就是制定应用层的数据通讯协议,通过协议来规范现有接收的数据是否满足消息数据的需要。在应用中处理粘包的基础方法主要有两种分别是以4节字描述消息大小或以结束符,实际上也有两者相结合的如HTTP,redis的通讯协议等。
示例:
1、服务端实现
package main import ( "bufio" "fmt" "encoding/binary" "io" "net" "bytes" ) func Decode(reader *bufio.Reader) (string, error) { // 9.读取接收到消息的前四个字节获取本次接收的消息长度,Peek读取消息时不移动读取位置 lengthByte, _ := reader.Peek(4) // 10.bytes.NewBuffer:从一个切片构造一个buffer(缓冲区) lengthBuff := bytes.NewBuffer(lengthByte) // 11.定义一个int32类型的变量存储要读取消息的长度 var length int32 // 12.读取lengthBuff变量里的数据存到length变量里,此时length的值是要从连接里读取数据的长度 err := binary.Read(lengthBuff, binary.LittleEndian, &length) if err != nil { // 错误处理 return "", err } // 13.如果缓冲里的数据没有要读取的数据长就返回错误信息 if int32(reader.Buffered()) < length+4 { return "", err } // 14.定义一个要读取数据长度+4的byte类型切片变量 pack := make([]byte, int(4+length)) // 15.将从连接里读取的消息存入pack变量里(即每次从连接里读取 “int(4+length)”长度的数据) _, err = reader.Read(pack) if err != nil { // 错误处理 return "", err } // 16.返回从连接里读取的数据(前四个是读取消息的长度(不返回),只返回下标4之后的内容) return string(pack[4:]), nil } func process(conn net.Conn) { // 5.定义defer关闭连接 defer conn.Close() // 6.实例化一个从当前连接里读取消息的对象 reader := bufio.NewReader(conn) // 7.无限循环 for { // 8.将读取消息的对象给Decode函数解码,返回值是从连接里读取到的消息和错误提示 msg, err := Decode(reader) // 17.判断错误,读取到结尾错误和其他错误 if err == io.EOF { return } if err != nil { fmt.Println("decode msg failed, err:", err) return } // 18.最终将客户端发来的消息打印出来 fmt.Println("收到client发来的数据:", msg) } } func main() { // 1.启动 tcp协议的监听 listen, err := net.Listen("tcp", "127.0.0.1:30000") if err != nil { // 错误判断 fmt.Println("listen failed, err:", err) return } defer listen.Close() // 定义defer关闭连接 // 2.无限循环 for { // 3.等待客户端连接(无连接时会阻塞,有连接时自动处理连接) conn, err := listen.Accept() if err != nil { // 错误处理 fmt.Println("accept failed, err:", err) continue } // 4.启动一个携程,将连接传给携程处理 go process(conn) } }
2、客户端实现
package main import ( "fmt" "net" ) func Encode(message string) ([]byte, error) { // 6.读取消息的长度,转换成int32类型(占4个字节) var length = int32(len(message)) // 7.定义缓冲区变量 var pkg = new(bytes.Buffer) // 8.往缓冲区变量里写入消息的长度(binary.LittleEndian:按照小端写入的方式写入(百度搜大小端读写了解相关内容)) err := binary.Write(pkg, binary.LittleEndian, length) if err != nil { // 错误处理 return nil, err } // 9.往缓冲区变量里写入消息实体 err = binary.Write(pkg, binary.LittleEndian, []byte(message)) if err != nil { // 错误处理 return nil, err } // 10.返回缓冲区里的所有字节和错误信息 return pkg.Bytes(), nil } func main() { // 1.建立拨号连接连接服务端 conn, err := net.Dial("tcp", "127.0.0.1:30000") if err != nil { // 错误处理 fmt.Println("dial failed, err", err) return } // 2.定义defer关闭连接 defer conn.Close() // 3.循环20次 for i := 0; i < 20; i++ { // 4.定义要发送消息内容 msg := `Hello, Hello. How are you?` // 5.将要发送的消息传给Encode函数进行编码 data, err := Encode(msg) if err != nil { // 错误处理 fmt.Println("encode msg failed, err:", err) return } // 11.往连接里写入编码后的数据 conn.Write(data) } }