⼀、使用Golang创建一个TCP连接
1.tcp服务端处理理流程
a.监听端口
b.接受客户端的链接
c.创建Goroutine,处理这个链接
package main
import (
"fmt"
"net"
)
func main() {
//1.建立监听端口
listen, err := net.Listen("tcp", "0.0.0.0:20000")
if err != nil {
fmt.Println("listen failed, err:", err)
return
}
fmt.Println("listen Start...:")
for {
//2.接收客户端的链接
conn, err := listen.Accept()
if err != nil {
fmt.Printf("accept failed, err:%v\n", err)
continue
}
//3.开启一个Goroutine,处理链接
go process(conn)
}
}
//处理请求,类型就是net.Conn
func process(conn net.Conn) {
//处理结束后关闭链接
defer conn.Close()
for {
var buf [128]byte
n, err := conn.Read(buf[:])
if err != nil {
fmt.Printf("read from conn failed, err:%v", err)
break
}
fmt.Printf("recv from client, content:%v\n", string(buf[:n]))
}
}
2.tcp客户端处理流程
a.和服务端建立一个链接
b.进行数据的收发
c.关闭链接
package main
import (
"bufio"
"fmt"
"net"
"os"
"strings"
)
func main() {
//1.建立一个链接(Dial拨号)
conn, err := net.Dial("tcp", "0.0.0.0:20000")
if err != nil {
fmt.Printf("dial failed, err:%v\n", err)
return
}
fmt.Println("Conn Established...:")
//读入输入的信息
reader := bufio.NewReader(os.Stdin)
for {
data, err := reader.ReadString('\n')
if err != nil {
fmt.Printf("read from console failed, err:%v\n", err)
break
}
data = strings.TrimSpace(data)
//传输数据到服务端
_, err = conn.Write([]byte(data))
if err != nil {
fmt.Printf("write failed, err:%v\n", err)
break
}
}
}
二、使用Golang创建一个UDP连接
UDP协议(User Datagram Protocol)中文名称是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠的、没有时序的通信,但是UDP协议的实时性比较好,通常用于视频直播相关领域。
1.udp服务端处理理流程
package main
import (
"fmt"
"net"
)
func main() {
listen,err := net.ListenUDP("udp",&net.UDPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 9000,
})
if err != nil {
fmt.Println("conn failed",err)
return
}
for {
//接受数据
var buf []byte
n,addr,err := listen.ReadFromUDP(buf[:])
if err != nil {
fmt.Printf("接受数据失败,err is %v",err)
return
}
fmt.Println("接受的数据为%v:%v\n",addr,string(buf[:n]))
//回复数据
nn,err = listen.WriteToUDP([]byte("好的"),addr)
if err != nil {
fmt.Printf("回复数据失败,err is %v",err)
return
}
fmt.
}
}
2.udp客户端处理流程
package main
import (
"fmt"
"net"
)
func main() {
conn,err := net.Dial("udp","127.0.0.1:9000")
if err != nil{
fmt.Println("连接失败",err)
return
}
fmt.Println("连接成功")
//发送数据
n,err :=conn.Write([]byte("约吗"))
if err != nil {
fmt.Println("发送数据失败",err)
return
}
//接受数据
var buf []byte
n,err := conn.Read(buf[:])
if err != nil {
fmt.Println("接受数据失败",err)
return
}
fmt.Println("接受的数据为%v",string(buf[:n]))
}
三、TCP粘包
主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。
“粘包”可发生在发送端也可发生在接收端:
- 由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
- 接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。
解决办法
出现”粘包”的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。
封包:封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入”包尾”内容)。包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。
我们可以自己定义一个协议,比如数据包的前4个字节为包头,里面存储的是发送的数据的长度。 ```go // socket_stick/proto/proto.go package proto
import ( “bufio” “bytes” “encoding/binary” )
// 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
} ```