【Linux】Socket 编程预备知识

一、网络通信的核心定位:从主机到进程

1.1 源 IP 地址与目的 IP 地址的作用

  • IP(Internet Protocol)的核心功能是在互联网中唯一标识一台主机,确保数据能从源主机传输到正确的目的主机。后续会深入讲解 IP 分类(如 A、B、C 类地址)及子网划分等特性。
  • 数据传输的终点并非 "主机",而是 "主机内的用户进程"。例如:
    • 聊天数据需交给 QQ 进程,下载数据需交给迅雷进程,网页数据需交给浏览器进程。
    • 进程是用户在操作系统中的 "代理",只有将数据交付给目标进程,用户才能获取并使用数据。

1.2 从 "主机定位" 到 "进程定位" 的问题

  • 一台主机中会同时运行多个进程(如同时打开 QQ、浏览器、音乐软件),当数据到达目的主机后,操作系统无法仅凭 IP 地址判断应交给哪个进程。
  • 解决该问题需引入新的标识 ------端口号,实现 "IP 地址定位主机 + 端口号定位进程" 的二级定位体系。

二、端口号:进程的网络身份标识

2.1 端口号的基础定义

  • 端口号是传输层协议的核心字段,本质是一个 2 字节(16 位)的无符号整数,取值范围为 0-65535。
  • 核心作用:告知操作系统,当前接收的网络数据应分配给哪一个进程处理。
  • 唯一标识公式:IP 地址 + 端口号 = 套接字(Socket),可唯一确定互联网中某台主机上的某个网络进程。

2.2 端口号的范围划分

根据使用场景,端口号分为两类,避免冲突并明确功能定位:

端口号范围 类型 特点与用途
0-1023 知名端口号 由 IANA(互联网数字分配机构)统一分配,对应固定应用层协议。例如:HTTP 用 80、HTTPS 用 443、SSH 用 22、FTP 用 21。
1024-65535 动态端口号 由操作系统在客户端进程发起网络连接时随机分配,使用后释放,避免占用知名端口。

2.3 端口号与进程 ID(PID)的区别

  • 共性:均能唯一标识进程。
  • 差异:
    1. 作用范围不同:PID 是操作系统本地 的进程标识(如 Windows 的任务管理器、Linux 的 ps 命令查看),仅在本机有效;端口号是网络层面的进程标识,跨主机通信需依赖端口号。
    2. 绑定规则不同:一个进程可绑定多个 端口号(如一个服务器进程同时监听 80 和 443 端口),但一个端口号只能被一个进程绑定(避免数据分发混乱)。
    3. 设计目的不同:若用 PID 标识网络进程,会导致 "系统进程管理" 与 "网络通信" 强耦合(PID 会随进程重启变化),端口号的设计实现了两者解耦。

2.4 源端口号与目的端口号

  • 传输层协议(TCP/UDP)的数据包中,会同时包含源端口号目的端口号
    • 源端口号:标识数据的发送进程(如客户端的浏览器进程)。
    • 目的端口号:标识数据的接收进程(如服务器的 Web 进程)。
  • 两者结合,可清晰描述 "数据从哪个进程发出,要发给哪个进程",是网络通信的基础字段。

三、传输层协议:TCP 与 UDP 的核心差异

传输层位于 IP 层(网络层)之上,直接为应用层提供通信服务,核心协议为 TCP 和 UDP,两者设计理念完全不同,适用于不同场景。

3.1 TCP 协议(Transmission Control Protocol,传输控制协议)

  • 核心特性:
    1. 有连接:通信前需先建立 "三次握手" 连接,通信结束后需 "四次挥手" 释放连接,类似 "打电话先拨号,挂电话先道别"。
    2. 可靠传输:通过确认应答(ACK)、重传机制、流量控制、拥塞控制等,确保数据不丢失、不重复、按序到达
    3. 面向字节流:数据以连续的字节流形式传输,无固定 "数据包" 边界,需应用层自行定义数据分割规则(如 HTTP 的 Content-Length 字段)。

3.2 UDP 协议(User Datagram Protocol,用户数据报协议)

  • 核心特性:
    1. 无连接:无需建立连接,直接发送数据,类似 "发短信无需拨号",通信效率高。
    2. 不可靠传输:不保证数据到达,也不保证顺序,可能丢失或乱序,需应用层自行处理可靠性(如通过重传、校验等机制)。
    3. 面向数据报:数据以 "数据报" 为单位传输,每个数据报包含完整的源 / 目的端口号和数据,接收方一次接收一个完整数据报,有天然边界。

四、网络字节序:跨主机通信的 "数据格式统一标准"

4.1 字节序的问题来源

  • 多字节数据(如 2 字节的端口号、4 字节的 IP 地址)在内存中的存储顺序,不同主机可能不同,分为两种:
    • 大端字节序:低地址存储高字节(如数字 0x1234,内存低地址存 0x12,高地址存 0x34)。
    • 小端字节序:低地址存储低字节(如数字 0x1234,内存低地址存 0x34,高地址存 0x12)。
  • 若直接传输多字节数据,小端主机发送的数据到了大端主机,会被解析为错误值(如 0x1234 变成 0x3412),导致通信失败。

4.2 TCP/IP 协议的字节序规定

  • TCP/IP 协议强制要求:所有网络数据流必须采用大端字节序(称为 "网络字节序"),无论发送方和接收方是大端机还是小端机。
  • 转换规则:
    • 发送方:若本机是小端字节序,需先将数据转为大端字节序再发送;若本机是大端字节序,直接发送。
    • 接收方:若本机是小端字节序,需将接收的大端数据转为小端再处理;若本机是大端字节序,直接处理。

4.3 字节序转换函数

为保证程序可移植性(在大端 / 小端主机上均能正常运行),C 语言提供了以下标准库函数,用于主机字节序与网络字节序的转换:

函数名 功能 适用场景
htons() Host to Network Short 将 16 位短整数(如端口号)从主机字节序转为网络字节序
ntohs() Network to Host Short 将 16 位短整数从网络字节序转为主机字节序
htonl() Host to Network Long 将 32 位长整数(如 IPv4 地址)从主机字节序转为网络字节序
ntohl() Network to Host Long 将 32 位长整数从网络字节序转为主机字节序

4.4 关键注意点

  • 单字节数据(如char类型)无需转换,因为字节序不影响单个字节的存储和解析。
  • 所有涉及网络传输的多字节数据(端口号、IP 地址),必须通过上述函数转换,否则会出现 "数据解析错误"。

五、Socket 编程接口:网络通信的 "系统调用入口"

Socket(套接字)是操作系统提供的一套网络编程 API,封装了底层 TCP/IP 协议的细节,让开发者无需关注协议实现,只需调用接口即可完成网络通信。

5.1 核心 Socket API

1. int socket(int domain, int type, int protocol)
  • 功能:创建一个套接字(socket 文件描述符),用于后续网络通信。
  • 参数
    • domain(协议族 / 地址族):指定网络协议的类型,常用值:
      • AF_INET:IPv4 协议(最常用)。
      • AF_INET6:IPv6 协议。
      • AF_UNIX/AF_LOCAL:UNIX 域套接字(用于本地进程间通信)。
    • type(套接字类型):指定通信方式,常用值:
      • SOCK_STREAM:流式套接字,对应 TCP 协议(有连接、可靠传输)。
      • SOCK_DGRAM:数据报套接字,对应 UDP 协议(无连接、不可靠传输)。
      • SOCK_RAW:原始套接字,可直接操作底层协议(如 ICMP,需管理员权限)。
    • protocol(协议编号):指定具体协议,通常设为0(自动匹配type对应的默认协议):
      • typeSOCK_STREAM,默认协议为IPPROTO_TCP(TCP)。
      • typeSOCK_DGRAM,默认协议为IPPROTO_UDP(UDP)。
  • 返回值 :成功返回非负整数(socket 文件描述符),失败返回-1(并设置errno)。
2. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
  • 功能:将套接字与本地 IP 地址和端口号绑定(固定套接字的本地标识)。
  • 参数
    • sockfdsocket()返回的套接字文件描述符。
    • addr:指向struct sockaddr(或其子类,如struct sockaddr_in)的指针,存储本地 IP 和端口信息:
      • 对于 IPv4,需使用struct sockaddr_in,并强制转换为struct sockaddr*
        • sin_family:必须设为AF_INET
        • sin_port:端口号(需用htons()转换为网络字节序)。
        • sin_addr.s_addr:IP 地址(INADDR_ANY表示绑定所有本地网卡 IP,需用htonl()转换)。
    • addrlenaddr指向的结构体的字节长度(如sizeof(struct sockaddr_in))。
  • 返回值 :成功返回0,失败返回-1(并设置errno,常见错误如端口被占用)。
3. int listen(int sockfd, int backlog)
  • 功能:将 TCP 套接字转为监听状态,允许接收客户端连接请求(仅 TCP 服务器使用)。
  • 参数
    • sockfdbind()绑定后的 TCP 套接字文件描述符。
    • backlog:未完成连接队列(处于三次握手阶段的客户端)的最大长度:
      • 若超过此值,新的连接请求会被拒绝(客户端可能收到ECONNREFUSED错误)。
      • 实际有效长度可能受系统限制(如 Linux 中默认取backlog/proc/sys/net/core/somaxconn的最小值)。
  • 返回值 :成功返回0,失败返回-1(并设置errno)。
4. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
  • 功能:从 TCP 监听套接字的连接队列中取出一个已完成三次握手的客户端连接,返回新的套接字用于与该客户端通信(仅 TCP 服务器使用)。
  • 参数
    • sockfdlisten()后的监听套接字文件描述符。
    • addr:输出参数,指向struct sockaddr(或struct sockaddr_in),用于存储客户端的 IP 地址和端口号(可设为NULL,表示不关心客户端信息)。
    • addrlen:输入输出参数:
      • 输入:addr指向的结构体长度(如sizeof(struct sockaddr_in))。
      • 输出:实际存储的客户端地址信息长度(若addrNULL,可设为NULL)。
  • 返回值 :成功返回新的套接字文件描述符(用于与客户端通信),失败返回-1(并设置errno)。
  • 注意 :原监听套接字sockfd不受影响,可继续接收其他客户端连接。
5. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
  • 功能:TCP 客户端向服务器发起连接请求(触发三次握手)。
  • 参数
    • sockfdsocket()返回的 TCP 套接字文件描述符(客户端未绑定端口时,系统会自动分配动态端口)。
    • addr:指向struct sockaddr(或struct sockaddr_in)的指针,存储服务器的 IP 地址和端口号:
      • 对于 IPv4,sin_family设为AF_INETsin_portsin_addr.s_addr需转换为网络字节序。
    • addrlenaddr指向的结构体的字节长度(如sizeof(struct sockaddr_in))。
  • 返回值 :成功返回0(三次握手完成),失败返回-1(并设置errno,如服务器不可达、连接被拒绝)。
6. ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen)
  • 功能:从套接字接收数据,并获取发送方的地址信息(常用于 UDP 协议,因 UDP 无连接,需每次接收时确认发送方)。
  • 参数
    • sockfdsocket() 创建的套接字文件描述符(UDP 套接字或未连接的 TCP 套接字)。
    • buf:接收缓冲区,用于存储接收到的数据(用户需提前分配内存)。
    • len:接收缓冲区的最大长度(字节数),避免缓冲区溢出。
    • flags:接收方式标志,通常设为 0(默认阻塞接收),常用可选值:
      • MSG_DONTWAIT:非阻塞接收(若暂无数据,立即返回 -1 并设 errno=EAGAIN)。
      • MSG_PEEK:预览数据(数据仍保留在接收缓冲区,可再次读取)。
    • src_addr:输出参数,指向 struct sockaddr(或 struct sockaddr_in),用于存储发送方的 IP 地址和端口号(可设为 NULL,表示不关心发送方信息)。
    • addrlen:输入输出参数:
      • 输入:src_addr 指向的结构体长度(如 sizeof(struct sockaddr_in))。
      • 输出:实际存储的发送方地址信息长度(若 src_addrNULL,可设为 NULL)。
  • 返回值
    • 成功:返回实际接收到的字节数(若连接被关闭,返回 0)。
    • 失败:返回 -1(并设置 errno,如 EINTR 表示被信号中断)。
  • 注意
    • UDP 中,recvfrom 一次接收一个完整的数据报(若数据报长度超过 len,超出部分会被截断且不报错)。
    • 若用于 TCP 套接字,需先通过 connect 建立连接,此时 src_addraddrlen 可设为 NULL,功能等同于 recv
7. ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen)
  • 功能:通过套接字向指定目标地址发送数据(常用于 UDP 协议,因 UDP 无连接,需每次发送时指定接收方)。
  • 参数
    • sockfdsocket() 创建的套接字文件描述符(UDP 套接字或未连接的 TCP 套接字)。
    • buf:发送缓冲区,存储待发送的数据(用户需确保数据有效)。
    • len:待发送数据的字节数(若超过协议最大长度,可能导致数据被截断或发送失败)。
    • flags:发送方式标志,通常设为 0(默认阻塞发送),常用可选值:
      • MSG_DONTWAIT:非阻塞发送(若缓冲区满,立即返回 -1 并设 errno=EAGAIN)。
      • MSG_CONFIRM:告知底层协议,该地址有效(用于 UDP 多播等场景,优化路由)。
    • dest_addr:指向 struct sockaddr(或 struct sockaddr_in)的指针,存储接收方的 IP 地址和端口号:
      • 对于 IPv4,需指定 sin_family=AF_INETsin_portsin_addr.s_addr 需转换为网络字节序。
    • addrlendest_addr 指向的结构体的字节长度(如 sizeof(struct sockaddr_in))。
  • 返回值
    • 成功:返回实际发送的字节数(通常等于 len,除非被信号中断)。
    • 失败:返回 -1(并设置 errno,如 EINTR 表示被信号中断,EHOSTUNREACH 表示目标不可达)。
  • 注意
    • UDP 中,sendto 一次发送一个完整的数据报,发送成功仅表示数据已进入内核缓冲区,不保证接收方已收到(因 UDP 无确认机制)。
    • 若用于已通过 connect 建立连接的 TCP 套接字,dest_addraddrlen 可设为 NULL0,功能等同于 send

5.2 sockaddr 地址结构

Socket API 需适配多种网络协议(如 IPv4、IPv6、UNIX 域套接字),因此设计了通用的地址结构struct sockaddr,但实际使用时需根据协议类型转换为具体结构:

  • 通用结构(struct sockaddr :仅作为函数参数的 "占位符",定义如下(简化版):

    cpp 复制代码
    struct sockaddr {
        sa_family_t sa_family; // 协议族(如AF_INET表示IPv4,AF_INET6表示IPv6)
        char sa_data[14];      // 存储具体协议的地址信息,长度固定14字节
    };
  • IPv4 专用结构(struct sockaddr_in :更贴合 IPv4 地址格式,使用时需强制转换为struct sockaddr*类型传给 Socket API:

    cpp 复制代码
    struct sockaddr_in {
        sa_family_t sin_family;  // 协议族,必须设为AF_INET
        in_port_t sin_port;      // 端口号,需用htons()转为网络字节序
        struct in_addr sin_addr; // IPv4地址结构体,sin_addr.s_addr为32位IP地址(需用htonl()或inet_addr()转换)
        unsigned char sin_zero[8]; // 填充字段,需设为0,使结构体长度与sockaddr一致
    };
  • 关键说明:sockaddr的设计实现了 "一套 API 适配多协议",但实际开发中,IPv4 场景主要使用struct sockaddr_in,IPv6 场景使用struct sockaddr_in6

相关推荐
智者知已应修善业2 小时前
【c语言蓝桥杯计算卡片题】2023-2-12
c语言·c++·经验分享·笔记·算法·蓝桥杯
littlepeanut.top2 小时前
C++中将FlatBuffers序列化为JSON
开发语言·c++·json·flatbuffers
hansang_IR2 小时前
【题解】洛谷 P2330 [SCOI2005] 繁忙的都市 [生成树]
c++·算法·最小生成树
jenchoi4132 小时前
【2025-11-12】软件供应链安全日报:最新漏洞预警与投毒预警情报汇总
网络·安全·web安全·网络安全·npm
摘星|3 小时前
架设一台NFS服务器,并按照以下要求配置
linux·运维·服务器
做运维的阿瑞3 小时前
Linux环境变量持久化完全指南
linux·运维·服务器
FMRbpm3 小时前
链表中出现的问题
数据结构·c++·算法·链表·新手入门
天才奇男子3 小时前
从零开始搭建Linux Web服务器
linux·服务器·前端
xxtzaaa4 小时前
游戏被IP限制多开,如何在同一网络下用不同IP多开游戏?
网络·tcp/ip·游戏