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 客户端与服务端,拆解更多网络编程的核心细节。

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

相关推荐
_wyt0012 小时前
洛谷 B3930 [GESP202312 五级] 烹饪问题 题解
c++·gesp
大树884 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠4 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质4 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush44 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5205 小时前
Linux 11 动态监控指令top
linux
小宇宙Zz5 小时前
Maven依赖冲突
java·服务器·maven
Inhand陈工5 小时前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
玖玥拾6 小时前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
酣大智6 小时前
ARP代理--工作原理
运维·网络·arp·arp代理