一、TCP状态转换
- 主动发起连接请求端(实线):CLOSED —— 完成三次握手 —— ESTABLISEHED(数据通信状态)—— Dial()函数返回
- 被动发起连接请求端(虚线):CLOSED —— 调用Accept()函数 —— LISTEN —— 完成三次握手 —— ESTABLISEHED (数据通信状态)—— Accept()函数返回
- 数据传递期间:ESTABLISEHED (数据通信状态)
- 主动关闭连接请求端:ESTABLISEHED —— FIN_WAIT_2 (半关闭)—— TIME_WAIT —— 2MSL —— 确认最后一个ACK被对端成功接收。—— CLOSE
- 被动关闭连接请求端:ESTABLISEHED —— CLOSE
- 查看状态命令
- windows:
netstat -an | findstr 8001(端口号)
- Linux:
netstat -apn | grep 8001
- TCP状态转换图
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/1e66547189358f7378a699ed2858258f.png)
二、UDP通信
- UDP服务器原理:由于UDP是“无连接”的,所以,服务器端不需要额外创建监听套接字,只需要指定好IP和port,然后监听该地址,等待客户端与之建立连接,即可通信
- 创建监听地址:
func ResolveUDPAddr(network, address string) (*UDPAddr, error)
- 创建监听连接:
func ListenUDP(network string, laddr *UDPAddr) (*UDPConn, error)
- 接收udp数据:
func (c *UDPConn) ReadFromUDP(b []byte) (int, *UDPAddr, error)
- 写出数据到udp:
func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error)
- TCP与UDP区别
- TCP通信:面向连接的,可靠的数据包传输
- UDP通信:无连接的,不可靠的报文传递
1 - UDP简单服务器
- UDP简单服务器实现步骤
- ①.创建 server端地址结构(IP + port):
net.ResolveUDPAddr()
- ②.创建用于通信的socket, 绑定地址结构:
udpConn = net.ListenUDP(“udp”, server端地址结构)
- ③.
defer udpConn.Close()
- ④.读取客户端发送数据:
ReadFromUDP(buf)
;返回: n, cltAddr(客户端的IP+port) , err - ⑤.写数据给客户端:
WriteToUDP("待写数据",cltAddr)
package main
import (
"fmt"
"net"
"time"
)
func main() {
srvAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8006")
if err != nil {
fmt.Println("ResolveUDPAddr err:", err)
return
}
fmt.Println("udp 服务器地址结构,创建完程!!!")
udpConn, err := net.ListenUDP("udp", srvAddr)
if err != nil {
fmt.Println("ListenUDP err:", err)
return
}
defer udpConn.Close()
fmt.Println("udp 服务器通信socket创建完成!!!")
buf := make([]byte, 4096)
n, cltAddr, err := udpConn.ReadFromUDP(buf)
if err != nil {
fmt.Println("ReadFromUDP err:", err)
return
}
fmt.Printf("服务器读到 %v 的数据:%s\n", cltAddr, string(buf[:n]))
daytime := time.Now().String()
_, err = udpConn.WriteToUDP([]byte(daytime), cltAddr)
if err != nil {
fmt.Println("WriteToUDP err:", err)
return
}
}
2 - UDP简单客户端
- UDP简单客户端实现步骤:参考 TCP 客户端
net.Dial("udp", server 的IP+port)
package main
import (
"fmt"
"net"
)
func main() {
conn, err := net.Dial("udp", "127.0.0.1:8006")
if err != nil {
fmt.Println("net.Dial err:", err)
return
}
defer conn.Close()
conn.Write([]byte("Are you Ready?"))
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Read err:", err)
return
}
fmt.Println("服务器回发:", string(buf[:n]))
}
3 - UDP并发服务器
- UDP并发服务器实现
- ①.UDP默认支持客户端并发访问
- ②.使用go程将服务器处理 ReadFromUDP和 WriteToUDP操作分开,提高并发效率
package main
import (
"fmt"
"net"
"time"
)
func main() {
srvAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8006")
if err != nil {
fmt.Println("ResolveUDPAddr err:", err)
return
}
fmt.Println("udp 服务器地址结构,创建完程!!!")
udpConn, err := net.ListenUDP("udp", srvAddr)
if err != nil {
fmt.Println("ListenUDP err:", err)
return
}
defer udpConn.Close()
fmt.Println("udp 服务器通信socket创建完成!!!")
buf := make([]byte, 4096)
for {
n, cltAddr, err := udpConn.ReadFromUDP(buf)
if err != nil {
fmt.Println("ReadFromUDP err:", err)
return
}
fmt.Printf("服务器读到 %v 的数据:%s\n", cltAddr, string(buf[:n]))
go func() {
daytime := time.Now().String() + "\n"
_, err = udpConn.WriteToUDP([]byte(daytime), cltAddr)
if err != nil {
fmt.Println("WriteToUDP err:", err)
return
}
}()
}
}
package main
import (
"fmt"
"net"
)
func main() {
conn, err := net.Dial("udp", "127.0.0.1:8006")
if err != nil {
fmt.Println("net.Dial err:", err)
return
}
defer conn.Close()
for i := 0; i < 1000000; i++ {
conn.Write([]byte("Are you Ready?"))
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Read err:", err)
return
}
fmt.Println("服务器回发:", string(buf[:n]))
}
}
4 - TCP和UDP的区别
TCP | UDP |
---|
面向连接 | 面向无连接 |
要求系统资源较多 | 要求系统资源较少 |
TCP程序结构较复杂 | UDP程序结构较简单 |
使用流式 | 使用数据包式 |
保证数据准确性 | 不保证数据准确性 |
保证数据顺序 | 不保证数据顺序 |
通讯速度较慢 | 通讯速度较快 |
- TCP使用场景:对数据传输安全性、稳定性要求较高的场合,如网络文件传输、下载、上传
- UDP使用场景:对数据实时传输要求较高的场合,如视频直播、在线电话会议、游戏
三、案例:文件传输
1 - 命令行参数获取
- 命令行参数:在main函数启动时,向整个程序传参
- 语法:
go run xxx.go argv1 argv2 argv3 argv4
- xxx.go: 第0个参数(可执行文件的全路径)
- argv1 :第1个参数
- argv2 :第2个参数
- argv3 :第3个参数
- argv4 :第3个参数
- 命令行参数获取:
list := os.Args 参数3 = list[3]
package main
import (
"fmt"
"os"
)
func main() {
list := os.Args
fmt.Println(list)
}
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/36f6f621bbf32c532f5bfdbe64b1f3b7.png)
2 - 文件属性获取
- 获取文件属性:在函数返回的文件属性中包含文件名和文件大小。Stat参数name传入的是文件访问的绝对路径
func Stat(name string) (FileInfo, error)
- FileInfo原型
Name()
:获取文件名Size()
:获取文件大小
type FileInfo interface {
Name() string
Size() int64
Mode() FileMode
ModTime() time.Time
IsDir() bool
Sys() interface{}
}
3 - 文件传输:发送端
- 文件传输——发送端(客户端)流程
- ①.提示用户使用命令行参数输入文件名。接收文件名 filepath(含访问路径)
- ②.使用 os.Stat()获取文件属性,得到纯文件名 fileName(去除访问路径)
- ③.主动发起连接服务器请求,结束时关闭连接
- ④.发送文件名到接收端 conn.Write()
- ⑤.读取接收端回发的确认数据 conn.Read()
- ⑥.判断是否为“ok”。如果是,封装函数 SendFile() 发送文件内容。传参 filePath 和 conn
- ⑦.只读 Open 文件, 结束时Close文件
- ⑧.循环读本地文件,读到 EOF,读取完毕
- ⑨.将读到的内容原封不动 conn.Write 给接收端(服务器)
package main
import (
"fmt"
"io"
"net"
"os"
)
func sendFile(conn net.Conn, filePath string) {
f, err := os.Open(filePath)
if err != nil {
fmt.Println("os.Open err:", err)
return
}
defer f.Close()
buf := make([]byte, 4096)
for {
n, err := f.Read(buf)
if err != nil {
if err == io.EOF {
fmt.Println("发送文件完成。")
} else {
fmt.Println("os.Open err:", err)
}
return
}
_, err = conn.Write(buf[:n])
if err != nil {
fmt.Println("conn.Write err:", err)
return
}
}
}
func main() {
list := os.Args
if len(list) != 2 {
fmt.Println("格式为:go run xxx.go 文件绝对路径")
return
}
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:8008")
if err != nil {
fmt.Println("net.Dial err:", err)
return
}
defer conn.Close()
_, err = conn.Write([]byte(fileName))
if err != nil {
fmt.Println("conn.Write err:", err)
return
}
buf := make([]byte, 16)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Read err:", err)
return
}
if "ok" == string(buf[:n]) {
sendFile(conn, filePath)
}
}
4 - 文件传输:接收端
- 文件传输——接收端(服务器)流程
- ①.创建监听 listener,程序结束时关闭
- ②.阻塞等待客户端连接 conn,程序结束时关闭conn
- ③.读取客户端发送文件名。保存 fileName
- ④.回发“ok”
- ⑤.封装函数 RecvFile 接收客户端发送的文件内容。传参 fileName 和 conn
- ⑥.按文件名 Create 文件,结束时 Close
- ⑦.循环 Read 发送端网络文件内容,当读到 0 说明文件读取完毕
- ⑧.将读到的内容原封不动Write到创建的文件中
package main
import (
"fmt"
"net"
"os"
)
func recvFile(conn net.Conn, fileName string) {
f, err := os.Create(fileName)
if err != nil {
fmt.Println("os.Create err:", err)
return
}
defer f.Close()
buf := make([]byte, 4096)
for {
n, _ := conn.Read(buf)
if n == 0 {
fmt.Println("接收文件完成。")
return
}
f.Write(buf[:n])
}
}
func main() {
listener, err := net.Listen("tcp", "127.0.0.1:8008")
if err != nil {
fmt.Println(" net.Listen 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])
conn.Write([]byte("ok"))
recvFile(conn, fileName)
}