2.7 Socket Programming: Creating Network Applications

找不到工作 · · 976 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

在网络应用开发中,开发者首先要做的一个决定是使用 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 Network Applications

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

976 次点击  
加入收藏 微博
1 回复  |  直到 2025-05-15 09:05:22
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传