计算机网络之七 - Socket 编程入门
在前几篇文章中,我们探讨了 IP、端口、DNS、HTTP、TCP 和 UDP 等网络核心概念与协议。这些协议定义了数据如何在网络中寻址、路由和传输。然而,这些理论知识最终需要通过编程实践来应用。Socket(套接字)便是连接应用层程序与传输层协议(如 TCP/UDP)的桥梁,是进行网络编程的核心接口。本文将深入介绍 Socket 的基本概念、工作原理,并通过代码示例演示如何使用 Socket 进行网络通信。
本系列其余几篇的目录:
- 计算机网络之一 - IP 与端口
- 计算机网络之二 - URL 与 DNS
- 计算机网络之三 - DHCP 与内网穿透
- 计算机网络之四 - 虚拟网卡与 WireGuard
- 计算机网络之五 - HTTP 与 HTTPS
- 计算机网络之六 - 可靠的 TCP 与高效的 UDP
Socket 概述
Socket 是一种抽象概念,它封装了网络通信所必需的底层协议细节(如 TCP 或 UDP),为应用程序提供了一套标准的、简单的接口来实现进程间通信。可以将其理解为应用程序与网络协议栈交互的“端点”或“插座”。通过这个“端点”,程序可以像读写文件一样发送和接收数据。
Socket 的核心要素
一个 Socket 通常由以下几个核心要素定义,它们共同构成了一个唯一的网络通信地址:
- 通信域 (Domain/Family):指定了 Socket 所使用的底层协议族。最常见的包括:
AF_INET
:用于 IPv4 网络。AF_INET6
:用于 IPv6 网络。AF_UNIX
:用于本地进程间通信(同一台机器上的不同进程)。
- 套接字类型 (Type):定义了 Socket 的通信语义。主要类型有:
SOCK_STREAM
:提供面向连接、可靠、有序的字节流服务,通常与 TCP 协议关联。数据像水流一样,无边界地传输。SOCK_DGRAM
:提供无连接、不可靠、但高效的数据报服务,通常与 UDP 协议关联。数据以独立的“包裹”(数据报)形式发送。
- 协议 (Protocol):在给定的域和类型下,进一步指定使用的具体协议。大多数情况下,此字段可以设为 0,表示使用默认协议(如
SOCK_STREAM
类型默认使用 TCP,SOCK_DGRAM
类型默认使用 UDP)。
Socket 与 TCP/UDP 的关系
Socket 本身并不是协议,它是一个 API(应用程序编程接口)。它允许应用程序使用 TCP 或 UDP 等传输层协议进行通信。
- TCP Socket:当创建一个
SOCK_STREAM
类型的 Socket 时,系统会自动使用 TCP 协议。TCP 的连接建立(三次握手)、可靠传输、流量控制等特性都由操作系统内核的 TCP/IP 协议栈处理,应用程序只需通过 Socket 接口进行读写操作。 - UDP Socket:当创建一个
SOCK_DGRAM
类型的 Socket 时,系统会自动使用 UDP 协议。UDP 的无连接、尽力而为传输特性同样由内核协议栈处理,应用程序通过 Socket 发送和接收独立的数据报。
理解 Socket 与 TCP/UDP 的关系,是掌握网络编程的关键。Socket 是操作接口,而 TCP/UDP 是底层的实现协议。
TCP Socket 编程流程
TCP 是一种面向连接的、可靠的协议。因此,使用 TCP Socket 进行通信前,客户端和服务器需要先建立连接。这个过程通常被称为“三次握手”,在“可靠的 TCP 与高效的 UDP”一文中已有详细介绍。在编程层面,这个过程体现为服务器端和客户端各自遵循的一套标准操作流程。
服务器端流程
服务器端的主要任务是监听来自客户端的连接请求,并与之建立连接,然后进行数据交换。典型的流程如下:
- 创建 Socket:调用系统调用(如
socket()
)创建一个新的 Socket。此时,Socket 还未与任何地址或端口绑定。 - 绑定地址和端口:调用
bind()
系统调用,将创建的 Socket 与服务器本机的一个 IP 地址和端口号绑定。这样,客户端才能通过这个地址和端口找到服务器。 - 监听连接:调用
listen()
系统调用,使 Socket 进入被动监听状态,等待客户端的连接请求。 - 接受连接:调用
accept()
系统调用。该调用会阻塞(暂停)程序执行,直到收到一个客户端的连接请求。收到请求后,accept()
会返回一个新的 Socket 文件描述符,这个新的 Socket 专门用于与该客户端进行数据通信。原来的监听 Socket 则继续保持监听状态。 - 数据交换:通过
accept()
返回的新 Socket,调用send()
和recv()
(或write()
和read()
)等系统调用来发送和接收数据。 - 关闭连接:数据交换完成后,通信双方(客户端和服务器)都需要调用
close()
来关闭各自的 Socket,释放相关资源。这个过程涉及到 TCP 的“四次挥手”,同样在前文中有所描述。
客户端流程
客户端的任务是主动发起连接请求,并与服务器进行数据交换。
- 创建 Socket:与服务器端一样,客户端也需要调用
socket()
创建一个 Socket。 - 发起连接:调用
connect()
系统调用,向服务器的指定地址和端口发起连接请求。客户端需要知道服务器的 IP 地址和端口号。 - 数据交换:连接建立成功后,客户端就可以调用
send()
和recv()
等系统调用来发送和接收数据了。注意,客户端通常不需要bind()
调用,操作系统会自动为其分配一个临时的源端口。 - 关闭连接:数据交换完成后,客户端调用
close()
关闭 Socket。
TCP Socket 工作流程图
下图展示了服务器端和客户端的完整交互流程:
S2 --> S3 --> S4C1 --> C2
C2 -- "连接请求" --> S4
S4 -- "连接建立" --> C3
C3 <--> S5
C4 --> S6
-->