[C++高频精进] 网络编程:网路基础

核心要点速览

  • 协议栈:TCP/IP 四层模型(应用层→传输层→网络层→数据链路层)
  • TCP vs UDP:TCP 面向连接、可靠流式;UDP 无连接、高效数据报
  • 三次握手:建立 TCP 连接,确保双方收发能力正常;四次挥手:断开连接,释放全双工通道
  • TIME_WAIT:客户端第四次挥手后停留 2MSL,确保 ACK 送达、旧报文失效
  • Socket:网络编程接口,由 "IP + 端口" 唯一标识,TCP 需按固定流程(绑定 - 监听 - 连接 - 收发)编程

一、TCP/IP 四层模型

  • 应用层:提供具体业务协议(HTTP、FTP、DNS),定义数据格式和交互逻辑
  • 传输层:TCP/UDP,负责端到端(进程间)数据传输(可靠 / 高效)
  • 网络层:IP 协议,负责跨网络路由转发(寻址)
  • 数据链路层:处理物理介质上的帧传输(如以太网帧)

二、TCP 与 UDP 对比

对比维度 TCP(传输控制协议) UDP(用户数据报协议)
连接性 面向连接(三次握手建连,四次挥手断连) 无连接(直接发送,无需建连)
可靠性 可靠(重传、序列号、确认、滑动窗口、拥塞控制) 不可靠(无重传,可能丢包 / 乱序)
传输速率 低(含确认、重传等额外开销) 高(无额外开销,仅传输数据)
数据形式 字节流(无边界,需应用层定义分割) 数据报(有边界,一次收发一个完整报文)
拥塞控制 有(避免网络过载) 无(可能导致网络拥塞)
适用场景 文件传输、HTTP、邮件(需可靠性) 实时通信(视频 / 语音)、DNS(需实时性)

1. TCP:面向连接的可靠传输

  • 特性
    • 面向连接:通信前必须通过三次握手建立连接,结束后通过四次挥手断开。
    • 可靠保障:通过序列号 (保证有序)、确认应答 (ACK,确保接收)、重传机制 (丢失重发)、滑动窗口 (流量控制)、拥塞控制(避免网络过载)实现数据不丢、不重、有序。
    • 字节流:数据无天然边界,需应用层自行处理粘包 / 半包问题。
  • 典型协议:HTTP、FTP、SMTP、SSH。

2. UDP:无连接的高效传输

  • 特性
    • 无连接:无需建连 / 断连,直接发送数据,开销极低。
    • 不可靠:不保证数据到达、有序,无重传机制(丢包需应用层处理)。
    • 数据报:每个报文是独立单元(有边界),接收方一次接收一个完整报文(无粘包)。
  • 典型协议:RTP(实时音视频)、DNS、DHCP、游戏数据传输。

三、三次握手与四次挥手

1. 三次握手(建立 TCP 连接)

  • 目的:确认双方 "发送" 和 "接收" 能力正常,协商初始序列号(避免历史报文干扰)。
  • 流程 (客户端→服务器):
    1. 第一次握手:客户端发SYN报文(同步请求),携带初始序列号seq = x
    2. 第二次握手:服务器回SYN+ACK报文(同步 + 确认),携带自身初始序列号seq = y、确认号ack = x + 1(表示已接收客户端x)。
    3. 第三次握手:客户端回ACK报文,确认号ack = y + 1(表示已接收服务器y)。
  • 问答 :为什么需要三次握手?
    • 避免 "过期连接请求" 浪费服务器资源。若客户端旧SYN报文延迟到达,服务器二次握手后,客户端会因识别为无效请求而不发第三次ACK,服务器超时后释放资源(二次握手会导致服务器误建连)。

2. 四次挥手(断开 TCP 连接)

  • 目的:TCP 是全双工通信(双方可同时发数据),需分别关闭各自的发送通道。
  • 流程 (客户端先发起关闭):
    1. 第一次挥手:客户端发FIN报文(终止请求),seq = u,表示不再发送数据。
    2. 第二次挥手:服务器回ACK报文,ack = u + 1,表示确认关闭请求(此时服务器→客户端通道仍可发数据)。
    3. 第三次挥手:服务器数据发送完毕,发FIN报文,seq = v,表示不再发送数据。
    4. 第四次挥手:客户端回ACK报文,ack = v + 1,表示确认关闭(此时客户端→服务器通道关闭)。
  • 问答 :为什么需要四次挥手?
    • 全双工特性导致:第二次挥手仅确认客户端的关闭请求,服务器可能仍有未发送完的数据,需等数据发完后,再通过第三次挥手关闭自身发送通道,因此需四次交互。

3. TIME_WAIT 状态

  • 触发场景 :客户端发送第四次挥手的ACK后进入该状态。
  • 停留时间:默认 2MSL(MSL 是报文最大生存时间,通常 1 分钟)。
  • 目的
    1. 确保服务器能收到第四次挥手的ACK(若服务器未收到,会重发FIN,客户端可在TIME_WAIT内重传)。
    2. 避免客户端新连接收到旧连接的残留报文(2MSL 足够让网络中旧报文失效)。

四、Socket:网络编程接口

1. Socket 本质

  • 操作系统提供的网络通信接口,封装了 TCP/UDP 底层协议细节。
  • 唯一标识:IP地址 + 端口号(如(192.168.1.1, 8080)),对应进程间通信的端点。

2. 核心Socket函数

函数名 作用 说明
socket() 创建 Socket 文件描述符 1. 参数 typeSOCK_STREAM=TCP,SOCK_DGRAM=UDP(必考);2. 返回值:成功非负 fd,失败 - 1
bind() 绑定本地 IP + 端口 1. 端口范围 165535,11024 为知名端口;2. 必须用 htons() 转换端口为网络序(高频易错点)
listen() TCP 服务器开启监听 1. 仅 TCP 使用,UDP 无需;2. 参数 backlog:监听队列最大长度,不代表最大连接数(易混淆点)
accept() TCP 服务器接收客户端连接 1. 阻塞函数,返回新的 conn_fd用于通信,原 listen_fd 继续监听;2. 输出参数获取客户端地址
connect() TCP 客户端发起连接 1. 触发三次握手,阻塞直到连接建立;2. UDP 无此函数(必考差异点)
send()/recv() TCP 收发数据 1. 面向字节流,recv()返回 0 表示对方关闭连接;2. send()返回值可能 < len,需循环发送
sendto()/recvfrom() UDP 收发数据 1. 无连接,需指定对方地址;2. 自带消息边界,无粘包问题(与 TCP 对比考点)
close() 关闭 Socket 释放资源 1. TCP 关闭触发四次挥手;2. 服务器需先关 conn_fd,再关 listen_fd

3. TCP Socket 编程流程(示例)

服务器端(被动连接):
cpp 复制代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>

int main() {
    // 1. 创建TCP Socket
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);

    // 2. 绑定IP和端口
    sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;         // IPv4
    serv_addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡IP
    serv_addr.sin_port = htons(8080);       // 端口转换为网络序
    bind(listen_fd, (sockaddr*)&serv_addr, sizeof(serv_addr));

    // 3. 监听(最大等待连接数10)
    listen(listen_fd, 10);

    // 4. 阻塞等待客户端连接(返回与该客户端通信的新Socket)
    sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    int conn_fd = accept(listen_fd, (sockaddr*)&client_addr, &client_len);

    // 5. 收发数据
    char buf[1024] = {0};
    recv(conn_fd, buf, sizeof(buf), 0);     // 接收客户端数据
    send(conn_fd, "Hello Client", 12, 0);   // 发送响应

    // 6. 关闭Socket
    close(conn_fd);
    close(listen_fd);
    return 0;
}
客户端(主动连接):
cpp 复制代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>

int main() {
    // 1. 创建TCP Socket
    int client_fd = socket(AF_INET, SOCK_STREAM, 0);

    // 2. 连接服务器
    sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器IP
    serv_addr.sin_port = htons(8080);                  // 服务器端口
    connect(client_fd, (sockaddr*)&serv_addr, sizeof(serv_addr));

    // 3. 收发数据
    send(client_fd, "Hello Server", 12, 0);           // 发送数据
    char buf[1024] = {0};
    recv(client_fd, buf, sizeof(buf), 0);             // 接收响应

    // 4. 关闭Socket
    close(client_fd);
    return 0;
}

4. UDP Socket 收发示例

cpp 复制代码
// UDP服务器端(接收数据)
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>

int main() {
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); // SOCK_DGRAM指定UDP

    sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(8080);
    bind(sock_fd, (sockaddr*)&serv_addr, sizeof(serv_addr));

    char buf[1024] = {0};
    sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    // 接收数据并获取客户端地址
    recvfrom(sock_fd, buf, sizeof(buf), 0, (sockaddr*)&client_addr, &client_len);
    // 回复客户端
    sendto(sock_fd, "Hello UDP Client", 15, 0, (sockaddr*)&client_addr, client_len);

    close(sock_fd);
    return 0;
}

5. TCP 粘包解决方案示例(消息头 + 消息体,最常用)

cpp 复制代码
// 发送端:打包消息(4字节长度+消息体)
void send_msg(int sock_fd, const std::string& data) {
    int len = data.size();
    len = htonl(len); // 长度转换为网络序
    // 先发送消息长度(4字节)
    send(sock_fd, &len, sizeof(len), 0);
    // 再发送消息体
    send(sock_fd, data.c_str(), data.size(), 0);
}

// 接收端:解析消息(先读长度,再读对应长度的消息体)
std::string recv_msg(int sock_fd) {
    int len = 0;
    // 先接收消息长度(4字节)
    recv(sock_fd, &len, sizeof(len), 0);
    len = ntohl(len); // 转换为主机序

    // 再接收消息体
    char* buf = new char[len + 1];
    recv(sock_fd, buf, len, 0);
    buf[len] = '\0';
    std::string data(buf);
    delete[] buf;
    return data;
}
相关推荐
mjhcsp1 小时前
P1220关路灯mjhcsp
c++·动态规划
kyle~1 小时前
算法与数据结构---并查集(Union-Find)
数据结构·c++·算法
茉莉玫瑰花茶1 小时前
ProtoBuf - 1 - 下载和环境配置
开发语言·c++·protobuf
_OP_CHEN1 小时前
C++进阶:(十六)从裸指针到智能指针,C++ 内存管理的 “自动驾驶” 进化之路
开发语言·c++
爱学习的小邓同学1 小时前
C++ --- map/set的使用
开发语言·c++
MSTcheng.1 小时前
【C++进阶】继承(下)——挖掘继承深处的奥秘!
开发语言·c++
学困昇1 小时前
Linux基础开发工具(上):从包管理到“进度条”项目实战,掌握 yum/vim/gcc 核心工具
linux·运维·开发语言·数据结构·c++·vim
ALex_zry2 小时前
Rust语言基础分析与C++对比:系统编程的现代演进
java·c++·rust
Molesidy2 小时前
【QT】【C++】基于QT的多线程分别管理GUI和运算任务
开发语言·c++·qt