首页 文章详情

一篇文章带你了解Go语言基础之网络编程

Go语言进阶学习 | 223 2022-05-21 15:03 0 0 0
UniSMS (合一短信)
击上方“Python爬虫与数据挖掘”,进行关注

回复“书籍”即可获赠Python从入门到进阶共10本电子书

君不见吴中张翰称达生,秋风忽忆江东行。

前言

Hi,大家好呀,我是码农,星期八,我们身处21世纪,我们的世界已经在不知不觉中,就像很多网一样在互联互通。

互联网是一个统称,目前比较常用的有TCPUDP协议。

当然,还有很多其他的协议,但是本次主要讲最常用的TCPUDP协议。

socker编程

我们所学的TCPUDP,统称为Socker编程,也叫做套接字编程

多台机器要实现互相通讯,其实是一个非常复杂的过程,底层从铺设网线网线接口交换机路由器,在到规定各种协议

再到应用层QQ微信等软件。

如果没有一套标准,每次使用都要自己去实现,可能每个程序员都不是掉头发那么简单了!

有了Socker之后,Socker会在应用层之前,将各种繁琐的的底层操作隐藏,我们可能只需要Socker.TCP就实现了TCP协议的通讯。

Go语言TCP

TCP属于稳定的,可靠的长连接,

既然要涉及通讯,必然有两个终端,最起码一个是服务端,一个是客户端,就像我们的淘宝,我们每次打开淘宝,都要去链接它,当然,淘宝可不直接是TCP

服务端

在Go中实现服务端,并且在服务端并发很简单,只需要将每个连接让一个协程处理即可!

代码

package main
import ( "bufio" "fmt" "net")
func process(conn net.Conn) { defer conn.Close() for { reader := bufio.NewReader(conn) buf := make([]byte128) 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)}}

客户端

客户端就很简单了,相对来说是不需要并发的,只需要连接就行。

代码

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("发送成功") }}}

执行结果

就这样,我们实现了服务端并发的处理所有客户端的请求。

粘包

我们先看一下什么是粘包。

服务端

package main
import ( "bufio" "fmt" "io" "net")
func process(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) buf := make([]byte1024) 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)}}

客户端

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([]byteint(4+length)) _, err = reader.Read(pack) if err != nil { return "", err} return string(pack[4:]), nil}

服务端

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)}}

客户端

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是一个无连接协议,客户端不会在乎服务端有没有问题,客户端只管发,通常用于实时性比较高的领域

例如直播行业

服务端

package main
import ( "fmt" "net")
func main() { listen, err := net.ListenUDP("udp", &net.UDPAddr{ IP: net.IPv4(0000), Port: 8009}) if err != nil { panic(fmt.Sprintf("udp启动失败,err:%v", err))} defer listen.Close() for{ var data = make([]byte1024) n, addr, err := listen.ReadFromUDP(data) if err != nil { panic(fmt.Sprintf("读取数据失败,err:%v", err)) } fmt.Println(string(data[:n]),addr,n)}}

客户端

package main
import ( "fmt" "net")
func main() { socker, err := net.DialUDP("udp"nil, &net.UDPAddr{ IP: net.IPv4(0000), 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/,点击阅读原文,可以直达噢~

------------------- End -------------------

往期精彩文章推荐:

欢迎大家点赞,留言,转发,转载,感谢大家的相伴与支持

想加入Go学习群请在后台回复【入群

万水千山总是情,点个【在看】行不行

good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter