go语言提升(四):tcp/udp,命令行参数
1.TCP函数原型及服务器实现
- Listen函数
func Listen(net, laddr string) (Listener, error)
- 参数1:协议类型:tcp、upd(必须小写)
- 参数2:服务器端的IP:port(192.168.31.11:8000)
- 返回值:成功创建的监听器
type Listener interface{
Addr() addr
Accept() (c Conn, err error)
Close() error
}
type Addr interface {
Network() string // 网络名
String() string // 字符串格式的地址
}
- Accept函数–阻塞等待客户端连接
(listener *Listener) Accpt() (c Conn, err error)
- 返回值:成功与客户端建立的连接(socket)
type Conn interface {
// Read从连接中读取数据
// Read方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
Read(b []byte) (n int, err error)
// Write从连接中写入数据
// Write方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
Write(b []byte) (n int, err error)
// Close方法关闭该连接
// 并会导致任何阻塞中的Read或Write方法不再阻塞并返回错误
Close() error
// 返回本地网络地址
LocalAddr() Addr
// 返回远端网络地址
RemoteAddr() Addr
// 设定该连接的读写deadline,等价于同时调用SetReadDeadline和SetWriteDeadline
// deadline是一个绝对时间,超过该时间后I/O操作就会直接因超时失败返回而不会阻塞
// deadline对之后的所有I/O操作都起效,而不仅仅是下一次的读或写操作
// 参数t为零值表示不设置期限
SetDeadline(t time.Time) error
// 设定该连接的读操作deadline,参数t为零值表示不设置期限
SetReadDeadline(t time.Time) error
// 设定该连接的写操作deadline,参数t为零值表示不设置期限
// 即使写入超时,返回值n也可能>0,说明成功写入了部分数据
SetWriteDeadline(t time.Time) error
}
test
package main
import (
"bytes"
"fmt"
"net"
)
func main() {
// 1. 创建监听器
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("Listen error:", err)
return
}
defer listener.Close()
fmt.Println("-----------1")
// 2. 阻塞等待客户端连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("Accept error:", err)
return
}
defer conn.Close()
fmt.Println("-----------2")
// 创建buffer 存储读取到的客户端数据
buf := make([]byte, 4096)
for {
// 3. 读取客户端发送的数据
n, err := conn.Read(buf)
if err != nil {
fmt.Println("Read err:", err)
return
}
fmt.Printf("服务器读到, 客户端数据%s\n", string(buf[:n]))
// 4. 处理客户端数据
// 小写转大写
buf = bytes.ToUpper(buf)
// 5. 回发数据给客户端
conn.Write(buf)
}
fmt.Println("通信结束!")
// 6. 创建客户端进行测试, 使用nc命令
}
2. 客户端实现
-
Dial方法
func Dial(network, address string) (Conn, error)
- 参数1:使用的协议:udp/tcp(注意一定是小写)
- 参数2:指定服务器的ip:port(127.0.0.1:8000)
- 返回值:用于进行通信的socket
type Conn interface { // Read从连接中读取数据 // Read方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真 Read(b []byte) (n int, err error) // Write从连接中写入数据 // Write方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真 Write(b []byte) (n int, err error) // Close方法关闭该连接 // 并会导致任何阻塞中的Read或Write方法不再阻塞并返回错误 Close() error // 返回本地网络地址 LocalAddr() Addr // 返回远端网络地址 RemoteAddr() Addr // 设定该连接的读写deadline,等价于同时调用SetReadDeadline和SetWriteDeadline // deadline是一个绝对时间,超过该时间后I/O操作就会直接因超时失败返回而不会阻塞 // deadline对之后的所有I/O操作都起效,而不仅仅是下一次的读或写操作 // 参数t为零值表示不设置期限 SetDeadline(t time.Time) error // 设定该连接的读操作deadline,参数t为零值表示不设置期限 SetReadDeadline(t time.Time) error // 设定该连接的写操作deadline,参数t为零值表示不设置期限 // 即使写入超时,返回值n也可能>0,说明成功写入了部分数据 SetWriteDeadline(t time.Time) error }
-
test
package main import ( "fmt" "net" "time" ) func main() { // 1. 创建用于通信的套接字 conn, err := net.Dial("tcp", "127.0.0.1:8000") if err != nil { fmt.Println("Dial error:", err) return } defer conn.Close() buf := make([]byte, 4096) for { // 2. 发送数据给服务器 conn.Write([]byte("abcde")) // 3. 接收服务器回发数据 n, err := conn.Read(buf) if err != nil { fmt.Println("Read error:", err) } fmt.Println("客户端收到:", string(buf[:n])) time.Sleep(time.Second*2) } // 4. 关闭连接(已defer) }
3. 并发服务器
主go程:负责监听客户端的连接事件并且创建出conn
子go程:负责与客户端进行实时的交互和信息通信
业务流程
- 常见监听器Listener
- 使用for监听listener – Accept()
- 当有客户端连接上来,创建子go程,使用conn与客户端进行数据通信
- 封装、实现函数:完成单个子go程与客户端的read、write – handleConnect(conn)
- 读取客户端的数据 conn.read()
- 处理数据 小 – 大 toUpper()
- 写回大写数据到客户端
package main
import (
"bytes"
"fmt"
"io"
"net"
"runtime"
)
func handleConnect(conn net.Conn) {
defer conn.Close()
fmt.Println("当前ip:", conn.RemoteAddr())
// 创建buffer 存储读取到的客户端数据
buf := make([]byte, 4096)
for {
// 读取客户端发送的数据
n, err := conn.Read(buf)
// 当读到exit就退出
if string(buf[:n-1]) == "exit" {
fmt.Println("服务器收到客户端[", conn.RemoteAddr().String(), "]关闭,关闭本端")
runtime.Goexit()
}
if n == 0 {
fmt.Println("服务器检测到客户端退出")
runtime.Goexit()
}
if err != nil && err != io.EOF {
fmt.Println("Read err:", err)
return
}
fmt.Printf("服务器读到, 客户端数据%s\n", string(buf[:n-1]))
// 处理客户端数据
// 小写转大写
buf = bytes.ToUpper(buf)
// 回发数据给客户端
_, err = conn.Write(buf[:n])
if err != nil {
fmt.Println("Write error:", err)
return
}
}
}
func main() {
// 1. 创建监听器
listener, err := net.Listen("tcp", "127.0.0.1:8001")
if err != nil {
fmt.Println("Listen error:", err)
return
}
defer listener.Close()
// 2. 使用for监听,每有一次连接创建一个go程
for {
// 2. 阻塞等待客户端连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("Accept error:", err)
continue
}
go handleConnect(conn)
}
}
4. 实现nc命令
package main
import (
"fmt"
"io"
"net"
"os"
"runtime"
)
func getFromKeyBoard(conn net.Conn, buf []byte) {
n, err := os.Stdin.Read(buf)
if err != nil {
fmt.Println("Dial error:", err)
return
}
// 3. 将buf中的数据写给服务器
_, err = conn.Write(buf[:n])
if err != nil {
fmt.Println("Write error:", err)
return
}
if string(buf[:n-1]) == "exit" {
fmt.Println("客户端结束退出")
os.Exit(1)
}
}
func readConn(conn net.Conn, buf []byte) {
n, err := conn.Read(buf)
if n == 0 {
fmt.Println("服务器关闭")
runtime.Goexit()
}
if err != nil && err != io.EOF {
fmt.Println("Read error:", err)
return
}
// 5. 应用数据/打印
fmt.Println("从服务器读到:", string(buf[:n-1]))
}
func main() {
// 1. 与服务器建立连接
conn, err := net.Dial("tcp", "127.1:8001")
if err != nil {
fmt.Println("Dial error:", err)
return
}
defer conn.Close()
buf := make([]byte, 4096)
for {
// 2. 从键盘获取键盘输入 -- os.Stdin.Read -> buf
getFromKeyBoard(conn, buf)
// 4. 创建go程将读取服务器回发的数据 conn.Read
go readConn(conn, buf)
}
}
5. UDP服务器的实现
实现流程:
-
获取UDP的地址结构 – UDPAddr
func ResolveUDPAddr(net, addr string) (*UDPAddr, error)
- 参数1:“udp”
- 参数2:“ip:port”
- 返回值:UDP地址结构
-
绑定地址结构体, 得到通信套接字 – udpConn
func ListenUDP(net string, laddr *UDPAddr) (*UDPConn, error)
- 参数1:“udp”
- 参数2:ResolveUDPAddr的返回值:UDPAddr
- 返回值:用于通信的socket
-
接收客户端发生的数据 – ReadFromUDP() – n, cltAddr, err
func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error)
- 参数:接收数据的缓冲区
- 返回值:
- n:读到的实际字节数
- addr:对端的地址结构(ip+port)
-
写数据给客户端 – WriteToUDP(数据,cltAddr) – n, err
func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error)
- 参数1:实际写出的数据
- 参数2:对端的地址结构
- 返回值:实际写出的字节数
type UDPAddr struct {
IP IP
Port int
Zone string // IPv6范围寻址域
}
Demo
package main
import (
"bytes"
"fmt"
"net"
)
func main() {
// 1. 获取UDP的地址结构
udpAddr, err := net.ResolveUDPAddr("udp", "127.1:8002")
if err != nil {
fmt.Println("ResolveUDPAddr error:", err)
return
}
// 2. 绑定地址结构体, 得到通信套接字
udpConn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
fmt.Println("ListenUDP error:", err)
return
}
defer udpConn.Close()
for {
// 3. 接收客户端发生的数据
buf := make([]byte, 4096)
n, addr, err := udpConn.ReadFromUDP(buf)
if err != nil {
fmt.Println("ReadFromUDP error:", err)
return
}
fmt.Println("UDP服务器读到:", string(buf[:n-1]))
// 小写转大写
buf = bytes.ToUpper(buf)
// 4. 写数据给客户端
_, err = udpConn.WriteToUDP(buf[:n], addr)
if err != nil {
fmt.Println("WriteToUDP error:", err)
return
}
}
}
6. UDP客户端的实现
与TCP相同,直接用Dial创建出来的套接字通信即可
package main
import (
"fmt"
"io"
"net"
"os"
)
func main() {
conn, err := net.Dial("udp", "127.1:8002")
if err != nil {
fmt.Println("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("Stdin.Read error:", err)
return
}
_, err = conn.Write(str[:n])
if err != nil {
fmt.Println("Write error:", err)
return
}
}
}()
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if n == 0 {
fmt.Println("检测到服务器关闭,关闭本端")
return
}
if err != nil && err != io.EOF {
fmt.Println("Read error:", err)
return
}
fmt.Println("客户端读到:", string(buf[:n-1]))
}
}
7. 命令行参数的使用
直接看demo
func main() {
list := os.Args
for i, arg := range list {
fmt.Printf("[%d]:%s\n", i, arg)
}
}
os.Args获取命令行参数的切片