欢迎光临
我们一直在努力

《Linux网络编程》吃透 Linux Socket 底层:端口、五元组、字节序与 API 详解

🔥小叶-duck:个人主页

 ❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》 《Linux系统从入门到实践》《Linux网络从入门到实践》 《Qt 方寸极境》 《MySQL》

未择之路,不须回头 已择之路,纵是荆棘遍野,亦作花海遨游


目录

前言

一、网络通信的本质:从主机寻址到进程间的数据交付

二、端口号:主机内部进程的网络身份唯一标识

  2.1 端口号的核心定义

  2.2 端口号的范围划分

  2.3 端口号 VS 进程 PID:为什么不用 PID 做网络标识?

  2.4 源端口与目的端口的分工差异

  2.5 内核实现逻辑:端口如何定位到对应业务进程

三、Socket:网络通信的核心抽象载体

  3.1 Socket 的核心定义

  3.2 四元组、五元组:区分网络连接的关键标识

  3.3 Linux 环境下 Socket 的内核本质

四、传输层两大核心协议:TCP 与 UDP 对比剖析

  4.1 TCP 协议(传输控制协议):面向连接的可靠传输机制

  4.2 UDP 协议(用户数据报协议):无连接的高效数据报传输

五、网络字节序:跨主机数据交互的统一编码规范

  5.1 大端、小端存储模式原理

  5.2 网络字节序的强制约束与设计初衷

  5.3 字节序转换系统函数原理

函数名记忆

六、Socket 核心 API 与地址结构体深度拆解

  6.1 Socket 地址结构体:C 语言实现的多态设计

    6.1.1 通用地址结构体:struct sockaddr

    6.1.2 IPv4 专用地址结构体:struct sockaddr_in

    6.1.3 UNIX 域套接字地址结构体:struct sockaddr_un

这套设计带来两大优势

  6.2 Socket 核心系统调用  API  逐行解读

    6.2.1 创建 Socket 文件描述符:socket ()

    6.2.2 绑定端口号与 IP 地址:bind ()

    6.2.3 开启 Socket 监听:listen ()

    6.2.4 接受客户端连接:accept ()

    6.2.5 发起连接请求:connect ()

七、高频面试考点梳理

结束语


前言

      学习 Linux 网络编程,很多人会直接上手调用 socket API 写代码,却忽略底层基础概念,只知调用接口,不懂背后内核如何完成数据收发。网络通信并不只是两台主机之间的数据传递,最终数据要交付给主机内部对应的进程进行处理。端口号如何定位进程、Socket 究竟是什么、四元组五元组起到什么作用,TCP 与 UDP 存在哪些本质差异,还有容易被忽视的网络字节序问题,都是套接字编程绕不开的前置知识。

      本文将从网络通信的本质入手,一步步讲解端口号、Socket 抽象、传输层两大协议、字节序规范,再深度解析 Socket 地址结构体与核心系统调用。帮助大家打通应用层到内核的认知,搞懂每一个接口背后的原理,为后续 TCP 协议详解、服务端实战开发打下扎实基础。

一、网络通信的本质:从主机寻址到进程间的数据交付

      在正式学习 Socket 之前,首先要理清一个关键问题:网络通信最终要达成的目标究竟是什么?不少人会简单认为,网络通信就是把数据从一台主机发送至另一台主机,这个理解并不完整。

  • IP 地址的作用是在网络环境中唯一标识一台主机,它只能保障报文可以抵达目标机器,这仅仅是通信过程里的中间手段,并非通信的最终目的。
  • 我们日常聊天、浏览网页、下载文件,本质都是应用程序之间完成数据交互。应用程序在操作系统内部以进程形态运行。也就是说,进程代表了用户在操作系统中的载体,只有数据成功交付到目标主机对应的进程,用户才可以获取数据,一次完整通信才算真正结束。

误区:报文抵达目标主机不等于通信完成,主机只是传输途中的载体。

      软件层面,人通过启动各类应用进程,接收、处理网络消息。进程就是运行状态下的程序。操作系统将收到的网络数据递交给对应进程,等价于把数据交给使用软件的用户。

      这里就出现了一个新问题:在系统中,同时会存在非常多的进程(浏览器、QQ、后台服务等),当数据到达目标主机之后,怎么转发给目标进程?这就要在网络的背景下,在系统中,标识主机的唯一性。       这也就是端口号要解决的核心问题,也是 Socket 编程的逻辑起点。

      同时我们可以得到一个贯穿网络编程始终的核心结论:表面看网络是机器与机器的数据交互,技术本质上,实际是跨主机的进程和进程之间的通信(IPC)。

  • 单机内的进程间通信,我们用管道、消息队列、共享内存;
  • 跨主机的进程间通信,我们用 Socket 套接字,它把​ IPC 的能力从单机扩展到了全球互联网。 ​

二、端口号:主机内部进程的网络身份唯一标识

      端口号是传输层协议的核心内容,是实现「数据精准交付给进程」的关键。

  2.1 端口号的核心定义

      端口号(port)是传输层协议的内容。

  • 数据类型与范围:端口号是一个2 字节 16 位的无符号整数,取值范围是0 ~ 65535;
  • 核心作用:是在一台主机内部对网络进程做唯一标记,告诉操作系统,收到的网络报文应当交给哪一个进程处理。
  • 一个端口号只能被一个进程占用。
  • 核心结论:IP 地址搭配端口号,就可以在互联网范围内,精准定位某台主机上的某一个网络进程。

  2.2 端口号的范围划分

      操作系统对端口号的使用做了明确的范围划分,主要分为两大类:

端口范围分类核心说明典型示例
0‑1023 知名端口号 预留给主流应用层协议,普通用户进程没有权限绑定 HTTP(80)、FTP(21)、SSH(22)、HTTPS(443)
1024‑65535 动态分配端口号 操作系统自动分配,客户端临时端口取自该区间,用户程序也可自由绑定 浏览器、客户端 APP 临时端口,自定义服务端口

  2.3 端口号 VS 进程 PID:为什么不用 PID 做网络标识?

      不少人会产生疑问:PID 同样可以在本机唯一标识进程,为什么网络体系还要额外设计端口号?

      这是网络分层设计里解耦思想的典型体现,主要有三点原因:

  • 可控性不同:PID 由操作系统动态分配,进程重启之后 PID 就会改变,开发者无法人为固定。而服务端服务需要固定端口对外提供服务,例如 HTTP 默认 80 端口,客户端需要预先知晓端口才能建立通信,PID 满足不了固定标识的需求。

  • 架构解耦:PID 属于进程管理模块的概念,端口号属于网络协议栈概念。倘若直接拿 PID 充当网络标识,会造成进程管理与网络模块深度耦合。一旦操作系统 PID 分配逻辑改动,网络协议栈也要跟着改动,违背分层设计原则。

  • 灵活度差异:并不是全部进程都要参与网络交互。端口号允许进程自主选择是否接入网络、绑定哪个端口;一个进程也能够同时绑定多个端口,但一个端口同一时刻只能归属一个进程。这种一对多的映射能力,PID 无法实现。

  2.4 源端口与目的端口的分工差异

TCP、UDP 报文头部都会携带两组端口字段:

  • 源端口号:标记发送方本机发起通信的进程。
  • 目的端口号:标记接收方机器上需要处理报文的目标进程。

      这两个字段就是传输层实现报文分发给进程的关键。报文抵达目标主机传输层之后,内核读取目的端口号,就可以把数据递交给本机对应的进程,完成数据从主机到进程的交付。

  2.5 内核实现逻辑:端口如何定位到对应业务进程

      很多同学只知道端口号可以对应网络进程,却不清楚内核内部的映射实现逻辑。在 Linux 内核中依靠哈希表,完成端口到 Socket、再到进程的快速检索,核心结构体为 inet_hashinfo,定义头文件:include/net/inet_hashtables.h。

      inet_hashinfo 内部维护三张不同用途的哈希表,分工处理不同网络场景:

// Linux内核 inet_hashinfo 精简示意
struct inet_hashinfo {
// 1.已连接哈希表:存放ESTABLISHED已完成连接的socket
struct inet_ehash_bucket *ehash;
// 2.绑定哈希表:以本地端口为key,管理bind绑定的socket,用于端口占用检测
struct inet_bind_hashbucket *bhash;
// 3.监听哈希表:以本机IP+端口作为key,保存LISTEN监听socket,处理客户端新建连接
struct inet_listen_hashbucket *lhash2;
};

内核报文递交给进程完整流程

  • 网卡接收报文,经过链路层、网络层校验处理,向上递交传输层;
  • 传输层解析报文,提取五元组信息:源 IP、源端口、目的 IP、目的端口、传输层协议;
  • 调用 __inet_lookup,优先查询 ehash 已连接哈希表,用五元组计算哈希,快速匹配对应的 Socket 结构体;
  • 如果是新建连接请求(SYN 报文),则访问 lhash2 监听哈希表,根据目的端口查找处于 LISTEN 状态的监听 Socket;
  • 定位 Socket 之后,内核顺着 Socket 关联的文件对象,找到持有该 socket 的进程 task_struct,最终把数据交给对应进程,完成报文分发。
  • 三、Socket:网络通信的核心抽象载体

          弄懂 IP 地址与端口号之后,就可以进一步理解 Socket 的底层本质。

      3.1 Socket 的核心定义

          IP 地址用来定位互联网里唯一一台主机,端口号定位这台主机内部唯一一个网络进程。把IP 地址 + 端口号组合在一起,就可以在全网范围唯一确定一个正在通信的进程,该组合就叫做套接字(Socket)。

          Socket 翻译过来本意是 “插座”,这个比喻十分形象:       网络通信好比家里电器通电。IP 地址代表家庭住址,端口号就是墙上插座编号;进程相当于电器,进程绑定 Socket 就如同电器插上插座,借助电网也就是互联网,和远端设备完成数据交互。

      3.2 四元组、五元组:区分网络连接的关键标识

    • 四元组:{源 IP,源端口,目的 IP,目的端口},唯一标识互联网上进行交互的两个通信进程,代表通信两端端点。
    • 五元组:在四元组基础上新增传输层协议(TCP/UDP)。Linux 内核依靠五元组,完整标记一条双向网络连接。

          举个实例:本机 192.168.1.100 浏览器访问 CSDN 服务器 39.106.226.131:80,操作系统给浏览器分配临时端口 49152。 这条连接完整五元组:{192.168.1.100,49152,39.106.226.131,80,TCP},该五元组在全网具备唯一性。

      3.3 Linux 环境下 Socket 的内核本质

          Linux 秉持一切皆文件的设计理念,Socket 同样遵循这套思想。

    • 用户态视角:Socket 对外表现为一个文件描述符 fd。程序直接调用 read、write 系统调用读写 fd,就可以完成网络数据收发。
    • 内核态视角:Socket 是内核内部复杂结构体,保存 IP、端口、协议类型、收发缓冲区、连接状态等全套网络信息,充当用户进程和内核协议栈之间的桥梁。

    四、传输层两大核心协议:TCP 与 UDP 对比剖析

      4.1 TCP 协议(传输控制协议):面向连接的可靠传输机制

          TCP 全称 Transmission Control Protocol,属于面向连接、可靠、面向字节流的传输层协议,主要特点如下:

  • 面向连接:通信双方正式传输数据前,要通过三次握手完成连接建立;结束通信时执行四次挥手释放连接。类比打电话:必须拨号接通之后,双方才可以交谈。

  • 可靠传输:依靠确认应答、超时重传、序列号、校验和整套机制,保障数据不会丢失、不会重复、按序抵达对端,规避乱序、丢包问题。

  • 面向字节流:数据以无边界的字节流进行传输,类似自来水。发送方一次性发出 1000 字节,接收端可以分 10 次,每次读取 100 字节。发送和接收的次数没有一一对应关系,业务层需要自行划分消息边界。

  •   4.2 UDP 协议(用户数据报协议):无连接的高效数据报传输

          UDP 全称 User Datagram Protocol,属于无连接、不可靠、面向数据报的传输层协议,核心特性:

  • 无连接 通信之前不用预先建立连接,只要知道目标 IP 与端口,就可以直接发送报文。类比寄邮件,不需要提前和收件人沟通确认,直接投递即可。

  • 不可靠传输 UDP 没有应答确认、超时重传逻辑。只会尽力把报文发出去,既不保证对方一定收到,也不能保证报文按顺序到达。

  • 面向数据报 传输以完整报文作为最小单位,收发次数严格对应。发送端发出一份 100 字节报文,接收端必须一次性完整读取这 100 字节,不能拆分多次读取,原生保留消息边界。

  • 💡重要提醒:       可靠与不可靠是协议本身的属性,不等同于好坏。 TCP 实现可靠要付出开销,需要维护连接状态,因此传输开销更大,性能会有所损耗; UDP 不保障可靠,但头部短小,传输效率高、延迟更低。

    • 适合 TCP:看重数据完整性,例如文件传输、转账业务、HTTP 网页通信。
    • 适合 UDP:追求低延迟,可以接受少量丢包,例如直播、语音视频通话、网络游戏。

    五、网络字节序:跨主机数据交互的统一编码规范

          不同 CPU 架构的主机,多字节数据在内存里存储顺序不一样,也就是大小端问题。如果没有统一标准,异构主机互相通信,解析多字节数值就会错乱。网络字节序就是用来解决该问题的统一规范。

      5.1 大端、小端存储模式原理

    内存中存放多字节整数,相对于内存地址,分为两种存储模式:

    • 小端序(Little‑Endian):低权值字节放在低内存地址,高权值字节放在高内存地址。记忆口诀:小小小(低权值字节、低内存地址->小端序)。
    • 大端序(Big‑Endian):低权值字节放在高内存地址,高权值字节放在低内存地址,阅读顺序和人类习惯一致。

          以 4 字节整数 0x1234abcd 举例内存布局:

    内存地址大端序字节小端序字节
    0x0000(低地址) 0x12 0xCD
    0x0001 0x23 0xAB
    0x0002 0xAB 0x23
    0x0003(高地址) 0xCD 0x12

    简要说明:

    • 大端序:高位字节(0x12)存储在低地址,低位字节(0xCD)存储在高地址。
    • 小端序:低位字节(0xCD)存储在低地址,高位字节(0x12)存储在高地址。

          现在 x86 架构 PC、服务器,大部分 ARM 手机、嵌入式设备,默认都是小端序;只有部分网络设备、大型机使用大端序。跨机器通信,大小端差异客观存在,必须处理。

      5.2 网络字节序的强制约束与设计初衷

          TCP/IP 协议硬性规定:网络数据流一律使用大端字节序(低地址存放高字节)。

  • 发送端:不管本机是大端还是小端,发送多字节数值前,必须把主机字节序转为网络字节序(大端)。
  • 接收端:无论本机架构,收到网络多字节数据后,要把网络字节序转回本机主机字节序,再做业务解析。
  • 为什么网络选用大端序?

          网络发送规则:内存低地址的数据优先向外发送。大端模式下最先发出的是数值高字节,抓包观察报文时,阅读逻辑更贴合人类习惯,可读性更好。

      5.3 字节序转换系统函数原理

          为实现代码跨平台可移植,保证同一份 C 代码在大端和小端主机上都能正常运行,Linux 提供一套标准字节序转换接口,头文件:<arpa/inet.h>

    函数原型与核心说明:

    #include <arpa/inet.h>

    // 主机序 → 网络序,32位长整数,多用于IP
    uint32_t htonl(uint32_t hostlong);
    // 主机序 → 网络序,16位短整数,多用于端口号
    uint16_t htons(uint16_t hostshort);

    // 网络序 → 主机序,32位长整数
    uint32_t ntohl(uint32_t netlong);
    // 网络序 → 主机序,16位短整数
    uint16_t ntohs(uint16_t netshort);

    函数名记忆

    • h = host,主机字节序
    • n = network,网络字节序
    • l = long,32 位无符号整数
    • s = short,16 位无符号整数

          举例:htons → host‑to‑network‑short,把 16 位端口从主机序转为网络字节序。

    函数底层逻辑

          这一组函数会自适应本机大小端:

    • 如果本机是小端序:内部执行字节翻转,完成大小端转换;
    • 如果本机是大端序:不需要改动,参数直接原样返回。

          开发规范:写网络代码,无论你清楚当前机器是大端还是小端,都要调用转换函数。不需要手动检测本机字节序,保证一份代码可以在不同架构机器正常编译运行。

    ⚠️重要提示:       单字节字符串不需要字节序转换。大小端只作用于 short、int、long 这类占用多个字节的数值类型;单个字节不存在存储顺序问题,而发送字符串时,是一个字母一个字母独立发送的(单字节),接收也是单字节接收。

    六、Socket 核心 API 与地址结构体深度拆解

      6.1 Socket 地址结构体:C 语言实现的多态设计

          Socket API 属于一套抽象通用接口,除 IPv4 之外,还支持 IPv6、UNIX 域套接字等多种地址格式。为实现同一套接口适配各类地址,系统在 C 语言层面实现多态思想,主要依靠三类地址结构体完成。

        6.1.1 通用地址结构体:struct sockaddr

    // 通用地址结构体,所有Socket API的参数都用这个类型
    struct sockaddr
    {
    sa_family_t sa_family; // 16位地址类型,标识是哪种地址协议
    char sa_data[14];// 14字节地址数据,不同协议有不同的格式
    };

    • sa_family:地址族,标记地址协议类型,常用取值:
      • AF_INET:代表 IPv4 协议
      • AF_UNIX/AF_LOCAL:UNIX 域套接字,用于本机进程间通信

        6.1.2 IPv4 专用地址结构体:struct sockaddr_in

          头文件:<netinet/in.h>,专门用于 IPv4 网络通信,内存大小和 sockaddr 保持一致。

    #include <netinet/in.h>

    // IPv4地址结构体,和sockaddr长度完全一致
    struct sockaddr_in
    {
    sa_family_t sin_family; // 地址类型,必须填 AF_INET
    in_port_t sin_port; // 16位端口号,**必须使用网络字节序**
    struct in_addr sin_addr; // 32位IP地址,**必须使用网络字节序**
    unsigned char sin_zero[8]; // 8字节填充位,保证结构体大小与sockaddr对齐
    };

        6.1.3 UNIX 域套接字地址结构体:struct sockaddr_un

          头文件:<sys/un.h>,用于同一台机器上进程之间本地通信,不经过网络协议栈。

    #include <sys/un.h>

    // UNIX域套接字地址结构体,用于本地进程间通信
    struct sockaddr_un
    {
    sa_family_t sun_family; // 地址类型,必须填 AF_UNIX
    char sun_path[108]; // 本地socket文件路径名
    };

    设计精髓:C 语言模拟多态的原理

          Socket 全部系统调用接收的参数类型都是 struct sockaddr *。 开发时,我们根据通信协议选择对应的专用结构体,例如 sockaddr_in,把 IP、端口等字段填充完毕后,强制转换成 struct sockaddr * 传入函数。       内核读取结构体首个成员 sa_family,以此判断真实地址类型,再解析后面的内存数据。 本质就是基类指针接收派生类对象,在没有面向对象语法的 C 语言中,模拟实现多态效果。

    这套设计带来两大优势

  • 接口统一:同一套 Socket API 兼容 IPv4、IPv6、UNIX 域套接字,开发者不用记忆多套函数,降低学习成本。
  • 扩展性优秀:后续增加新的地址协议,不需要改动原有系统调用接口,只新增一套对应的地址结构体即可。
  •   6.2 Socket 核心系统调用  API  逐行解读

          Socket 编程分为服务端与客户端两大角色,核心共有 5 个系统 API,下面逐个拆解函数原型、参数、返回值与代码示例。

        6.2.1 创建 Socket 文件描述符:socket ()

    函数原型:

    #include <sys/socket.h>
    int socket(int domain, int type, int protocol);

    功能:生成 socket 文件描述符,在内核中创建 socket 内核对象,为后续网络通信做好初始化准备。TCP、UDP,不管是服务端还是客户端,第一步都要调用该函数。

    参数解析

    • domain:地址族 / 协议族,选定底层通信协议
      • AF_INET:IPv4 协议,最常用
      • AF_INET6:IPv6 协议
      • AF_UNIX / AF_LOCAL:UNIX 域套接字,本机进程间通信
    • type:套接字类型,确定传输特性
      • SOCK_STREAM:流式套接字,对应 TCP,可靠、面向连接、字节流传输
      • SOCK_DGRAM:数据报套接字,对应 UDP,无连接、不可靠报文传输
    • protocol:子协议编号,一般填0,系统自动根据 domain 和 type 匹配对应协议。

    返回值

    • 成功:返回非负整数,socket 文件描述符
    • 失败:返回-1,全局变量errno记录错误原因

    使用示例:

    // 创建一个TCP Socket,基于IPv4协议
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
    perror("socket create failed");
    exit(1);
    }

        6.2.2 绑定端口号与 IP 地址:bind ()

    函数原型:

    int bind(int socket, const struct sockaddr *address, socklen_t address_len);

    功能:把 socket 文件描述符和指定 IP、端口进行绑定。服务端必须调用,客户端一般不需要手动 bind。

    参数解析

    • socket:socket () 返回的文件描述符
    • address:填充完毕的地址结构体指针,需要强制转为struct sockaddr *通用类型
    • address_len:地址结构体字节大小,例如sizeof(struct sockaddr_in)

    返回值:

    • 成功:返回0
    • 失败:返回-1,设置 errno;常见错误:端口被占用、权限不足(绑定 1023 以下端口需要 root 权限)

    使用示例:

    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET; // IPv4协议
    serv_addr.sin_port = htons(8080); // 端口号,主机转网络序
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定本机所有网卡IP

    // 将Socket与地址绑定
    if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
    perror("bind failed");
    close(sockfd);
    }

          关键说明:INADDR_ANY代表监听本机全部网卡 IP,外部客户端访问本机任意 IP 的该端口,都可以被 socket 接收,是服务端最常用配置。

        6.2.3 开启 Socket 监听:listen ()

    函数原型:

    int listen(int socket, int backlog);

    功能:把 socket 切换为被动监听模式,内核开始接收客户端连接请求。只用于 TCP 服务端;UDP、客户端不需要调用 listen。

    参数解析:

    • socket:经过 bind 绑定地址后的 socket 文件描述符
    • backlog:已完成三次握手的连接队列最大长度,代表同一时刻最多多少个已经建立完成、等待 accept 处理的连接。

    返回值:

    • 成功:返回0
    • 失败:返回-1,设置 errno

    使用示例:

    // 开启监听,backlog设置为128
    if (listen(sockfd, 128) < 0)
    {
    perror("listen failed");
    close(sockfd);
    exit(1);
    }

        6.2.4 接受客户端连接:accept ()

    函数原型:

    int accept(int socket, struct sockaddr* address, socklen_t* address_len);

    功能:从内核已完成三次握手的连接队列取出一条连接,生成全新 socket 文件描述符专门和该客户端通信。仅 TCP 服务端使用。

    参数解析:

    • socket:listen 使用的监听 socket fd
    • address:输出参数,用来存放对端客户端 IP 与端口信息
    • address_len:输入输出参数;传入是地址结构体大小;函数返回后,里面是实际拷贝的地址长度

    返回值:

    • 成功:返回全新非负 socket fd,专门用来和当前客户端收发数据
    • 失败:返回-1,设置 errno

    使用示例:

    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);

    // 阻塞等待客户端连接
    int client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
    if (client_fd < 0)
    {
    perror("accept failed");
    close(sockfd);
    exit(1);
    }

          核心要点:accept 返回的新 fd 才是和客户端通信的 socket;原始监听 socket 会继续保持监听,持续等待新客户端接入。

        6.2.5 发起连接请求:connect ()

    函数原型:

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    功能:TCP 客户端向服务端发起连接请求,完成三次握手建立连接。只由 TCP 客户端调用。

    参数解析:

    • sockfd:客户端 socket 函数创建出的文件描述符
    • addr:服务端地址结构体,填充服务端 IP、端口
    • addrlen:地址结构体所占字节大小

    返回值:

    • 成功:返回0,连接建立完成,可以读写 sockfd 收发数据
    • 失败:返回-1,设置 errno;常见:服务端不可达、端口未开放、连接超时

    使用示例:

    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);
    // 填充服务端IP地址
    inet_pton(AF_INET, "192.168.1.100", &serv_addr.sin_addr);

    // 向服务端发起连接
    if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
    perror("connect failed");
    }

    七、高频面试考点梳理

    • 网络通信的本质

          跨主机之间的进程间通信。数据包到达目标主机只是中间步骤,最终目标是将数据交给主机上对应的业务进程。

    • 端口号的核心作用

          16 位无符号整数,用来区分同一主机内不同网络进程。IP+端口 可以在全网唯一定位一个网络进程。

    • 端口号与 PID 的区别,为什么不用 PID 做网络标识

          PID 进程 ID 会随进程启停不断变化,不能固定;端口可以固定绑定服务,实现网络层和进程 ID 解耦,部署更灵活可控,因此不直接使用 PID。

    • Socket 的本质

          逻辑上 IP+端口 代表一个 socket 端点;Linux 实现层面,socket 本质是文件描述符,充当用户进程和内核协议栈交互的桥梁。

    • 四元组与五元组

          四元组:包含源 IP、源端口、目的 IP、目的端口,标识通信两端;       五元组:额外增加协议类型 (TCP/UDP),能够唯一标识一条网络连接。

    • TCP 与 UDP 的核心特性以及适用场景

          TCP:面向连接、可靠传输、字节流;适合文件传输、网页等对数据可靠要求高的场景。       UDP:无连接、不可靠、数据报;适合直播、DNS、游戏等追求低延迟,可容忍少量丢包场景。

    • 网络字节序

          TCP/IP 协议统一采用大端序作为网络字节序。主机存在大小端差异,发送数据前需要将主机序转为网络大端序,接收之后再转回主机序;主要转换函数:htons、htonl、ntohs、ntohl。

    • Socket 地址结构体的设计思想

          借助通用基结构体struct sockaddr,利用 C 语言强制类型转换模拟多态,同一套系统调用接口,就可以兼容 IPv4、IPv6、UNIX 域套接字多种地址类型。

    • Socket 核心 API 的作用与使用场景

    • socket:创建 socket 文件描述符,客户端、服务端都要调用
    • bind:绑定 IP 和端口,主要服务端使用
    • listen:开启监听,仅 TCP 服务端调用
    • accept:获取已建立连接,仅 TCP 服务端调用,返回新 fd 用于通信
    • connect:发起三次握手建立连接,仅 TCP 客户端调用
    • 内核如何通过端口号找到进程

          内核依靠inet_hashinfo维护三张哈希表存储 socket 相关信息;网络报文抵达主机后,内核根据报文中 IP、端口查询哈希表,定位对应的 socket 对象,最终将数据交付给用户进程。

    结束语

          本篇我们从网络通信的本质出发,依次讲解了端口号的作用,厘清了端口与 PID 的区别,理解了内核依靠端口把报文交付给对应进程的底层逻辑。接着认识了 Socket 这一网络编程抽象,掌握四元组、五元组如何唯一标识一条网络连接,剖析了 Linux 下 Socket 在内核中的真实形态。随后对比了传输层 TCP、UDP 两大协议的核心特性,解释了网络字节序存在的意义,搞懂大小端差异以及字节序转换函数的用法,最后对 Socket 地址结构体和核心系统调用做了完整解析。

          端口、Socket、字节序、系统调用,是 Linux 网络编程的地基,很多后续 TCP 连接、IO 模型、服务端开发的内容都建立在这些知识之上。把这部分基础吃透,后续学习套接字编程、手写服务端代码时,才不会只停留在调用 API 的表层,能够看懂背后内核的行为。

    赞(0)
    未经允许不得转载:171主机测评 » 《Linux网络编程》吃透 Linux Socket 底层:端口、五元组、字节序与 API 详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址