import “net”
net包提供了可移植的网络I/O接口,包括TCP/IP、UDP、域名解析和Unix域socket。
虽然本包提供了对网络原语的访问,大部分使用者只需要Dial、Listen和Accept函数提供的基本接口;以及相关的Conn和Listener接口。crypto/tls包提供了相同的接口和类似的Dial和Listen函数。
Dial函数和服务端建立连接:
conn, err := net.Dial("tcp", "google.com:80")if err != nil {// handle error}fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")status, err := bufio.NewReader(conn).ReadString('\n')// ...
Listen函数创建的服务端:
ln, err := net.Listen("tcp", ":8080")if err != nil {// handle error}for {conn, err := ln.Accept()if err != nil {// handle errorcontinue}go handleConnection(conn)}
type Listener interface {// Addr返回该接口的网络地址Addr() Addr// Accept等待并返回下一个连接到该接口的连接Accept() (c Conn, err error)// Close关闭该接口,并使任何阻塞的Accept操作都会不再阻塞并返回错误。Close() error}
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}
type Addr interface {Network() string // 网络名String() string // 字符串格式的地址}
代码体验
tcpdemo/server/server.go
package mainimport ("fmt""net")func main() {fmt.Println("服务器开始监听...")ln, err := net.Listen("tcp", "0.0.0.0:8888")if err != nil {fmt.Println("监听失败")return} else {fmt.Printf("监听成功, ln类型: %T, 值: %v \n", ln, ln)// ln类型: *net.TCPListener, 值: &{0xc00008ea00 {<nil> 0}}}defer ln.Close()// 等待客户端连接for {conn, err := ln.Accept()if err != nil {// handle errorfmt.Println("n.Accept() err = ", err)continue} else {fmt.Printf("🔊 n.Accept() suc conn: %v, 连接的客户端ip: %v \n", conn, conn.RemoteAddr().String())// n.Accept() suc conn: &{{0xc00010ec80}}, 连接的客户端ip: 172.15.37.225:63367}// 这里准备一个协程,与客户端交互go handleConnection(conn)}}func handleConnection(conn net.Conn) {// 循环接收客户端连接defer conn.Close()for {// 创建切片buf := make([]byte, 1024)// 1.等待客户端通过 conn 发送信息// 2.如果客户端没有 发送信息 ,那么就阻塞// fmt.Printf("👀 服务端在等待客户端: %v 输入信息 \n", conn.RemoteAddr().String())n, err := conn.Read(buf)/* if err == io.EOF {fmt.Printf("客户端: %v 退出\n", conn.RemoteAddr().String())return} */if err != nil {fmt.Println("客户端退出: ", err)return}// 3.显示服务端接收到的客户端信息fmt.Printf("👉 接收到客户端: %v 发来的信息:", conn.RemoteAddr().String())fmt.Print(string(buf[:n])) // 这里需要对切片截取,只取有效字符,避免出现未知错误fmt.Println()}}/*go run server.go服务器开始监听...监听成功, ln类型: *net.TCPListener, 值: &{0xc00010ea00 {<nil> 0}}🔊 n.Accept() suc conn: &{{0xc00010ec80}}, 连接的客户端ip: 172.15.37.225:61240👉 接收到客户端: 172.15.37.225:61240 发来的信息:11👉 接收到客户端: 172.15.37.225:61240 发来的信息:hello🔊 n.Accept() suc conn: &{{0xc00009a000}}, 连接的客户端ip: 172.15.37.225:52461👉 接收到客户端: 172.15.37.225:52461 发来的信息:xixi👉 接收到客户端: 172.15.37.225:61240 发来的信息:haha*/
tcpdemo/client/client.go
可以多开几个终端测试连接的 goroutine 处理机制
package mainimport ("bufio""fmt""net""os""strings")func main() {conn, err := net.Dial("tcp", "172.15.37.225:8888")if err != nil {// handle errorfmt.Println("client connect err", err)return}fmt.Println("连接成功", conn)// 客户端发送数据给服务端reader := bufio.NewReader(os.Stdin) // 从标准输入【终端】拿到信息for {line, err := reader.ReadString('\n')if err != nil {fmt.Println("文件读取错误", err)return}line = strings.Trim(line, "\r\n")if line == "exit" {fmt.Println("客户端退出")break}fmt.Println("line = ", line)n, err := conn.Write([]byte(line))if err != nil {fmt.Println("conn.Write() err = ", err)return}fmt.Printf("客户端发送 %v 个字节 \n", n)}}/*go run client.go连接成功 &{{0xc00008ea00}}xixiline = xixi客户端发送 4 个字节*/
