美文网首页
2.7 Socket Programming: Creating

2.7 Socket Programming: Creating

作者: 找不到工作 | 来源:发表于2020-05-12 21:10 被阅读0次

    在网络应用开发中,开发者首先要做的一个决定是使用 TCP 还是 UDP 作为传输层协议。TCP 是基于连接,并且基于字节流提供可靠的数据传输的协议。而 UDP 是无连接,通过数据包发送数据,并不保证送达的协议。

    我们将在这一节分别利用 UDP 和 TCP 实现一套 client-server 程序。

    该程序主要完成的功能是:

    1. client 从键盘读取一行字符串,并发给 server
    2. server 收到字符串并转换成大写
    3. server 将修改后的字符串发送给 client
    4. client 收到修改后的数据并显示

    2.7.1 Socket Programming with UDP

    UDP 在发送数据包时,需要先在数据包中附加地址的信息。网络会利用这个信息 route 数据包到达接收程序。

    附加的地址信息应该包括:

    • 目标 IP 地址
    • 目标端口号
    • 自身 IP 地址
    • 自身端口号

    附加自身 IP 地址和自身端口号一般不需要自己实现,操作系统会完成。

    Golang 中对 UDP 地址的定义为:

    type UDPAddr struct {
        IP   IP
        Port int
        Zone string // IPv6 scoped addressing zone
    }
    

    提供的接口中常用的地址包括 local 和 remote 的。

    client

    示例代码:

    package udp
    
    import (
        "log"
        "net"
    )
    
    func SendToServer(ip string, port string, content string) string {
      remoteAddr, err := net.ResolveUDPAddr("udp", ip+":"+port)
      if err != nil {
        log.Printf("ResolveUDPAddr: %v\n", err)
        return ""
      }
    
      conn, err := net.DialUDP("udp", nil, remoteAddr)
        if err != nil {
            log.Printf(
                "Connect to %v failed: %v\n",
                remoteAddr,
                err)
            return ""
        }
      defer conn.Close()
    
      _, err = conn.Write([]byte(content))
      if err != nil {
        log.Fatal("WriteToUDP: ", err)
        return ""
      }
    
      buffer := make([]byte, 1024)
        n, err := conn.Read(buffer)
        if err != nil {
            log.Printf(
                "Receive from %v failed: %v\n",
                remoteAddr,
                err)
            return ""
        }
    
      return string(buffer[:n])
    }
    
    1. 调用 DialUDP 创建 UDP Socket 的文件描述符 UDPConn
    func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error)
    

    从接口可以看出,

    这里需要注意,UDP 是无连接的,这里的 UDPConn 并非指一个“连接”,而是打开了一个 socket,并且设置好了源地址和目标地址。

    1. 调用 Write 方法向 socket 写入要发送给 server 的数据。
    func (c *conn) Write(b []byte) (int, error) 
    
    1. 调用 Read 读取来自 server 响应数据。
    func (c *conn) Read(b []byte) (int, error)
    

    server

    示例代码:

    package udp
    
    import (
        "log"
        "net"
        "strings"
    )
    
    func StartServer(port int) {
        addr := net.UDPAddr {
        Port: port,
        IP: net.IP{127, 0, 0, 1},
      }
    
        l, err := net.ListenUDP("udp", &addr)
        if err != nil {
            log.Fatal("ListenUDP: ", err)
            return
        }
    
        defer l.Close()
    
        for {
            data := make([]byte, 1024)
            // Echo all incoming data.
            n, remoteAddr, err := l.ReadFromUDP(data)
            if err != nil {
                log.Fatal("ReadFromUDP: ", err)
                return
            }
    
            log.Printf("Received %d bytes from %v", n, remoteAddr)
            s := strings.ToUpper(string(data[:n]))
            data = []byte(s)
    
            _, err = l.WriteToUDP(data, remoteAddr)
            if err != nil {
                log.Fatal("WriteToUDP: ", err)
          return
            }
    
        log.Printf("Send %v to %v\n", s, remoteAddr)
        }
    }
    
    1. 调用 ListenUDP 开始监听
     func ListenUDP(network string, laddr *UDPAddr) (*UDPConn, error)
    
    1. (在for循环中)调用 ReadFromUDP 阻塞式读取 client 发来的数据
    func (c *UDPConn) ReadFromUDP(b []byte) (int, *UDPAddr, error)
    
    1. (在for循环中)调用 WriteToUDP 将响应发送给 client
    func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error)
    

    下面的图总结了上述过程。

    The client-server application using UDP (in python)
    • AF_INET 表示 IPv4
    • SOCK_DGRAM 表示 UDP
    • 无连接

    2.7.2 Socket Programming with TCP

    与 UDP 不同,TCP 是一个基于连接的协议。client 和 server 必须先建立连接才能发送数据。当 client 创建 TCP socket 时,他需要指定 server 的 IP 地址和端口号,然后进行三次握手建立 TCP 连接(对应用程序是透明的)。

    Golang 中对 TCP 地址的定义为:

    type TCPAddr struct {
        IP   IP
        Port int
        Zone string // IPv6 scoped addressing zone
    }
    

    我们发现这与 UDP 地址定义完全一致。

    client

    示例代码:

    package tcp
    
    import (
        "log"
        "net"
    )
    
    func SendToServer(ip string, port string, content string) string {
      remoteAddr, err := net.ResolveTCPAddr("tcp", ip+":"+port)
      if err != nil {
        log.Printf("ResolveTCPAddr: %v\n", err)
        return ""
      }
    
      conn, err := net.DialTCP("tcp", nil, remoteAddr)
        if err != nil {
            log.Printf(
                "Connect to %v failed: %v\n",
                remoteAddr,
                err)
            return ""
        }
      defer conn.Close()
    
      _, err = conn.Write([]byte(content))
      if err != nil {
        log.Fatal("WriteToTCP: ", err)
        return ""
      }
    
      buffer := make([]byte, 1024)
        n, err := conn.Read(buffer)
        if err != nil {
            log.Printf(
                "Receive from %v failed: %v\n",
                remoteAddr,
                err)
            return ""
        }
    
      return string(buffer[:n])
    }
    
    1. 调用 DialTCP 创建 TCP 连接:
    func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)
    
    1. 调用 Write 方法向 socket 写入要发送给 server 的数据。
    func (c *conn) Write(b []byte) (int, error)
    
    1. 调用 Read 读取来自 server 的响应数据。
     func (c *conn) Read(b []byte) (int, error)
    

    可以看出,socket 读写并无区别,主要区别就是 DialTCPDialUDPDialTCP 会创建 TCP 连接。

    server

    示例代码:

    package tcp
    
    import (
        "log"
        "net"
        "strings"
    )
    
    func handleConnection(conn *net.TCPConn) {
        defer conn.Close()
    
        buffer := make([]byte, 1024)
        n, err := conn.Read(buffer)
        if err != nil {
            log.Fatal("ReadFromTCP: ", err)
            return
        }
    
        log.Printf("Received %d bytes from %v", n, conn.RemoteAddr())
        s := strings.ToUpper(string(buffer[:n]))
        buffer = []byte(s)
    
        _, err = conn.Write(buffer)
        if err != nil {
            log.Fatal("WriteToTCP: ", err)
            return
        }
    
        log.Printf("Send %v to %v\n", s, conn.RemoteAddr())
    }
    
    func StartServer(port int) {
        addr := net.TCPAddr {
        Port: port,
        IP: net.IP{127, 0, 0, 1},
      }
    
        l, err := net.ListenTCP("tcp", &addr)
        if err != nil {
            log.Fatal("ListenTCP: ", err)
            return
        }
    
        defer l.Close()
    
        for {
            conn, err := l.AcceptTCP()
            if err != nil {
                log.Fatal("AcceptTCP: ", err)
                return
            }
    
            go handleConnection(conn)
        }
    }
    
    1. 调用 ListenTCP 开始监听 TCP 连接请求
    func ListenTCP(network string, laddr *TCPAddr) (*TCPListener, error)
    
    1. (在for循环中)调用 AcceptTCP 为 TCP 连接创建 socket
    func (l *TCPListener) AcceptTCP() (*TCPConn, error)
    
    1. (在for循环中)调用 Read 读取 client 发来的数据
     func (c *conn) Read(b []byte) (int, error)
    
    1. (在for循环中)调用 Write 方法向 socket 写入要发送给 client 的数据。
    func (c *conn) Write(b []byte) (int, error)
    

    需要注意的是 ListenTCPAcceptTCP 的关系。这是 TCP 和 UDP 的关键区别。

    如下图所示,ListenTCP 创建了一个 "Welcoming socket" 来专门接受 TCP 连接请求。AcceptTCP会处理请求,并创建新的 socket 来与对应的 client 通信。因此它使用了两个 socket。而 UDP 则只使用了一个 socket。

    在与 client 通信结束后,我们并不需要关闭 "Welcoming socket"。只需要关闭这个 client-server 之间的连接即可。

    TCP server has two sockets

    下面的图总结了上述过程。

    The client-server application using TCP

    参考文献

    1. 深入Go UDP编程

    相关文章

      网友评论

          本文标题:2.7 Socket Programming: Creating

          本文链接:https://www.haomeiwen.com/subject/lftsghtx.html