前言:欢迎 各位光临本博客,这里小编带你直接手撕**,文章并不复杂,愿诸君**耐其心性,忘却杂尘,道有所长!!!!

IF'Maxue :个人主页
🔥 个人专栏 :
《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》
⛺️生活是默默的坚持,毅力是永久的享受。不破不立!
文章目录
-
- 一、端口号:网络通信的"门牌号"
-
- [1.1 底层实现原理](#1.1 底层实现原理)
- [1.2 端口号 vs PID:解耦的两个标识](#1.2 端口号 vs PID:解耦的两个标识)
- [1.3 IP+端口](#1.3 IP+端口)
- [1.4 Socket:IP+端口的组合体](#1.4 Socket:IP+端口的组合体)
- [1.5 端口号范围划分](#1.5 端口号范围划分)
- [1.6 端口号和进程ID的关联](#1.6 端口号和进程ID的关联)
- [1.7 源端口号 vs 目的端口号](#1.7 源端口号 vs 目的端口号)
- [1.8 用"唐僧取西经"理解Socket](#1.8 用“唐僧取西经”理解Socket)
- [1.9 传输层核心:TCP vs UDP](#1.9 传输层核心:TCP vs UDP)
-
- 字节流的读写特点
- [可靠传输 vs 不可靠传输](#可靠传输 vs 不可靠传输)
- 为啥还要保留UDP?
- 二、网络字节序:大端与小端
-
- [2.1 大端/小端的定义](#2.1 大端/小端的定义)
- [2.2 速记法](#2.2 速记法)
- [2.3 解决大小端问题:网络字节序约定](#2.3 解决大小端问题:网络字节序约定)
- [2.4 大小端转换的接口](#2.4 大小端转换的接口)
- 三、Socket编程接口:网络通信的"工具集"
-
- [3.1 常见API](#3.1 常见API)
- [3.2 网络通信的本质](#3.2 网络通信的本质)
- [3.3 三种套接字](#3.3 三种套接字)
- [3.4 sockaddr结构:统一的地址格式](#3.4 sockaddr结构:统一的地址格式)
- [四、UDP实战:实现Echo Server(回显服务器)](#四、UDP实战:实现Echo Server(回显服务器))
-
- [4.1 项目文件结构](#4.1 项目文件结构)
- [4.2 Makefile:一键编译](#4.2 Makefile:一键编译)
- [4.3 核心代码文件](#4.3 核心代码文件)
- [4.4 核心接口详解](#4.4 核心接口详解)
- 1. ##的功能
Socket(套接字)编程是让不同设备上的进程实现网络通信的核心方法,本质就是操作系统提供了一套标准化的 API,让我们能通过代码控制 "进程如何通过网络收发数据",全程不用关心底层网络硬件、协议细节,只需要按规则调用接口就行。
一、端口号:网络通信的"门牌号"
端口号说白了就是操作系统给网络进程分配的唯一标识,就像你家的门牌号
数据从网络过来,靠端口号才能精准找到要交给哪个程序处理,没它数据就成了"无家可归"的流浪包。

1.1 底层实现原理
端口号不是凭空来的,内核会通过专门的数据结构管理端口和进程的关联,简单说就是维护一张"端口-进程"映射表,数据来了先查这张表,再递交给对应进程,保证不会发错对象。

1.2 端口号 vs PID:解耦的两个标识
我一开始总把这俩弄混,后来找了个通俗比喻就懂了:
PID(进程ID)是进程在本机的"校内学号",只在这台机器上有用;
端口号是进程在网络中的"身份证号",跨设备通信全靠它。这种设计让网络和本地进程标识解耦,哪怕进程的PID变了,只要端口号不变,网络通信就不受影响。

1.3 IP+端口
IP地址负责定位网络中的设备(比如"北京市朝阳区XX小区"),端口号负责定位设备上的进程(比如"小区里的101室")。
一次完整的网络通信,必须靠{源IP,源port,目的IP,目的端口号}这四元组,才能唯一确定"谁给谁发数据"。


1.4 Socket:IP+端口的组合体
Socket(套接字)本质就是"IP地址+端口号"的组合,是应用程序和网络打交道的"接口"------有了Socket,
进程才能和外部设备建立连接、传输数据,没它程序就是"闭门造车",没法和外界通信。

1.5 端口号范围划分
端口号是16位整数,范围0~65535,不同区间有固定用途,避免端口占用冲突
具体划分看这张图就清楚了:

1.6 端口号和进程ID的关联
这里要记两个关键点:一个进程可以占用多个端口(比如一个服务同时监听TCP和UDP端口),但一个端口同一时间只能被一个进程占用
不然数据过来,内核都不知道该交给谁。

1.7 源端口号 vs 目的端口号
-
源端口号:发送方随机分配的临时端口,作用是接收对方的响应数据(比如你给服务器发请求,源端口就是"你的回邮地址");
-
目的端口号:接收方提供服务的固定端口(比如HTTP服务的80端口),是数据要到达的"最终目的地"。

1.8 用"唐僧取西经"理解Socket
要是还觉得抽象,就用这个例子类比:
西天 = 目的IP(定位"如来"所在的设备);如来的莲台 = 目的端口(定位"如来"这个进程);东土大唐 = 源IP;唐僧的行囊 = 源端口;Socket = 西天+莲台(或东土大唐+行囊)
保证唐僧能精准找到如来,完成"数据传递"。

1.9 传输层核心:TCP vs UDP
从这两张图能明确:传输层属于系统内核,我们要通过网络通信,就得调用它提供的 TCP/UDP 协议。
先看 TCP,它是传输层协议,核心特点是 "有连接、可靠传输、面向字节流"------ 通信前得先建立连接,能保证数据不丢、不错、不乱序,像 "稳定的管道" 一样连续传数据。
再看 UDP,同样是传输层协议,但它是 "无连接、不可靠传输、面向数据报"------ 不用建立连接直接发数据,不保证对方能收到,像 "对讲机喊话",简单直接但没保障。


字节流的读写特点
TCP是"字节流"协议,我总结下来就是:写数据像往水管里放水,连续写就行,特别简单;读数据像接水,不知道啥时候接完,还得处理"粘包",贼麻烦。
可靠传输 vs 不可靠传输
- 可靠传输(TCP):会解决丢包、超时、乱序这些问题,靠校验、重传、确认等机制,保证数据一点不差到达;
- 不可靠传输(UDP):不管这些,数据发出去就不管了,丢了也不重传,乱了也不调整。
为啥还要保留UDP?
我一开始也纳闷,既然UDP不可靠,为啥不都用TCP?
后来才懂:TCP为了可靠,协议复杂、占资源多(还要建立连接、维护状态);UDP简单、无连接、速度快,开发周期短,适合直播、语音通话这种"能容忍少量丢包,但要实时"的场景。
我们直播中也会选择udp or tcp的协议的选项

二、网络字节序:大端与小端
不同设备存储数据的方式不一样,就像有人从左写字,有人从右写字,网络通信必须统一"写字顺序",不然数据传过去就成了"乱码"。
2.1 大端/小端的定义
- 大端:数据的高位存在低内存地址(比如数字12,"1"在低地址,"2"在高地址);
- 小端:数据的低位存在低内存地址(数字12,"2"在低地址,"1"在高地址)。
简单记:12存在最下面的是大端。

2.2 速记法
记不住大小端?看这张图的速记技巧,一眼就能分清:

2.3 解决大小端问题:网络字节序约定
市面上大小端设备都有,为了通信统一,规定:所有发往网络的数据,必须是大端(网络字节序) 。发送方要把本机字节序转成网络字节序,接收方再转回来,这样就不会解析错了。

补充一个关键点:
- 网络传输规则:先发低地址数据,后发高地址数据;

2.4 大小端转换的接口
不用自己写转换逻辑,系统提供了现成的接口,直接调用就行:

三、Socket编程接口:网络通信的"工具集"
Socket是操作系统给的网络编程接口,核心要记住:网络通信的本质,其实是"跨设备的进程间通信"。
3.1 常见API
Socket编程的核心函数都在这张图里,是入门的基础:
cpp
// 创建 socket 文件描述符 (TCP/UDP,客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP,服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
// 开始监听socket (TCP,服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP,服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
// 建立连接 (TCP,客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
3.2 网络通信的本质
别被"网络通信"唬住,其实就是不同设备上的进程在说话。
大家看这张图,展示的是TCP三次握手的过程,这本质就是两个设备上的进程,通过网络建立连接、准备通信的过程
说到底,网络通信就是跨设备的进程间通信(IPC),和本地用管道、共享内存通信的核心目的一致,只是媒介换成了网络。
别把"三次握手"和"通信本质"搞混,三次握手是TCP实现可靠通信的手段,不是本质哦。

3.3 三种套接字
Socket分三类,但重点学网络套接字就行------学会网络套接字,本地套接字自然就会了,没必要单独花时间。

这张图展示的是TCP四次挥手,刚好对应上面的三次握手,是TCP断开连接的过程。大家可以结合着记:TCP通信有始有终,建立连接用三次握手,断开连接用四次挥手。
我们说的本地套接字和网络套接字,和这个挥手过程没关系
核心区别是通信范围------本地只能在同一台机器,网络能跨设备。新手千万别把"连接断开过程"和"套接字分类"混为一谈,这是很容易踩的坑!

3.4 sockaddr结构:统一的地址格式
为了兼容不同类型的套接字,系统设计了sockaddr基类结构,有点像C++的"继承多态",网络接口会自动区分你是要本地通信还是网络通信。

这张图是TCP的状态机,展示了TCP从建立连接到断开连接的各种状态(比如ESTABLISHED、TIME_WAIT)
虽然和sockaddr结构不直接相关,但能帮大家理解TCP的可靠性------正因为有这些状态管理,TCP才能保证数据可靠传输。
sockaddr结构的作用,是让内核知道你要用哪种方式通信:是AF_INET的网络通信,还是AF_UNIX的本地通信,内核会根据结构里的字段自动区分,不用我们手动指定,
sockaddr结构的设计思路
这里有个小知识点:为啥不用void*,非要用C语言模拟"基类"?
- 一是可读性更高;
- 二是设计这个结构的时候,void*还没普及。


四、UDP实战:实现Echo Server(回显服务器)
光懂理论没用,动手写个最简单的UDP回显服务器,就能把前面的知识点串起来------客户端发啥,服务器就回啥,新手入门超合适。
4.1 项目文件结构
先看整体文件组织,清晰的结构能少踩很多坑:

4.2 Makefile:一键编译
写个Makefile,不用每次手动敲编译命令,一键搞定客户端和服务器:
cpp
.PHONY:all
all:udpclient udpserver
udpclient:UdpClient.cc
g++ -o $@ $^ -std=c++17
udpserver:UdpServer.cc
g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
rm -f udpclient udpserver
4.3 核心代码文件
- 客户端代码(udpClient.cc):负责发送数据到服务器,再接收回显;
cpp
#include <iostream>
int main()
{
return 0;
}
- 服务器头文件(udpServer.hpp):封装UDP服务器的核心逻辑;
cpp
#pragma once
class UdpServer
{
public:
UdpServer()
{}
void Init()
{}
void Start()
{}
~UdpServer()
{}
private:
};
udpSever实现代码:
cpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"
using namespace LogModule;
const int defaultfd = -1;
class UdpServer
{
public:
UdpServer():_sockfd(defaultfd)
{}
void Init()
{
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error!";
exit(1);
}
LOG(LogLevel::INFO) << "socket success, sockfd : " << _sockfd;
}
void Start()
{}
~UdpServer()
{}
private:
int _sockfd;
};
UDP绑定Socket套接字
cpp
LOG(LogLevel::INFO) << "socket success, sockfd : " << _sockfd;
// 2. 绑定socket信息,ip和端口,ip(比较特殊,后续解释)
// 2.1 填充sockaddr_in结构体
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(8080); // 补充:指定端口(如8080),需转网络字节序
local.sin_addr.s_addr = INADDR_ANY; // 补充:绑定所有本地IP
关键字段解析
字段 补充内容 说明
- 服务器源文件(udpServer.cc):实现头文件声明的方法,核心是绑定端口、接收数据、回显数据;
cpp
#include <iostream>
#include <memory>
#include "UdpServer.hpp"
int main()
{
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>();
usvr->Init();
usvr->Start();
return 0;
}
4.4 核心接口详解
socket函数(UDP)
socket是创建 UDP 套接字的核心函数,其作用是为进程间通信创建一个 "端点"。
在 UDP 场景下,调用它时需指定三个关键参数:
- domain填AF_INET(表示使用 IPv4 网络协议)
- type填SOCK_DGRAM(表示创建 UDP 类型的套接字)
- protocol填 0(默认使用与type匹配的协议)。
函数执行后,会返回一个套接字描述符(类似文件描述符),后续的绑定、收发数据等操作都需要通过这个描述符来完成,是 UDP 网络通信的基础入口

- 返回值:成功返回套接字描述符,失败返回-1;

- 头文件:使用socket函数必须包含的头文件;

sockaddr_in结构
存储网络地址的结构体,是sockaddr的"子类",必须掌握:

宏替换:简化字节序转换
系统提供的宏,能让字节序转换更简洁,不用自己写繁琐的转换逻辑:

1. ##的功能
##是宏定义中的 "符号连接运算符",可以将其两侧的符号拼接为一个新的标识符,用于从分离的文本片段创建自定义标识。
- 示例解析
宏定义:
cpp
#define ADD_TO_SUM(num, value) \
sum##num += value;

Socket使用的完整片段:
cpp
LOG(LogLevel::INFO) << "socket success, sockfd : " << _sockfd;
// 2. 绑定socket信息,ip和端口,ip(比较特殊,后续解释)
// 2.1 填充sockaddr_in结构体
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
// 端口转换为网络字节序
local.sin_port = htons(_port);
// IP转换为网络字节序(将字符串格式的IP转为4字节网络序)
local.sin_addr.s_addr = inet_addr(_ip.c_str());
// 调用bind绑定Socket与IP、端口
int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));