Socket是BSD UNIX的进程通讯机制,一般也称做”套接字”,用于描述IP地址和端口,是一个通讯链的句柄。Socket能够理解为TCP/IP网络的API,它定义了许多函数或例程,程序员能够用它们来开发TCP/IP网络上的应用程序。电脑上运行的应用程序一般经过”套接字”向网络发出请求或者应答网络请求。
Socket是应用层与TCP/IP协议族通讯的中间软件抽象层。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket后面,对用户来讲只须要调用Socket规定的相关函数,让Socket去组织符合指定的协议数据而后进行通讯。html
TCP/IP(Transmission Control Protocol/Internet Protocol) 即传输控制协议/网间协议,是一种面向链接(链接导向)的、可靠的、基于字节流的传输层(Transport layer)通讯协议,由于是面向链接的协议,数据像水流同样传输,会存在黏包问题。程序员
一个TCP服务端能够同时链接不少个客户端,Go语言中建立多个goroutine实现并发很是方便和高效,因此能够每创建一次连接就建立一个goroutine去处理。
TCP服务端程序的处理流程:算法
//TCP server端 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("链接客户端失败,错误信息:",err) } recvStr := string(buf[:n]) fmt.Println("收到客户端信息:",recvStr) conn.Write([]byte(recvStr)) //发送数据 } } func main() { listen,err := net.Listen("tcp","127.0.0.1:8888") if err != nil{ fmt.Println("监听失败,错误:",err) return } for { conn,err := listen.Accept() //创建链接 if err!= nil{ fmt.Println("创建链接失败,错误:",err) continue } go process(conn) //启动一个goroutine处理链接 } }
一个TCP客户端进行TCP通讯的流程以下:编程
TCP客户端:设计模式
//客户端 func main() { conn ,err := net.Dial("tcp","127.0.0.1:8888") if err != nil { fmt.Println("链接失败,错误:",err) return } defer conn.Close() inputReader := bufio.NewReader(os.Stdout) for { input, _ := inputReader.ReadString('\n') //读取用户输入 inputInfo := strings.Trim(input,"\r\n") if strings.ToUpper(inputInfo) == "q"{ return //若是输入q就退出 } _,err = conn.Write([]byte(inputInfo)) //发送数据 if err != nil{ return } buf := [512]byte{} n,err := conn.Read(buf[:]) if err != nil{ fmt.Println("接受失败,错误:",err) return } fmt.Println(string(buf[:n])) } }
先启动server,后启动client:服务器
$go run main.go 我是客户端 我是客户端 $go run main.go 收到客户端信息: 我是客户端
UDP协议(User Datagram Protocol)中文名称是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无链接的传输层协议,不须要创建链接就能直接进行数据发送和接收,属于不可靠的、没有时序的通讯,可是UDP协议的实时性比较好,一般用于视频直播相关领域。网络
//服务端 func main() { listen,err := net.ListenUDP("udp",&net.UDPAddr{ IP:net.IPv4(0,0,0,0), Port:8888, }) if err != nil{ fmt.Println("监听失败,错误:",err) return } defer listen.Close() for { var data [1024]byte n,addr,err := listen.ReadFromUDP(data[:]) if err != nil{ fmt.Println("接收udp数据失败,错误:",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("发送数据失败,错误:",err) continue } } }
//客户端 func main() { socket,err := net.DialUDP("udp",nil,&net.UDPAddr{ IP:net.IPv4(0,0,0,0), Port:8888, }) if err != nil{ fmt.Println("链接服务器失败,错误:",err) return } defer socket.Close() sendData := []byte("hello world!") _,err = socket.Write(sendData) if err != nil{ fmt.Println("发送数据失败,错误:",err) return } data := make([]byte,4096) n,remoteAddr,err := socket.ReadFromUDP(data) if err != nil{ fmt.Println("接受数据失败,错误:",err) return } fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n) }
先启动server,后启动client:并发
$go run main.go recv:hello world! addr:127.0.0.1:8888 count:12 $go run main.go data:hello world! addr:127.0.0.1:51222 count:12
超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为普遍的一种网络传输协议,全部的WWW文件都必须遵照这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。socket
net/http包是对net包的进一步封装,专门用来处理HTTP协议的数据。tcp
// http server func sayHi(w http.ResponseWriter,r *http.Request) { fmt.Fprintln(w,"你好,ares!") } func main() { http.HandleFunc("/",sayHi) err := http.ListenAndServe(":8888",nil) if err != nil{ fmt.Println("Http 服务创建失败,err:",err) return } }

func main() { resp, err := http.Get("https://www.baidu.com/") if err != nil { fmt.Println("get failed, err:", err) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) fmt.Printf("%T\n",body) fmt.Println(string(body)) }
执行以后就能在终端输出www.baidu.com网站首页的内容了。
//粘包 func process(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) var buf [1024]byte for { n,err := reader.Read(buf[:]) if err == io.EOF{ break } if err != nil{ fmt.Println("读取客户端失败,err",err) break } recvStr := string(buf[:n]) fmt.Println("收到client发来的数据:",recvStr) } } func main() { listen,err := net.Listen("tcp","127.0.0.1:8888") if err != nil{ fmt.Println("监听失败,err",err) return } defer listen.Close() for { conn,err := listen.Accept() if err != nil{ fmt.Println("接受失败,err",err) continue } go process(conn) } }
func main() { conn,err := net.Dial("tcp","127.0.0.1:8888") if err != nil{ fmt.Println("链接失败,err",err) return } defer conn.Close() for i:=0;i<20;i++{ msg := "Ares is a bird!" conn.Write([]byte(msg)) } }
先启动服务端再启动客户端,能够看到服务端输出结果以下:
$go run main.go 收到client发来的数据: Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird! 收到client发来的数据: Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!
客户端分10次发送的数据,在服务端并无成功的输出10次,而是多条数据“粘”到了一块儿。
在socket网络程序中,TCP和UDP分别是面向链接和非面向链接的。所以TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,所以,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将屡次间隔较小、数据量小的数据,合并成一个大的数据块,而后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。
对于UDP,不会使用块的合并优化算法,这样,实际上目前认为,是因为UDP支持的是一对多的模式,因此接收端的skbuff(套接字缓冲区)采用了链式结构来记录每个到达的UDP包,在每一个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来讲,就容易进行区分处理了。因此UDP不会出现粘包问题。
1发送端须要等缓冲区满才发送出去,形成粘包
2接收方不及时接收缓冲区的包,形成多个包接收
具体点:
(1)发送方引发的粘包是由TCP协议自己形成的,TCP为提升传输效率,发送方每每要收集到足够多的数据后才发送一包数据。若连续几回发送的数据都不多,一般TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。
(2)接收方引发的粘包是因为接收方用户进程不及时接收数据,从而致使粘包现象。这是由于接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据还没有被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据以后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。
参考:TCP通讯粘包问题分析和解决
出现”粘包”的关键在于接收方不肯定将要传输的数据包的大小,所以咱们能够对数据包进行封包和拆包的操做。
自定义一个协议,好比数据包的前4个字节为包头,里面存储的是发送的数据的长度。
// Encode 将消息编码 func Encode(message string)([]byte ,error) { // 读取消息的长度,转换成int32类型(占4个字节) var length = int32(len(message)) var pkg = new(bytes.Buffer) //写入消息头 err := binary.Write(pkg,binary.LittleEndian,length) if err != nil{ return nil,err } //写入消息实体 err = binary.Write(pkg,binary.LittleEndian,[]byte(message)) if err != nil{ return nil,err } return pkg.Bytes(),nil } // Decode 消息解码 func Decode(reader *bufio.Reader)(string,error) { //读取消息长度 lengthByte,_ := reader.Peek(4) //读取前4个字节数据 lengthBuff := bytes.NewBuffer(lengthByte) var length int32 err := binary.Read(lengthBuff,binary.LittleEndian,&length) if err != nil{ return "",err } // Buffered返回缓冲中现有的可读取的字节数。 if int32(reader.Buffered()) < length+4{ return "",err } //读取真正的消息数据 pack := make([]byte,int(4+length)) _,err = reader.Read(pack) if err != nil{ return "",err } return string(pack[4:]),nil }
server端:
func process(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) for { msg,err := proto.Decode(reader) if err == io.EOF{ return } if err != nil{ fmt.Println("decode 失败,err",err) return } fmt.Println("收到client数据:",msg) } } func main() { listen,err := net.Listen("tcp","127.0.0.1:8888") if err != nil{ fmt.Println("监听失败,err",err) return } defer listen.Close() for { conn,err := listen.Accept() if err != nil{ fmt.Println("接受失败,err",err) continue } go process(conn) } }
client端:
func main() { conn,err := net.Dial("tcp","127.0.0.1:8888") if err != nil{ fmt.Println("dial失败,err",err) return } defer conn.Close() for i:=0;i<20;i++{ msg := "Hello Ares!" data,err := proto.Encode(msg) if err != nil{ fmt.Println("encode失败,err",err) return } conn.Write(data) } }
先启动服务端再启动客户端,能够看到服务端输出结果以下:
go run main.go 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares! 收到client数据: Hello Ares!