在一个服务器上注册并监听一个端口。然后它阻塞在一个 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 是非阻塞模式,会直接返回 EAGAINruntime.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 完成