在前几篇文章中,我们探讨了 IP、端口、DNS、HTTP、TCP 和 UDP 等网络核心概念与协议。这些协议定义了数据如何在网络中寻址、路由和传输。然而,这些理论知识最终需要通过编程实践来应用。Socket(套接字)便是连接应用层程序与传输层协议(如 TCP/UDP)的桥梁,是进行网络编程的核心接口。本文将深入介绍 Socket 的基本概念、工作原理,并通过代码示例演示如何使用 Socket 进行网络通信。

本系列其余几篇的目录:

Socket 概述

Socket 是一种抽象概念,它封装了网络通信所必需的底层协议细节(如 TCP 或 UDP),为应用程序提供了一套标准的、简单的接口来实现进程间通信。可以将其理解为应用程序与网络协议栈交互的“端点”或“插座”。通过这个“端点”,程序可以像读写文件一样发送和接收数据。

Socket 的核心要素

一个 Socket 通常由以下几个核心要素定义,它们共同构成了一个唯一的网络通信地址:

  1. 通信域 (Domain/Family):指定了 Socket 所使用的底层协议族。最常见的包括:
    • AF_INET:用于 IPv4 网络。
    • AF_INET6:用于 IPv6 网络。
    • AF_UNIX:用于本地进程间通信(同一台机器上的不同进程)。
  2. 套接字类型 (Type):定义了 Socket 的通信语义。主要类型有:
    • SOCK_STREAM:提供面向连接、可靠、有序的字节流服务,通常与 TCP 协议关联。数据像水流一样,无边界地传输。
    • SOCK_DGRAM:提供无连接、不可靠、但高效的数据报服务,通常与 UDP 协议关联。数据以独立的“包裹”(数据报)形式发送。
  3. 协议 (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”一文中已有详细介绍。在编程层面,这个过程体现为服务器端和客户端各自遵循的一套标准操作流程。

服务器端流程

服务器端的主要任务是监听来自客户端的连接请求,并与之建立连接,然后进行数据交换。典型的流程如下:

  1. 创建 Socket:调用系统调用(如 socket())创建一个新的 Socket。此时,Socket 还未与任何地址或端口绑定。
  2. 绑定地址和端口:调用 bind() 系统调用,将创建的 Socket 与服务器本机的一个 IP 地址和端口号绑定。这样,客户端才能通过这个地址和端口找到服务器。
  3. 监听连接:调用 listen() 系统调用,使 Socket 进入被动监听状态,等待客户端的连接请求。
  4. 接受连接:调用 accept() 系统调用。该调用会阻塞(暂停)程序执行,直到收到一个客户端的连接请求。收到请求后,accept() 会返回一个新的 Socket 文件描述符,这个新的 Socket 专门用于与该客户端进行数据通信。原来的监听 Socket 则继续保持监听状态。
  5. 数据交换:通过 accept() 返回的新 Socket,调用 send()recv()(或 write()read())等系统调用来发送和接收数据。
  6. 关闭连接:数据交换完成后,通信双方(客户端和服务器)都需要调用 close() 来关闭各自的 Socket,释放相关资源。这个过程涉及到 TCP 的“四次挥手”,同样在前文中有所描述。

客户端流程

客户端的任务是主动发起连接请求,并与服务器进行数据交换。

  1. 创建 Socket:与服务器端一样,客户端也需要调用 socket() 创建一个 Socket。
  2. 发起连接:调用 connect() 系统调用,向服务器的指定地址和端口发起连接请求。客户端需要知道服务器的 IP 地址和端口号。
  3. 数据交换:连接建立成功后,客户端就可以调用 send()recv() 等系统调用来发送和接收数据了。注意,客户端通常不需要 bind() 调用,操作系统会自动为其分配一个临时的源端口。
  4. 关闭连接:数据交换完成后,客户端调用 close() 关闭 Socket。

TCP Socket 工作流程图

下图展示了服务器端和客户端的完整交互流程:

S2 --> S3 --> S4
C1 --> C2
C2 -- "连接请求" --> S4
S4 -- "连接建立" --> C3
C3 <--> S5
C4 --> S6
-->