分享
  1. 首页
  2. 文章

2.7 Socket Programming: Creating Network Applications

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

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

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

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏