前言
Hi,大家好呀,我是码农,星期八,我们身处21世纪,我们的世界已经在不知不觉中,就像很多网一样在互联互通。
互联网是一个统称,目前比较常用的有TCP
,UDP
协议。
当然,还有很多其他的协议,但是本次主要讲最常用的TCP
和UDP
协议。
socker编程
我们所学的TCP
和UDP
,统称为Socker
编程,也叫做套接字编程。
多台机器要实现互相通讯,其实是一个非常复杂的过程,底层从铺设网线,网线接口,交换机,路由器,在到规定各种协议。
再到应用层QQ
,微信
等软件。
如果没有一套标准,每次使用都要自己去实现,可能每个程序员都不是掉头发那么简单了!
有了Socker
之后,Socker
会在应用层之前,将各种繁琐的的底层操作隐藏,我们可能只需要Socker.TCP
就实现了TCP
协议的通讯。
Go语言TCP
TCP属于稳定的,可靠的长连接,
既然要涉及通讯,必然有两个终端,最起码一个是服务端,一个是客户端,就像我们的淘宝,我们每次打开淘宝,都要去链接它,当然,淘宝可不直接是TCP
。
服务端
在Go中实现服务端,并且在服务端并发很简单,只需要将每个连接让一个协程处理即可!
代码
代码语言:javascript复制package main
import (
"bufio"
"fmt"
"net"
)
func process(conn net.Conn) {
defer conn.Close()
for {
reader := bufio.NewReader(conn)
buf := make([]byte, 128)
n, err := reader.Read(buf)
if err != nil {
fmt.Println("数据读取失败", err)
return
}
recvStr := string(buf[:n])
fmt.Println("客户端发送过来的值:", recvStr)
}
}
func main() {
lister, err := net.Listen("tcp", "0.0.0.0:8008")
if err != nil {
fmt.Println("连接失败", err)
}
for {
fmt.Println("等待建立连接,此时会阻塞住")
conn, err := lister.Accept() //等待建立连接
fmt.Println("连接建立成功,继续")
if err != nil {
fmt.Println("建立连接失败", err)
//继续监听下次链接
continue
}
go process(conn)
}
}
客户端
客户端就很简单了,相对来说是不需要并发的,只需要连接就行。
代码
代码语言:javascript复制package main
import (
"bufio"
"fmt"
"net"
"os"
)
//客户端
func main() {
conn, err := net.Dial("tcp", "192.168.10.148:8008")
if err != nil {
fmt.Println("连接服务器失败",err)
}
defer conn.Close()
inputReader:=bufio.NewReader(os.Stdin)
for{
fmt.Println(":")
input,_:=inputReader.ReadString('n')
_, err = conn.Write([]byte(input))
if err != nil {
fmt.Println("发送成功")
}
}
}
执行结果
就这样,我们实现了服务端并发的处理所有客户端的请求。
粘包
我们先看一下什么是粘包。
服务端
代码语言:javascript复制package main
import (
"bufio"
"fmt"
"io"
"net"
)
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
buf := make([]byte, 1024)
for {
n, err := reader.Read(buf)
//读完了
if err == io.EOF {
fmt.Println("读完了")
break
}
//读错了
if err != nil {
fmt.Println("数据读取失败", err)
return
}
recvStr := string(buf[:n])
fmt.Println("客户端发送过来的值:", recvStr)
}
}
func main() {
lister, err := net.Listen("tcp", "0.0.0.0:8008")
if err != nil {
fmt.Println("连接失败", err)
return
}
defer lister.Close()
for {
fmt.Println("等待建立连接,此时会阻塞住")
conn, err := lister.Accept() //等待建立连接
fmt.Println("连接建立成功,继续")
if err != nil {
fmt.Println("建立连接失败", err)
//继续监听下次链接
continue
}
go process(conn)
}
}
客户端
代码语言:javascript复制package main
import (
"fmt"
"net"
)
//客户端
func main() {
conn, err := net.Dial("tcp", "192.168.10.148:8008")
if err != nil {
fmt.Println("连接服务器失败", err)
}
defer conn.Close()
for i := 0; i < 10; i {
sendStr := "hello world ads asdf asd fads fadsf ads ads asd asd ads "
conn.Write([]byte(sendStr))
time.Sleep(time.Second)
}
}
注意:18行代码睡眠了1s
执行结果
如果我注释了第18行代码
执行结果
直接都淦到一行了,what?这是啥情况,不应该跟原来一样吗???
每发送一个值,那边就接收一下,这怎么整到一块了!!!
原因
主要原因是因为我们是应用层软件,是跑在操作系统之上的软件,当我们向服务器发送一个数据时,是调用操作系统的相关接口发送的,操作系统再经过各种复杂的操作,发送到对方机器
但是操作系统有一个发送数据缓冲区,默认情况如果缓冲区是有大小的,如果缓冲区没满,是不会发送数据的,所以上述客户端在发送数据时,系统的缓冲区都没满,一直压在了操作系统的缓冲区中,最后发现没数据了,才一次都发送到服务端
但是为什么sleep(1)
又管用了呢?这是因为缓冲区不止一个程序在用,1s的时间足够其他程序将缓冲区打满,然后各自发各自的数据,这也是为什么第一次操作没问题,第二次有问题,因为第二次全部都是我们客户端打满的
解决粘包
工具函数
我们将解包封包的函数封装一下
socker_sitck/stick.go
package socker_stick
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
)
//Encode 将消息编码
func Encode(message string) ([]byte, error) {
length := int32(len(message))
var pkg = new(bytes.Buffer)
//写入消息头
err := binary.Write(pkg, binary.LittleEndian, length)
if err != nil {
fmt.Println("写入消息头失败", err)
return nil, err
}
//写入消息实体
err = binary.Write(pkg, binary.LittleEndian, []byte(message))
if err != nil {
fmt.Println("写入消息实体失败", err)
return nil, err
}
return pkg.Bytes(), nil
}
//Decode解码消息
func Decode(reader *bufio.Reader) (string, error) {
//读取信息长度
lengthByte, _ := reader.Peek(4)
lengthBuff := bytes.NewBuffer(lengthByte)
var length int32
err := binary.Read(lengthBuff, binary.LittleEndian, &length)
if err != nil {
return "", err
}
//BuffRead 返回缓冲区现有的可读的字节数
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
}
服务端
代码语言:javascript复制package main
import (
"a3_course/socker_stick"
"bufio"
"fmt"
"io"
"net"
)
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for {
msg, err := socker_stick.Decode(reader)
//读完了
if err == io.EOF {
fmt.Println("读完了")
break
}
//读错了
if err != nil {
fmt.Println("数据读取失败", err)
return
}
fmt.Println("客户端发送过来的值:", msg)
}
}
func main() {
lister, err := net.Listen("tcp", "0.0.0.0:8008")
if err != nil {
fmt.Println("连接失败", err)
return
}
defer lister.Close()
for {
fmt.Println("等待建立连接,此时会阻塞住")
conn, err := lister.Accept() //等待建立连接
fmt.Println("连接建立成功,继续")
if err != nil {
fmt.Println("建立连接失败", err)
//继续监听下次链接
continue
}
go process(conn)
}
}
客户端
代码语言:javascript复制package main
import (
"a3_course/socker_stick"
"fmt"
"net"
)
//客户端
func main() {
conn, err := net.Dial("tcp", "192.168.10.148:8008")
if err != nil {
fmt.Println("连接服务器失败", err)
}
defer conn.Close()
for i := 0; i < 10; i {
sendStr := "hello world ads asdf asd fads fadsf ads ads asd asd ads "
data, err := socker_stick.Encode(sendStr)
if err != nil {
fmt.Println("编码失败",err)
return
}
conn.Write(data)
//time.Sleep(time.Second)
}
}
执行结果
这次真的不管执行几次,都是这样的结果
对了,只有TCP
才有粘包
Go语言UDP
UDP
是一个无连接协议,客户端不会在乎服务端有没有问题,客户端只管发,通常用于实时性比较高的领域
例如直播行业
服务端
代码语言:javascript复制package main
import (
"fmt"
"net"
)
func main() {
listen, err := net.ListenUDP("udp", &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 8009,
})
if err != nil {
panic(fmt.Sprintf("udp启动失败,err:%v", err))
}
defer listen.Close()
for{
var data = make([]byte,1024)
n, addr, err := listen.ReadFromUDP(data)
if err != nil {
panic(fmt.Sprintf("读取数据失败,err:%v", err))
}
fmt.Println(string(data[:n]),addr,n)
}
}
客户端
代码语言:javascript复制package main
import (
"fmt"
"net"
)
func main() {
socker, err := net.DialUDP("udp", nil, &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 8009,
})
if err != nil {
panic(fmt.Sprintf("连接服务器失败,err:%v", err))
}
defer socker.Close()
sendStr:="你好呀"
_, err = socker.Write([]byte(sendStr))
if err != nil {
panic(fmt.Sprintf("数据发送失败,err:%v", err))
}
}
执行结果
总结
本次章节我们讲述了什么是TCP,什么是UDP。
并且编写了代码如何实现TCP服务端,TCP客户端,UDP服务端,UDP客户端。
讲述了为什么会出现粘包,该怎么解决粘包。
逆水行舟,不进则退!
如果在操作过程中有任何问题,记得下面留言,我们看到会第一时间解决问题。
我是码农星期八,如果觉得还不错,记得动手点赞一下哈。
感谢你的观看。
如果你觉得文章还可以,记得点赞留言支持我们哈。感谢你的阅读,有问题请记得在下方留言噢~
想学习更多关于Go的知识,可以参考学习网址:http://pdcfighting.com/,点击阅读原文,可以直达噢~