listen()、accept()和connect()、三者之间的关系

基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下:
image.png

listen()函数

对于服务器,它是被动连接的。
举一个生活中的例子,通常的情况下,移动的客服(相当于服务器)是等待着客户(相当于客户端)电话的到来。而这个过程,需要调用listen()函数。

listen() 函数的主要作用就是将套接字( sockfd )变成被动的连接监听套接字(被动等待客户端的连接),至于参数 backlog 的作用是设置内核中连接队列的长度(这个长度有什么用,后面做详细的解释),TCP 三次握手也不是由这个函数完成,listen()的作用仅仅告诉内核一些信息。

listen()函数不会阻塞,它主要做的事情为,将该套接字和套接字对应的连接队列长度告诉 Linux 内核,然后,listen()函数就结束。

这样的话,当有一个客户端主动连接(connect()),Linux 内核就自动完成TCP 三次握手,将建立好的链接自动存储到队列中,如此重复。

所以,只要 TCP 服务器调用了 listen(),客户端就可以通过 connect() 和服务器建立连接,而这个连接的过程是由内核完成。
net/http - 图2

accept()函数

accept()函数功能是,从处于 established 状态的连接队列头部取出一个已经完成的连接。
如果这个队列没有已经完成的连接,accept()函数就会阻塞,直到取出队列中已完成的用户连接为止。

connect()函数

对于客户端的 connect() 函数,该函数的功能为客户端主动连接服务器。
建立连接是通过三次握手,而这个连接的过程是由内核完成,不是这个函数完成的。

这个函数的作用仅仅是通知 Linux 内核,让 Linux 内核自动完成 TCP 三次握手连接,最后把连接的结果返回给这个函数的返回值(成功连接为0, 失败为-1)。

通常的情况,客户端的 connect() 函数默认会一直阻塞,直到三次握手成功或超时失败才返回(正常的情况,这个过程很快完成)。

Golang中的TCP网络编程

image.png

socket相关的底层函数

  1. //创建一个socket文件描述符 套接字
  2. socketFunc func(int, int, int) (int, error) = syscall.Socket
  3. //绑定一个本机IP:port到socket文件描述符上
  4. func Bind(fd int, sa Sockaddr) (err error)
  5. //监听是否有tcp连接请求
  6. listenFunc func(int, int) error = syscall.Listen
  7. //获取一个建立好的tcp连接 默认阻塞
  8. acceptFunc func(int) (int, syscall.Sockaddr, error) = syscall.Accept
  9. //发起tcp连接请求
  10. connectFunc func(int, syscall.Sockaddr) error = syscall.Connect
  11. //关闭连接
  12. closeFunc func(int) error = syscall.Close

Dial()函数

Dial() 函数用于创建网络连接,函数原型如下:

  1. func Dial(network,address string) (Conn,error) {
  2. var d Dialer
  3. return d.Dial(network,address)
  4. }

参数说明如下:

  • network 参数表示传入的网络协议(比如 tcp、udp 等);
  • address 参数表示传入的 IP 地址或域名,而端口号是可选的,如果需要指定的话,以:的形式跟在地址或域名的后面即可。如果连接成功,该函数返回连接对象,否则返回 error。

成功建立连接后,就可以进行数据的发送和接收。
发送数据时使用连接对象 conn 的 Write() 方法,接收数据时使用 Read() 方法。

实际上,Dial() 函数是对 DialTCP()、DialUDP()、DialIP()、DialUnix() 函数的封装,同时实现了Conn接口。

  1. func Dial(network, address string) (Conn, error)
  2. func DialTCP(net string,laddr,raddr *TCPAddr) (c *TCPConn,err error)
  3. func DialUDP(net string,raddr *UDPAddr) (c *UDPConn,err error)
  4. func DialIP(netProto string,raddr *IPAddr) (c *IPConn,err error)
  5. func DialUnix(net string,raddr *UnixAddr) (c *UnixConn,err error)

虽然返回TCPConn,UDPConn,IPConn,UnixConn,但是他们的底层是一样的如下图:
image.png
Dial() 函数支持如下几种网络协议:

  • tcp、tcp4(仅限 IPv4)、tcp6(仅限 IPv6)
  • udp、udp4(仅限 IPv4)、udp6(仅限 IPv6)
  • ip、ip4(仅限 IPv4)、ip6(仅限 IPv6)
  • unix、unixgram 和 unixpacket

几种常见协议的调用方式

  1. // TCP 连接
  2. conn,err := net.Dial("tcp","192.168.10.10:80")
  3. // UDP 连接:
  4. conn,err := net.Dial("udp","192.168.10.10:8888")
  5. // ICMP 连接(使用协议名称):
  6. // 提示:ip4 表示 IPv4,相应的 ip6 表示 IPv6
  7. conn,err := net.Dial("ip4:icmp","c.biancheng.net")
  8. // ICMP 连接(使用协议编号):
  9. conn,err := net.Dial("ip4:1","10.0.0.3")

Demo

服务端 server.go

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. )
  6. func main() {
  7. // 服务端监听
  8. lis, err := net.Listen("tcp", "127.0.0.1:8000")
  9. if err != nil {
  10. fmt.Println("err: ", err)
  11. return
  12. }
  13. defer lis.Close()
  14. // 阻塞等待客户端连接
  15. conn, err := lis.Accept()
  16. if err != nil {
  17. fmt.Println("err: ", err)
  18. return
  19. }
  20. // 接收用户请求
  21. buf := make([]byte, 1024) // 缓冲区大小 1024byte
  22. n, err := conn.Read(buf)
  23. if err != nil {
  24. fmt.Println("err: ", err)
  25. return
  26. }
  27. fmt.Println("buf: ", string(buf[:n]))
  28. // 关闭当前用户连接
  29. defer conn.Close()
  30. }

启动服务端:go run server.go

  1. HSD:tcp daysun$ go run server.go
  2. buf: Are you ok?

客户端发送数据

  1. HSD:~ daysun$ nc 127.0.0.1 8000
  2. Are you ok?
  3. HSD:~ daysun$

客户端 client.go

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. )
  6. func main() {
  7. // 主动连接服务器
  8. conn, err := net.Dial("tcp", "127.0.0.1:8000")
  9. if err != nil {
  10. fmt.Println("连接服务器失败: ", err)
  11. return
  12. }
  13. fmt.Println("连接服务器成功")
  14. defer conn.Close()
  15. str := []byte("Are you ok?")
  16. fmt.Println("发送数据: ", str)
  17. // 发送数据
  18. conn.Write(str)
  19. }
  1. go run server.go
  2. go run client.go

并发Demo
server.go

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. "strings"
  6. )
  7. //处理用户请求
  8. func HandleConn(conn net.Conn) {
  9. //函数调用完毕,自动关闭conn
  10. defer conn.Close()
  11. //获取客户端的网络地址信息
  12. addr := conn.RemoteAddr().String()
  13. fmt.Println("客户端连接成功:", addr)
  14. buf := make([]byte, 2048)
  15. for {
  16. //读取用户数据
  17. n, err := conn.Read(buf)
  18. if err != nil {
  19. fmt.Println("err = ", err)
  20. return
  21. }
  22. fmt.Printf("接收到数据 [%s]: %s", addr, string(buf[:n]))
  23. //if "exit" == string(buf[:n-1]) { //nc测试
  24. if "exit" == string(buf[:n-2]) { //自己写的客户端测试, 发送时,多了2个字符, "\r\n"
  25. fmt.Println(addr, " exit")
  26. return
  27. }
  28. //把数据转换为大写,再给用户发送
  29. conn.Write([]byte(strings.ToUpper(string(buf[:n]))))
  30. }
  31. }
  32. func main() {
  33. //监听
  34. listener, err := net.Listen("tcp", "127.0.0.1:8000")
  35. if err != nil {
  36. fmt.Println("err = ", err)
  37. return
  38. }
  39. defer listener.Close()
  40. //接收多个用户
  41. for {
  42. conn, err := listener.Accept()
  43. if err != nil {
  44. fmt.Println("err = ", err)
  45. return
  46. }
  47. //处理用户请求, 新建一个协程
  48. go HandleConn(conn)
  49. }
  50. }

client.go

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. "os"
  6. )
  7. func main() {
  8. //主动连接服务器
  9. conn, err := net.Dial("tcp", "127.0.0.1:8000")
  10. if err != nil {
  11. fmt.Println("net.Dial err = ", err)
  12. return
  13. }
  14. //main调用完毕,关闭连接
  15. defer conn.Close()
  16. fmt.Println("请输入内容:")
  17. go func() {
  18. //从键盘输入内容,给服务器发送内容
  19. str := make([]byte, 1024)
  20. for {
  21. strLen, err := os.Stdin.Read(str) //从键盘读取内容, 放在str
  22. if err != nil {
  23. fmt.Println("os.Stdin. err = ", err)
  24. return
  25. }
  26. //把输入的内容给服务器发送
  27. conn.Write(str[:strLen])
  28. }
  29. }()
  30. //接收服务器回复的数据
  31. //切片缓冲
  32. buf := make([]byte, 1024)
  33. for {
  34. n, err := conn.Read(buf) //接收服务器的请求
  35. if err != nil {
  36. fmt.Println("conn.Read err = ", err)
  37. return
  38. }
  39. // 打印接收到的内容, 转换为字符串再打印
  40. fmt.Println("接收服务端内容:", string(buf[:n]))
  41. }
  42. }