Go-网络通信
TCP
TCP通信:面向连接的,可靠的数据包传输。
协议:一组规则,要求使用协议双方必须遵守协议内容。
网络分层架构:
OSI七层模型结构:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
TCP/IP四层模型:数据链路层、网络层、传输层、应用层。
各层功能:
1、链路层:ARP
源mac-目标mac
ARP协议作用:借助IP获取mac地址
2、网络层:IP
缘IP-目标IP
IP协议的作用:在网络环境中 唯一标示一台主机
IP地址本质:2机制。点分十进制IP地址(string)
3、传输层:TCP/UDP
port-在一台计算机上唯一标示一个进程
4、应用层:ftp、http、自定义
对数据进行封装、解封装
数据通知过程:
封装:应用层-传输层-网络层-链路层 。没有经过封装,不能在网络环境中传递。
解封装:链路层-网络层-传输层-应用层
通信过程:
1、mac地址(不需要用户指定)(ARP协议)->mac
2、IP地址(需要用户指定)-确定主机
3、port端口号(需要用户指定)-确定程序
1、不能使用系统占用的端口。2、5000+端口。我们使用。3、65535为端口上线。
socket:套接字
网络通信过程中,socket一定是成对出现的。
TCP通信过程
三次握手:
1、主动发起请求端,发送SYN
2、被动建立连接 请求端,应答ACK同时发送SYN
3、主动发起请求端,发送ACK
标志TCP三次握手建立完成,-----server:accept()返回。-----client:Dial()返回
四次挥手:
1、主动关闭连接请求端,发送FIN
2、被动关闭连接请求端,应答ACK
标志,半关闭完成。---close()
3、主动关连接请求端,发送FIN
4、被动关闭连接请求端,应答ACK
标志,四次挥手建立完成。---close()
TCP状态转换图
image.png image.png
代码示例(服务端):
package main
import (
"fmt"
"net"
)
func main() {
//1、指定服务器通信协议,ip地址,port。创建一个用于监听的socket
listener, err := net.Listen("tcp","127.0.0.1:8000")
if err != nil {
fmt.Println("net.Listen Err",err)
return
}
defer listener.Close()
fmt.Println("服务器正在等待客户端连接...")
//2、阻塞监听客户端连接请求,成功建立连接,返回用于通信的socket
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener.Accept() Err",err)
return
}
defer conn.Close()
fmt.Println("服务器与客户端成功建立连接!!!")
//3、读取客户端发送的数据
buf := make([]byte,4096)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Read(buf) Err",err)
return
}
conn.Write(buf[:n])//读多少,写多少
//4、处理数据
fmt.Println("服务器读到的数据,",string(buf[:n]))
}
执行结果:
服务器正在等待客户端连接...
服务器与客户端成功建立连接!!!
服务器读到的数据, hello world!
客户端:
package main
import (
"fmt"
"net"
)
func main(){
//指定服务器IP + port创建通信套接字
conn, err := net.Dial("tcp","127.0.0.1:8000")
if err != nil {
fmt.Println("net.Dial err",err)
return
}
defer conn.Close()
//主动写数据给服务器
_, err = conn.Write([]byte("Hello World"))
if err != nil {
fmt.Println("conn.Write err",err)
return
}
//接收服务器回发数据
buf := make([]byte,4096)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Write err",err)
return
}
fmt.Println("服务器回发",string(buf[:n]))
}
并发服务器(服务端):
package main
import (
"fmt"
"net"
"strings"
)
func HaldlerConn(conn net.Conn) {
defer conn.Close()
//获取客户端连接地址
addr := conn.RemoteAddr()
fmt.Println(addr,"客户端连接成功!!")
buf := make([]byte,4096)
//循环获取客户端发送的数据
for {
n, err := conn.Read(buf)
if "exit\n" == string(buf[:n]) || "exit\r\n" == string(buf[:n]) {
fmt.Println("服务器接收到客户端退出请求!!服务器退出")
}
if n == 0 {
fmt.Println("服务器监测到客户端已经关闭,断开连接!!")
return
}
if err != nil {
fmt.Println("conn.Read Err",err)
return
}
fmt.Printf("服务器获取客户端%s的数据%s",addr.String(),string(buf[:n]))
//答谢转小写,回发给客户端
upperStrings := strings.ToUpper(string(buf[:n]))
_, err = conn.Write([]byte(upperStrings))
if err != nil {
fmt.Println("conn.Write Err",err)
return
}
}
}
func main () {
//指定服务器通信协议,ip地址,port。创建一个用于监听的socket
listener, err := net.Listen("tcp","127.0.0.1:8000")
if err != nil {
fmt.Println("net.Listen Err",err)
return
}
defer listener.Close()
fmt.Println("服务器正在等待客户端连接...")
for {
conn, err := listener.Accept()
if err != nil {fmt.Println("listener.Accept Err",err)
return
}
go HaldlerConn(conn)
}
}
执行结果:
~/Downloads/learngo/src/(master ✘)✖✹✚✭ ᐅ ./tcp
服务器正在等待客户端连接...
127.0.0.1:62699 客户端连接成功!!
127.0.0.1:62700 客户端连接成功!!
服务器获取客户端127.0.0.1:62700的数据wef
服务器获取客户端127.0.0.1:62699的数据erwe
服务器获取客户端127.0.0.1:62699的数据hello
服务器获取客户端127.0.0.1:62700的数据world
~ ᐅ nc 127.0.0.1 8000
erwe
ERWE
hello
HELLO
ᐅ nc 127.0.0.1 8000
wef
WEF
world
WORLD
并发服务器(客户端):
package main
import (
"fmt"
"net"
"os"
)
func main() {
//指定服务器IP + port创建通信套接字
conn, err := net.Dial("tcp","127.0.0.1:8000")
if err != nil {
fmt.Println("net.Dial Err",err)
return
}
defer conn.Close()
//指定键盘输入
go func() {
str := make([]byte, 4096)
for {
n, err := os.Stdin.Read(str)
if err != nil {
fmt.Println("os.Stdin.Read Err",err)
continue
}
n, err = conn.Write(str[:n])
if err != nil {
fmt.Println("os.Stdin.Read Err",err)
return
}
}
}()
//回显服务器回发来的大写数据
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if n == 0 {//服务器端回显数据为0
fmt.Println("检查到服务器关闭,客户端退出")
return
}
if err != nil {
fmt.Println("conn.Read Err",err)
return
}
fmt.Println("客户端读到服务器回发",string(buf[:n]))
}
}
UDP
UDP通信:无连接的,不可靠的报文传输。
UDP服务端示例代码:
package main
import (
"fmt"
"net"
"time"
)
func main (){
//组织一个udp地址结构,指定服务器的ip+port
srvAddr, err := net.ResolveUDPAddr("udp","127.0.0.1:8002")
if err != nil {
fmt.Println("net.ResolveUDPAddr Err",err)
return
}
fmt.Println("udp服务器地址结构创建完成!")
//创建一个用户通信的socket
udpConn, err := net.ListenUDP("udp",srvAddr)
if err != nil {
fmt.Println("net.ListenUDP Err",err)
return
}
defer udpConn.Close()
fmt.Println("udp服务器socket创建完成!")
//读取客户端发送的数据
buf := make([]byte, 4096)
//返回三个只,分别是读取的字节数,客户端的IP地址,err
n, clientAddr, err := udpConn.ReadFromUDP(buf)
if err != nil {
fmt.Println("ReadFromUDP Err",err)
return
}
//处理数据
fmt.Printf("服务器读到%v的数据:%s\n",clientAddr,string(buf[:n]))
//提取系统当前时间
nowTime := time.Now().String()
//写回数据给客户端
_, err = udpConn.WriteToUDP([]byte(nowTime),clientAddr)
if err != nil {
fmt.Println("WriteToUDP Err",err)
return
}
}
UDP客户端示例代码:
package main
import (
"fmt"
"net"
)
func main(){
//指定服务器IP + port创建通信套接字
conn, err := net.Dial("udp","127.0.0.1:8002")
if err != nil {
fmt.Println("net.Dial err",err)
return
}
defer conn.Close()
//主动写数据给服务器
_, err = conn.Write([]byte("Hello World"))
if err != nil {
fmt.Println("conn.Write err",err)
return
}
//接收服务器回发数据
buf := make([]byte,4096)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Write err",err)
return
}
fmt.Println("服务器回发",string(buf[:n]))
}
总结UDP和TCP差异:
image.png
使用场景:
TCP:对数据传输安全性、稳定性要求较高的场合,网络文件传输、下载、上传。
UDP:对数据实时传输要求较高的场景,视频直播、在线电话会议、游戏。
文件传输-发送端(客户端)
1、提示用户使用命令行参数输入文件名,接收文件名filepath(含有访问路径)
2、使用os.stat()获取文件属性。得到文件名filename(取出访问路径)
3、主动发起连接服务器请求,结束时关闭连接
4、发送文件名到接收端conn.write()
5、读取接收端回发的确认数据conn.read()
6、判断是否为“ok”,如果是,封装函数sendFile发送文件内容,传参filepath和conn
7、只读方式open文件,结束时close文件
8、循环读取本地文件,读到EOF,读取完毕
9、将读取的内容原封不动的conn.write给接收端(服务端)
文件传输示例:
发送端
package main
import (
"fmt"
"io"
"net"
"os"
)
func main () {
//获取命令行参数
list := os.Args
//获取文件路径
filePath := list[1]
fileInfo, err := os.Stat(filePath)
if err != nil {
fmt.Println(" os.Stat err",err)
return
}
//获取文件名
fileName := fileInfo.Name()
//建立连接
conn, err := net.Dial("tcp","127.0.0.1:8003")
if err != nil {
fmt.Println("net.Dial err",err)
return
}
defer conn.Close()
//发送文件名给接收端
_, err = conn.Write([]byte(fileName))
if err != nil {
fmt.Println("conn.Read err",err)
return
}
//接收服务器回发消息
buf := make([]byte,4096)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Write err",err)
return
}
if "ok" == string(buf[:n]) {
sendFile(conn,filePath)
}
}
func sendFile (conn net.Conn, filePath string) {
//打开文件
file, err := os.Open(filePath)
if err != nil {
fmt.Println("os.Open err",err)
return
}
defer file.Close()
//从文件中读取数据,写给接收端.读多少,写多少。
buf := make([]byte, 4096)
for {
n, err := file.Read(buf)
if err != nil {
if err == io.EOF {
fmt.Println("文件发送完毕!")
return
}else {
fmt.Println("file.Read err",err)
}
return
}
//写到网络中
_, err = conn.Write(buf[:n])
if err != nil {
fmt.Println("conn.Write err",err)
return
}
}
}
文件传输-接收端
1、创建监听listener,程序结束时关闭。
2、阻塞等待客户端连接conn,程序结束时关闭conn
3、读取客户端发送文件名,保存filename
4、回发“ok”
5、封装函数recvFile接收客户端发送的文件内容,穿参数filename和conn
6、按照文件名create文件,结束时close
7、循环read发送端网络温年内容,当读到0时,说明文件读取完毕
8、将读取的文件内容原封不动的write到创建的文件中
接收端:
package main
import (
"fmt"
"net"
"os"
)
func main() {
//创建监听socket
listener, err := net.Listen("tcp","127.0.0.1:8003")
if err != nil {
fmt.Println("net.Dial err",err)
return
}
defer listener.Close()
//阻塞等待发送端建立连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener.Accept err",err)
return
}
defer conn.Close()
//获取文件名,保存
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Read err",err)
return
}
filename := string(buf[:n])
//回写ok给发送端
_, err = conn.Write([]byte("ok"))
if err != nil {
fmt.Println("conn.Write err",err)
return
}
//接收文件
recvFile(conn,filename)
}
func recvFile(conn net.Conn,filename string) {
//创建文件
file, err := os.Create(filename)
if err != nil {
fmt.Println("os.Create err",err)
return
}
defer file.Close()
//从网络中读数据,写入文件
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if n == 0 {
fmt.Printf("文件%s接收完毕!",filename)
return
}
if err != nil {
fmt.Println("conn.Read err",err)
return
}
//写入文件
_,err = file.Write(buf[:n])
if err != nil {
fmt.Println("file.Write err",err)
return
}
}
}