
🔥草莓熊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 接口、实现跨主机通信,很多人能熟练调用
socket、bind、listen这些 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 点:
- 可控性差异:PID 是操作系统动态分配的,进程重启后 PID 会发生变化,用户无法主动固定;而服务器的端口号必须是固定的(比如 HTTP 服务固定用 80 端口),客户端必须提前知道端口号才能发起通信,PID 完全无法满足这个需求。
- 架构解耦合:PID 属于操作系统进程管理的系统概念,端口号属于网络通信的网络概念。如果用 PID 做网络标识,会让系统进程管理和网络协议栈强耦合,一旦系统层的 PID 机制发生变化,整个网络模块都要跟着修改,违背了分层设计的核心思想。
- 灵活性差异 :不是所有进程都需要进行网络通信,端口号让进程可以自主选择是否参与网络通信、绑定哪个端口;同时一个进程可以绑定多个端口号,但一个端口号同一时间只能被一个进程占用,这种一对多的映射关系,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;
};
内核从报文到进程的完整查找流程:
- 网卡收到数据报文,经过数据链路层、网络层校验后,交付给传输层;
- 传输层提取报文的五元组(源 IP、源端口、目的 IP、目的端口、协议);
- 调用
__inet_lookup函数,先在ehash已建立连接表中,通过五元组计算哈希值,快速找到对应的 Socket 结构体; - 如果是新的连接请求(SYN 包),则在
lhash2监听表中,通过目的端口查找对应的监听 Socket; - 找到 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)是面向连接的、可靠的、面向字节流的传输层协议,核心特性:
- 有连接:通信前必须先通过「三次握手」建立连接,断开时通过「四次挥手」释放连接,就像打电话,必须先拨通电话、对方接听,才能正常对话;
- 可靠传输 :TCP 会通过确认应答、超时重传、序列号、校验和等机制,保证数据不丢失、不重复、按序到达,不会出现数据错乱、丢包的情况;
- 面向字节流:数据以无边界的字节流形式传输,就像自来水,发送端一次发 1000 字节,接收端可以分 10 次每次收 100 字节,收发次数没有严格的对应关系,上层需要自己处理数据边界。
4.2 UDP 协议(用户数据报协议)
UDP(User Datagram Protocol)是无连接的、不可靠的、面向数据报的传输层协议,核心特性:
- 无连接:通信前不需要建立连接,知道对方的 IP 和端口,就可以直接发送数据,就像发邮件,不需要提前和对方确认,直接投递即可;
- 不可靠传输:UDP 不提供确认应答、重传机制,只保证把数据发出去,不保证数据能到达对方,也不保证数据按序到达;
- 面向数据报:数据以报文为单位传输,收发次数严格匹配,发送端一次发一个 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 位的端口号从主机序转换为网络序。

函数实现的核心逻辑
这四个函数的实现,会根据主机的字节序做自适应处理:
- 如果主机是小端序:函数会执行字节翻转,完成大小端转换;
- 如果主机是大端序:函数不做任何转换,直接将参数原封不动返回。
这也是为什么我们写代码时,无论主机是大端还是小端,都必须调用转换函数 ------ 它保证了代码的跨平台可移植性,不需要我们手动判断主机字节序。
重要提醒:
单字节的字符串不需要做字节序转换。因为大小端问题只存在于多字节数据中,单个字节不存在存储顺序的问题,只有short、int、long等多字节类型需要转换。
六. 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,让系统根据domain和type自动选择对应的协议。
返回值:
- 成功:返回非负整数,即 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 地址、端口号绑定,服务端必须调用,客户端一般不需要主动调用。
参数解析:
socket:socket()函数返回的文件描述符;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 和客户端不需要。
参数解析:
socket:socket()函数返回的文件描述符,必须已经通过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);
}
七. 核心考点总结(面试高频)
- 网络通信的本质:跨主机的进程间通信,数据到主机只是手段,交付给目标进程才是目的。
- 端口号的核心作用:16 位无符号整数,标识主机内的网络进程,IP + 端口号唯一标识全网唯一进程。
- 端口号与 PID 的区别:为什么不用 PID 做网络标识,核心是可控性、解耦合、灵活性三个维度的差异。
- Socket 的本质:IP + 端口号 = Socket,Linux 中 Socket 本质是文件描述符,是内核协议栈与用户进程的桥梁。
- 四元组与五元组:四元组标识通信两端,五元组唯一标识一个网络连接。
- TCP 与 UDP 的核心特性:TCP 是有连接、可靠、面向字节流;UDP 是无连接、不可靠、面向数据报,以及各自的适用场景。
- 网络字节序:TCP/IP 规定网络字节序为大端序,为什么需要字节序转换,以及四个转换函数的使用。
- Socket 地址结构体的设计思想:struct sockaddr 如何用 C 语言实现多态,一套接口兼容多种地址类型。
- Socket 核心 API 的作用与使用场景:socket、bind、listen、accept、connect 分别在服务端 / 客户端的使用时机。
- 内核如何通过端口号找到进程:inet_hashinfo 的三个哈希表,以及报文到进程的查找流程。
结尾:
html
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!
结语:Socket 编程是 Linux C/C++ 后端开发的核心基石,很多人写了多年网络代码,却始终停留在「复制粘贴 API 模板」的层面,遇到线上网络问题、性能瓶颈时无从下手,本质就是对底层原理的理解不够透彻。本文我们从网络通信的本质出发,完整拆解了端口号、Socket 定义、传输层协议、网络字节序、地址结构体与核心 API 的底层逻辑,覆盖了 Socket 编程所有预备知识点。理解了这些本质,再去学习 TCP 三次握手、四次挥手、高并发服务端开发,都会事半功倍。后续我们会继续深入,基于这些基础,实现完整的 TCP 客户端与服务端,拆解更多网络编程的核心细节。
✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど
