美文网首页
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