Linux Socket 编程筑基:从底层本质到核心 API,一文吃透 Socket 预备知识


🔥草莓熊Lotso: 个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!


🎬 博主简介:


文章目录

  • 前言:
  • [一. 网络通信的本质:从主机到进程的核心逻辑](#一. 网络通信的本质:从主机到进程的核心逻辑)
  • [二. 端口号:主机内进程的唯一网络标识](#二. 端口号:主机内进程的唯一网络标识)
    • [2.1 端口号的核心定义](#2.1 端口号的核心定义)
    • [2.2 端口号 VS 进程 PID:为什么不用 PID 做网络标识?](#2.2 端口号 VS 进程 PID:为什么不用 PID 做网络标识?)
    • [2.3 端口号的范围划分](#2.3 端口号的范围划分)
    • [2.4 源端口号与目的端口号](#2.4 源端口号与目的端口号)
    • [2.5 内核底层:如何通过端口号找到对应进程?(主机是如何把报文交给进程的,这里我们主要是先简单理解下)](#2.5 内核底层:如何通过端口号找到对应进程?(主机是如何把报文交给进程的,这里我们主要是先简单理解下))
  • [三. Socket:网络通信的核心基石](#三. Socket:网络通信的核心基石)
    • [3.1 Socket 的核心定义](#3.1 Socket 的核心定义)
    • [3.2 四元组与五元组:唯一标识网络通信](#3.2 四元组与五元组:唯一标识网络通信)
    • [3.3 Linux 中 Socket 的本质](#3.3 Linux 中 Socket 的本质)
  • [四. 传输层两大核心协议:TCP 与 UDP](#四. 传输层两大核心协议:TCP 与 UDP)
    • [4.1 TCP 协议(传输控制协议)](#4.1 TCP 协议(传输控制协议))
    • [4.2 UDP 协议(用户数据报协议)](#4.2 UDP 协议(用户数据报协议))
  • [五. 网络字节序:跨主机通信的统一标准](#五. 网络字节序:跨主机通信的统一标准)
    • [5.1 大小端的核心定义](#5.1 大小端的核心定义)
    • [5.2 网络字节序的强制规定](#5.2 网络字节序的强制规定)
    • [5.3 字节序转换函数深度解析](#5.3 字节序转换函数深度解析)
  • [六. Socket 核心 API 与地址结构体深度解析](#六. Socket 核心 API 与地址结构体深度解析)
    • [6.1 Socket 地址结构体:C 语言实现的多态设计](#6.1 Socket 地址结构体:C 语言实现的多态设计)
    • [6.2 Socket 核心 API 逐行解析](#6.2 Socket 核心 API 逐行解析)
  • [七. 核心考点总结(面试高频)](#七. 核心考点总结(面试高频))
  • 结尾:

前言:

做 Linux C/C++ 后端开发的同学,几乎每天都在和 Socket 打交道:写 TCP 服务端、调 HTTP 接口、实现跨主机通信,很多人能熟练调用socketbindlisten这些 API,但面试被问到这些核心问题时,却常常卡壳:为什么有了能唯一标识主机的 IP 地址,还需要端口号?直接用 PID 标识进程不行吗?常说的 Socket 到底是什么?为什么行业内公认「IP+Port=Socket」?网络字节序为什么强制规定为大端?不做字节序转换会有什么问题?Socket API 为什么要设计通用的struct sockaddr结构体,而不是直接用 IPv4 专用的sockaddr_in?其实,Socket 编程的上限,从来不是你能背下多少 API,而是你对底层网络本质的理解深度。本文就从网络通信的本质出发,一步步拆解端口号、Socket 核心定义、网络字节序、地址结构体与核心 API 的底层逻辑,结合 Linux 内核源码,把 Socket 编程预备知识讲得明明白白。


一. 网络通信的本质:从主机到进程的核心逻辑

在学习 Socket 之前,我们必须先搞懂一个最核心的问题:网络通信的最终目标到底是什么?

很多人会回答「把数据从一台主机发送到另一台主机」,但这个答案是错的。

  • IP 地址在网络中唯一标识一台主机,它只能保证数据能从源主机送达目标主机,但这只是手段,不是目的。
  • 我们聊天、刷网页、下载文件,本质是人在使用应用程序通信 ,而应用程序在操作系统中以进程的形式运行。换句话说,进程是人在系统中的代表,只有把数据交给目标主机内的对应进程,人才能真正拿到数据,通信才算完成。


这里就出现了一个新问题:一台主机上同时运行着成百上千个进程(浏览器、QQ、后台服务等),当数据到达目标主机后,操作系统该如何精准地把数据交给对应的进程?

这就是端口号要解决的核心问题,也是 Socket 编程的逻辑起点。

同时我们可以得到一个贯穿网络编程始终的核心结论:网络通信的本质,是跨主机的进程间通信(IPC)。

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

二. 端口号:主机内进程的唯一网络标识

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

2.1 端口号的核心定义

  • 数据类型与范围 :端口号是一个2 字节 16 位的无符号整数 ,取值范围是0 ~ 65535
  • 核心作用:唯一标识主机内的一个网络进程,告诉操作系统,当前收到的网络数据,应该交给哪一个进程来处理;
  • 核心结论IP 地址 + 端口号,能够唯一标识互联网中某一台主机上的某一个进程。



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

很多同学会有疑问:PID 也能唯一标识主机内的一个进程,为什么还要单独设计端口号?

这是网络架构设计中「解耦合」思想的经典体现,核心原因有 3 点:

  1. 可控性差异:PID 是操作系统动态分配的,进程重启后 PID 会发生变化,用户无法主动固定;而服务器的端口号必须是固定的(比如 HTTP 服务固定用 80 端口),客户端必须提前知道端口号才能发起通信,PID 完全无法满足这个需求。
  2. 架构解耦合:PID 属于操作系统进程管理的系统概念,端口号属于网络通信的网络概念。如果用 PID 做网络标识,会让系统进程管理和网络协议栈强耦合,一旦系统层的 PID 机制发生变化,整个网络模块都要跟着修改,违背了分层设计的核心思想。
  3. 灵活性差异 :不是所有进程都需要进行网络通信,端口号让进程可以自主选择是否参与网络通信、绑定哪个端口;同时一个进程可以绑定多个端口号,但一个端口号同一时间只能被一个进程占用,这种一对多的映射关系,PID 无法灵活实现。

2.3 端口号的范围划分

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

端口范围 分类 核心说明 典型示例
0 - 1023 知名端口号 固定分配给广为使用的应用层协议,普通用户进程无权绑定 HTTP(80)、FTP(21)、SSH(22)、HTTPS(443)
1024 - 65535 动态分配端口号 由操作系统动态分配,客户端程序的端口号默认从这个范围分配,用户进程可自由绑定 浏览器、客户端 APP 的临时端口,自定义服务的非知名端口

2.4 源端口号与目的端口号

在 TCP/UDP 协议的报文段中,固定包含两个端口号字段:

  • 源端口号:标识发送端发起通信的进程;
  • 目的端口号:标识接收端要交付的目标进程。

这两个字段是传输层分用的核心依据:当数据到达目标主机的传输层,内核会根据目的端口号,精准地把数据交付给对应的进程。

2.5 内核底层:如何通过端口号找到对应进程?(主机是如何把报文交给进程的,这里我们主要是先简单理解下)

很多同学只知道「端口号对应进程」,却不知道内核是如何实现这个映射的。Linux 内核中,通过哈希表 实现了端口号到 Socket(进程)的高效查找,核心数据结构是inet_hashinfo,定义在include/net/inet_hashtables.h中。

inet_hashinfo内部细分了三个核心哈希表,分别应对不同的场景:

c 复制代码
// Linux内核中inet_hashinfo核心结构(精简版)
struct inet_hashinfo {
    // 1. 已建立连接哈希表:核心表,存储所有ESTABLISHED状态的socket
    struct inet_ehash_bucket    *ehash;
    // 2. 绑定哈希表:以本地端口为索引,存储绑定到特定端口的socket,检查端口是否占用
    struct inet_bind_hashbucket  *bhash;
    // 3. 监听哈希表:以本地IP+端口为索引,存储LISTEN状态的socket,处理新连接请求
    struct inet_listen_hashbucket *lhash2;
};

内核从报文到进程的完整查找流程

  1. 网卡收到数据报文,经过数据链路层、网络层校验后,交付给传输层;
  2. 传输层提取报文的五元组(源 IP、源端口、目的 IP、目的端口、协议)
  3. 调用__inet_lookup函数,先在ehash已建立连接表中,通过五元组计算哈希值,快速找到对应的 Socket 结构体;
  4. 如果是新的连接请求(SYN 包),则在lhash2监听表中,通过目的端口查找对应的监听 Socket;
  5. 找到 Socket 后,内核通过它关联的文件结构体,最终找到拥有这个 Socket 的进程task_struct,完成数据交付。
  • 上面这个图里面还有关于IP和Port的大家也可以看看都

三. Socket:网络通信的核心基石

理解了 IP 和端口号,我们就能彻底搞懂 Socket 的本质。

3.1 Socket 的核心定义

IP 地址标识互联网中唯一的一台主机,端口号标识该主机上唯一的一个网络进程,因此「IP 地址 + 端口号」就能唯一标识互联网中的一个进程,我们把这个组合叫做套接字(Socket)。

Socket 的英文原意是「插座」,这个命名极其形象:

  • 网络通信就像电器通电,IP 地址是你家的地址,端口号是你家墙上的插座编号,电器(进程)插上插座(绑定 Socket),就能通过电网(互联网)和远端的电器通信。

3.2 四元组与五元组:唯一标识网络通信

  • 四元组{源IP, 源端口, 目的IP, 目的端口},能够唯一标识互联网中唯二的两个通信进程,定义了通信的两个端点;
  • 五元组 :在四元组的基础上,增加了传输层协议(TCP/UDP),内核通过五元组,唯一标识一个完整的网络双向连接。

举个例子 :你的电脑(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

Socket 编程基于传输层协议,我们需要先对两大核心协议建立直观的认知,为后续编程打下基础。

4.1 TCP 协议(传输控制协议)

TCP(Transmission Control Protocol)是面向连接的、可靠的、面向字节流的传输层协议,核心特性:

  1. 有连接:通信前必须先通过「三次握手」建立连接,断开时通过「四次挥手」释放连接,就像打电话,必须先拨通电话、对方接听,才能正常对话;
  2. 可靠传输 :TCP 会通过确认应答、超时重传、序列号、校验和等机制,保证数据不丢失、不重复、按序到达,不会出现数据错乱、丢包的情况;
  3. 面向字节流:数据以无边界的字节流形式传输,就像自来水,发送端一次发 1000 字节,接收端可以分 10 次每次收 100 字节,收发次数没有严格的对应关系,上层需要自己处理数据边界。

4.2 UDP 协议(用户数据报协议)

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

  1. 无连接:通信前不需要建立连接,知道对方的 IP 和端口,就可以直接发送数据,就像发邮件,不需要提前和对方确认,直接投递即可;
  2. 不可靠传输:UDP 不提供确认应答、重传机制,只保证把数据发出去,不保证数据能到达对方,也不保证数据按序到达;
  3. 面向数据报:数据以报文为单位传输,收发次数严格匹配,发送端一次发一个 100 字节的报文,接收端必须一次完整接收 100 字节,不能分多次读取,天然保留了数据边界。

重要提醒: 可靠和不可靠是协议的特性,不是优缺点。TCP 的可靠是有代价的,它需要额外的开销维护连接、保证可靠性,传输效率更低;UDP
虽然不可靠,但它头部开销小、传输效率高、延迟低。

  • 对数据完整性要求高的场景(文件传输、银行转账、HTTP 通信),用 TCP;
  • 对实时性要求高、能容忍少量丢包的场景(直播、视频通话、游戏),用 UDP。

五. 网络字节序:跨主机通信的统一标准

网络通信中,不同主机的 CPU 架构不同,内存中多字节数据的存储方式也不同,这就是大小端问题。如果不做统一处理,两台不同字节序的主机通信,会出现数据解析错乱的问题,而网络字节序就是解决这个问题的统一标准。

5.1 大小端的核心定义

内存中的多字节数据,相对于内存地址有两种存储方式:

  • 小端序(Little-Endian):低权值位存放在低地址,高权值位存放在高地址(记忆口诀:小小小);
  • 大端序(Big-Endian):低权值位存放在高地址,高权值位存放在低地址,符合人类的阅读习惯。

我们以0x1234abcd这个 4 字节整数为例,看它在内存中的存储布局:

内存地址 大端序(字节值) 小端序(字节值)
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 代码在大端和小端主机上都能正常运行,系统提供了标准的字节序转换函数,头文件为<arpa/inet.h>

函数原型与核心说明:

c 复制代码
#include <arpa/inet.h>
// 主机序转网络序:32位长整数(用于IP地址)
uint32_t htonl(uint32_t hostlong);
// 主机序转网络序:16位短整数(用于端口号)
uint16_t htons(uint16_t hostshort);
// 网络序转主机序:32位长整数(用于IP地址)
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 位的端口号从主机序转换为网络序。

函数实现的核心逻辑

这四个函数的实现,会根据主机的字节序做自适应处理:

  • 如果主机是小端序:函数会执行字节翻转,完成大小端转换;
  • 如果主机是大端序:函数不做任何转换,直接将参数原封不动返回。

这也是为什么我们写代码时,无论主机是大端还是小端,都必须调用转换函数 ------ 它保证了代码的跨平台可移植性,不需要我们手动判断主机字节序。

重要提醒: 单字节的字符串不需要做字节序转换。因为大小端问题只存在于多字节数据中,单个字节不存在存储顺序的问题,只有shortintlong等多字节类型需要转换。


六. Socket 核心 API 与地址结构体深度解析

理解了底层原理后,我们进入实战环节,深度解析 Socket 编程的核心地址结构体与 API。

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

Socket API 是一套抽象的通用接口,不仅支持 IPv4,还支持 IPv6、UNIX 域套接字等多种地址类型。为了实现一套接口兼容所有地址类型,设计者用 C 语言实现了经典的多态设计,核心是三个地址结构体。

1. 通用地址结构体:struct sockaddr

c 复制代码
// 通用地址结构体,所有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 域套接字,用于本地进程间通信。

2. IPv4 专用地址结构体:struct sockaddr_in

c 复制代码
#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一致
};

// IP地址结构体,本质就是32位无符号整数
struct in_addr {
    in_addr_t s_addr; // 32位IPv4地址,网络字节序
};

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

c 复制代码
#include <sys/un.h>

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

设计精髓:C 语言的多态实现

所有 Socket API 都用struct sockaddr *作为通用参数类型,我们使用时,需要根据协议类型,定义对应的专用结构体(如sockaddr_in),填充完成后,强制转换为struct sockaddr *类型传入函数。

函数内部会根据结构体第一个字段sa_family,判断地址的真实类型,再做对应的处理。这就是典型的基类指针指向派生类对象,用 C 语言实现了面向对象的多态特性。

这样设计的核心优势:

  • 一套 API 兼容所有地址类型,用户不需要为 IPv4、IPv6、本地通信学习多套不同的接口,极大降低了学习成本;
  • 扩展性极强,后续新增新的地址协议,不需要修改 API 接口,只需要新增对应的地址结构体即可。

6.2 Socket 核心 API 逐行解析

Socket 编程分为服务端和客户端,核心 API 共 5 个,我们逐行深度解析。

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

c 复制代码
// 函数原型
#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,让系统根据domaintype自动选择对应的协议。

返回值

  • 成功:返回非负整数,即 Socket 文件描述符;
  • 失败:返回-1,并设置全局变量errno标识错误原因。

使用示例

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

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

c 复制代码
// 函数原型
int bind(int socket, const struct sockaddr *address, socklen_t address_len);

功能:将 Socket 文件描述符与指定的 IP 地址、端口号绑定,服务端必须调用,客户端一般不需要主动调用。

参数解析

  • socketsocket()函数返回的文件描述符;
  • address:填充好的地址结构体指针,需要强转为struct sockaddr *类型;
  • address_len:地址结构体的长度,即sizeof(struct sockaddr_in)

返回值

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

使用示例:

c 复制代码
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);
    exit(1);
}

关键说明:INADDR_ANY表示绑定本机所有的网卡 IP 地址,无论客户端访问哪个本机 IP,都能被这个 Socket 接收,是服务端最常用的配置。

3. 开启 Socket 监听:listen ()

c 复制代码
// 函数原型
int listen(int socket, int backlog);

功能 :将 Socket 设置为被动监听模式,让内核开始接受客户端的连接请求,仅 TCP 服务端需要调用,UDP 和客户端不需要。

参数解析

  • socketsocket()函数返回的文件描述符,必须已经通过bind()绑定了地址;
  • backlog:内核中已完成三次握手的连接队列的最大长度,决定了同一时间最多能有多少个等待accept()处理的已建立连接。

返回值

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

使用示例:

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

4. 接受客户端连接:accept ()

c 复制代码
// 函数原型
int accept(int socket, struct sockaddr* address, socklen_t* address_len);

功能 :从已完成三次握手的连接队列中,取出一个已建立的连接,创建一个新的 Socket 文件描述符,专门用于和这个客户端通信,仅 TCP 服务端需要调用

参数解析

  • socket:监听 Socket 文件描述符,即listen()的入参;
  • address:输出参数,用于接收客户端的地址信息(IP + 端口);
  • address_len:输入输出参数,传入时是地址结构体的长度,函数返回时是实际写入的地址长度。

返回值

  • 成功:返回一个新的非负整数 Socket 文件描述符,专门用于和当前客户端通信;
  • 失败 :返回-1,并设置errno

核心注意点:accept()返回的新 Socket,才是和客户端通信的 Socket,原来的监听 Socket 会继续保持监听状态,用于接受新的客户端连接。

使用示例:

c 复制代码
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);
}
// 打印客户端IP和端口
printf("client connected, ip: %s, port: %d\n",
       inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

5. 发起连接请求:connect ()

c 复制代码
// 函数原型
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

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

参数解析

  • sockfd:客户端socket()函数返回的文件描述符;
  • addr:服务端的地址结构体,填充了服务端的 IP 和端口号;
  • addrlen:地址结构体的长度。

返回值

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

使用示例

c 复制代码
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地址,192.168.1.100转网络字节序
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");
    close(sockfd);
    exit(1);
}

七. 核心考点总结(面试高频)

  1. 网络通信的本质:跨主机的进程间通信,数据到主机只是手段,交付给目标进程才是目的。
  2. 端口号的核心作用:16 位无符号整数,标识主机内的网络进程,IP + 端口号唯一标识全网唯一进程。
  3. 端口号与 PID 的区别:为什么不用 PID 做网络标识,核心是可控性、解耦合、灵活性三个维度的差异。
  4. Socket 的本质:IP + 端口号 = Socket,Linux 中 Socket 本质是文件描述符,是内核协议栈与用户进程的桥梁。
  5. 四元组与五元组:四元组标识通信两端,五元组唯一标识一个网络连接。
  6. TCP 与 UDP 的核心特性:TCP 是有连接、可靠、面向字节流;UDP 是无连接、不可靠、面向数据报,以及各自的适用场景。
  7. 网络字节序:TCP/IP 规定网络字节序为大端序,为什么需要字节序转换,以及四个转换函数的使用。
  8. Socket 地址结构体的设计思想:struct sockaddr 如何用 C 语言实现多态,一套接口兼容多种地址类型。
  9. Socket 核心 API 的作用与使用场景:socket、bind、listen、accept、connect 分别在服务端 / 客户端的使用时机。
  10. 内核如何通过端口号找到进程:inet_hashinfo 的三个哈希表,以及报文到进程的查找流程。

结尾:

html 复制代码
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!

结语:Socket 编程是 Linux C/C++ 后端开发的核心基石,很多人写了多年网络代码,却始终停留在「复制粘贴 API 模板」的层面,遇到线上网络问题、性能瓶颈时无从下手,本质就是对底层原理的理解不够透彻。本文我们从网络通信的本质出发,完整拆解了端口号、Socket 定义、传输层协议、网络字节序、地址结构体与核心 API 的底层逻辑,覆盖了 Socket 编程所有预备知识点。理解了这些本质,再去学习 TCP 三次握手、四次挥手、高并发服务端开发,都会事半功倍。后续我们会继续深入,基于这些基础,实现完整的 TCP 客户端与服务端,拆解更多网络编程的核心细节。

✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど

相关推荐
浅念-2 小时前
LeetCode最短路必看:BFS算法原理+经典题解
数据结构·c++·算法·leetcode·职场和发展·bfs·宽度优先
hhb_6182 小时前
Terra常见技术问题梳理与实战应用案例解析
运维·服务器·网络
say_fall3 小时前
装软件慢到崩溃?用户创建总出错?Linux 工具避坑指南
linux·运维·服务器·c++·学习
叼烟扛炮3 小时前
C++ 知识点02 输入输出
开发语言·c++·算法·输入输出
GZ_TOGOGO3 小时前
2026 年 RHCE 考试到底有哪些变化?给你盘盘干货
运维·rhce·rhce考试·rhce认证·it培训·rhce 10.0
小则又沐风a3 小时前
基础的开发工具(2)---Linux
java·linux·前端
yqcoder3 小时前
JavaScript 事件流:从“捕获”到“冒泡”的完整旅程
服务器·前端·javascript
一个学Java小白3 小时前
LV.12 Linux应用开发综合实战-在线词典
linux·运维·服务器
小此方3 小时前
Re:思考·重建·记录 现代C++ C++11篇(六) 从 shared_ptr 到 weak_ptr:起底智能指针的引用计数与循环引用之痛
开发语言·c++·c++11·现代c++