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 error
continue
}
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 main
import (
"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 error
fmt.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 main
import (
"bufio"
"fmt"
"net"
"os"
"strings"
)
func main() {
conn, err := net.Dial("tcp", "172.15.37.225:8888")
if err != nil {
// handle error
fmt.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}}
xixi
line = xixi
客户端发送 4 个字节
*/