go语言提升(四):tcp/udp,命令行参数

go语言提升(四):tcp/udp,命令行参数

1.TCP函数原型及服务器实现

  1. 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  // 字符串格式的地址
}
  1. 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程:负责与客户端进行实时的交互和信息通信

业务流程

  1. 常见监听器Listener
  2. 使用for监听listener – Accept()
  3. 当有客户端连接上来,创建子go程,使用conn与客户端进行数据通信
  4. 封装、实现函数:完成单个子go程与客户端的read、write – handleConnect(conn)
    1. 读取客户端的数据 conn.read()
    2. 处理数据 小 – 大 toUpper()
    3. 写回大写数据到客户端
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服务器的实现

实现流程:

  1. 获取UDP的地址结构 – UDPAddr

    func ResolveUDPAddr(net, addr string) (*UDPAddr, error)
    
    • 参数1:“udp”
    • 参数2:“ip:port”
    • 返回值:UDP地址结构
  2. 绑定地址结构体, 得到通信套接字 – udpConn

    func ListenUDP(net string, laddr *UDPAddr) (*UDPConn, error)
    
    • 参数1:“udp”
    • 参数2:ResolveUDPAddr的返回值:UDPAddr
    • 返回值:用于通信的socket
  3. 接收客户端发生的数据 – ReadFromUDP() – n, cltAddr, err

    func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error)
    
    • 参数:接收数据的缓冲区
    • 返回值:
      • n:读到的实际字节数
      • addr:对端的地址结构(ip+port)
  4. 写数据给客户端 – 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获取命令行参数的切片

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值