在一个服务器上注册并监听一个端口。然后它阻塞在一个 Accept
操作,并等待客户端连接。当一个客户端连接, Accept调用返回一个连接 Connection
对象
func main() {
var err error
var ln net.Listener
ln, err = net.Listen("tcp", ":9999")
defer ln.Close()
for {
conn, err := ln.Accept()
go handleRequest(conn)
}
}
func handleRequest(conn net.Conn) {
defer conn.Close()
for {
io.Copy(conn, conn)
}
}
Accept
socket
执行 accept
来获取可能的客户端连接socket
是非阻塞模式,会直接返回 EAGAIN
runtime.poll_runtime_pollWait
将当前协程挂起,并且根据是等待读还是等待写将当前 g
的引用保存到 pollDesc
中的 rg
或者 wg
中epoll
会通知将当前阻塞的协程恢复,然后重新执行第一步Go 使用非阻塞IO来防止大量系统线程阻塞带来的上下文切换,取而代之的是让轻量级的协程阻塞在 IO 事件上,然后通过epoll
来实现IO事件通知,唤醒阻塞的协程。
一旦客户端已经建立TCP服务, 就可以"拨号"了. 如果成功,该调用返回一个用于通信的TCPConn
。客户端和服务器通过它交换消息。通常情况下,客户端使用TCPConn
写入请求到服务器, 并从TCPConn
的读取响应。持续如此,直到任一(或两者)的两侧关闭连接。客户端使用该函数建立一个TCP连接
同步机制
Share memory by communicating, don't communicate by sharing memory
因为客户端发送和接收是在两个goroutine 中,main 函数中如果不加上同步机制, 客户端还没有发送接收就执行完了
通过channel实现同步,读和写完成后分别往channel中写入"done",main读取channel中的值,当两个done都读取到后就知道读写已经完成。
func main() {
conn, err := net.Dial("tcp", ":9000")
defer conn.Close()
done := make(chan string)
go handleWrite(conn, done)
go handleRead(conn, done)
fmt.Println(<-done)
fmt.Println(<-done)
}
func handleWrite(conn net.Conn, done chan string) {
for i := 10; i > 0; i-- {
_, e := conn.Write([]byte("hello " + strconv.Itoa(i) + "\\r\\n"))
}
done <- "sent"
}
func handleRead(conn net.Conn, done chan string) {
buf := make([]byte, 1024)
reqLen, err := conn.Read(buf)
fmt.Println(string(buf[:reqLen-1]))
done <- "read"
}
net.Dial
建立连接, handleWrite
发送十个请求, handleRead
接收服务器的响应。一旦完成,往 channel 中写 done
goroutine
完成