引言在 Linux 网络编程中传输层提供两种核心协议TCP传输控制协议和UDP用户数据报协议。它们各有特点适用于不同的应用场景。特性TCPUDP连接性面向连接三次握手无连接可靠性可靠确认重传不可靠尽最大努力数据边界流式服务无边界数据报服务有边界传输效率较低较高适用场景文件传输、网页访问实时音视频、DNS查询今天我们将深入学习 TCP 和 UDP 的编程模型理解它们的核心差异并通过完整的代码示例掌握两种协议的使用方法。第一部分TCP 编程回顾一、TCP 服务端完整代码#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define PORT 6000 #define BUFFER_SIZE 128 int main() { int listen_fd, client_fd; struct sockaddr_in server_addr, client_addr; socklen_t client_len; char buffer[BUFFER_SIZE]; // 1. 创建套接字 listen_fd socket(AF_INET, SOCK_STREAM, 0); if (listen_fd -1) { perror(socket error); exit(1); } // 2. 绑定 IP 和端口 memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(PORT); server_addr.sin_addr.s_addr htonl(INADDR_ANY); if (bind(listen_fd, (struct sockaddr*)server_addr, sizeof(server_addr)) -1) { perror(bind error); exit(1); } // 3. 创建监听队列 if (listen(listen_fd, 5) -1) { perror(listen error); exit(1); } printf(TCP 服务器启动成功端口%d\n, PORT); while (1) { // 4. 接受客户端连接 client_len sizeof(client_addr); client_fd accept(listen_fd, (struct sockaddr*)client_addr, client_len); if (client_fd -1) { perror(accept error); continue; } printf(客户端连接%s:%d\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // 5. 循环接收数据 while (1) { memset(buffer, 0, BUFFER_SIZE); int n recv(client_fd, buffer, BUFFER_SIZE - 1, 0); if (n 0) { printf(客户端已断开\n); break; } if (n -1) { perror(recv error); break; } printf(收到数据%s\n, buffer); send(client_fd, OK, 2, 0); } close(client_fd); } close(listen_fd); return 0; }二、TCP 客户端完整代码#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define PORT 6000 #define BUFFER_SIZE 128 int main() { int sock_fd; struct sockaddr_in server_addr; char buffer[BUFFER_SIZE]; // 1. 创建套接字 sock_fd socket(AF_INET, SOCK_STREAM, 0); if (sock_fd -1) { perror(socket error); exit(1); } // 2. 设置服务器地址 memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(PORT); server_addr.sin_addr.s_addr inet_addr(127.0.0.1); // 3. 连接服务器 if (connect(sock_fd, (struct sockaddr*)server_addr, sizeof(server_addr)) -1) { perror(connect error); exit(1); } printf(连接服务器成功\n); // 4. 循环收发数据 while (1) { printf(请输入消息输入end退出); fgets(buffer, BUFFER_SIZE, stdin); buffer[strlen(buffer) - 1] \0; if (strcmp(buffer, end) 0) { break; } send(sock_fd, buffer, strlen(buffer), 0); memset(buffer, 0, BUFFER_SIZE); recv(sock_fd, buffer, BUFFER_SIZE - 1, 0); printf(服务器响应%s\n, buffer); } close(sock_fd); return 0; }三、netstat 命令使用# 查看所有 TCP 连接netstat -natp# 查看特定端口netstat -natp | grep 6000# 查看 UDP 服务netstat -nauptnetstat 输出字段说明字段含义Proto协议类型TCP/UDPRecv-Q接收缓冲区待处理数据量Send-Q发送缓冲区待处理数据量Local Address本地 IP:端口Foreign Address对端 IP:端口State连接状态TCPPID/Program name进程ID/程序名第二部分TCP 协议栈深入理解一、监听套接字与连接套接字套接字类型功能生命周期监听套接字接收客户端连接请求整个服务周期连接套接字与特定客户端通信单次会话周期二、TCP 缓冲区机制TCP 是流式服务数据在发送方和接收方都有缓冲区重要特性send()成功只表示数据已写入发送缓冲区不代表对方已接收recv()从接收缓冲区读取数据缓冲区为空时阻塞TCP 允许分次读取如recv1字节可逐个接收// 实验修改接收长度为1字节观察现象 // 客户端发送 hello 需要5次 recv 才能读完 int n recv(client_fd, buffer, 1, 0); // 每次只读1字节三、TCP vs UDP 数据接收对比场景TCPUDP发送端多次 send 可能合并每次 sendto 独立报文接收端可分批读取必须单次完整读取数据边界无边界流有边界数据报第三部分UDP 编程一、UDP 服务端完整代码UDP 无需建立连接使用recvfrom()和sendto()收发数据。#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define PORT 6000 #define BUFFER_SIZE 128 int main() { int sock_fd; struct sockaddr_in server_addr, client_addr; socklen_t client_len; char buffer[BUFFER_SIZE]; // 1. 创建套接字注意SOCK_DGRAM sock_fd socket(AF_INET, SOCK_DGRAM, 0); if (sock_fd -1) { perror(socket error); exit(1); } // 2. 绑定 IP 和端口 memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(PORT); server_addr.sin_addr.s_addr htonl(INADDR_ANY); if (bind(sock_fd, (struct sockaddr*)server_addr, sizeof(server_addr)) -1) { perror(bind error); exit(1); } printf(UDP 服务器启动成功端口%d\n, PORT); while (1) { client_len sizeof(client_addr); memset(buffer, 0, BUFFER_SIZE); // 3. 接收数据同时获取客户端地址 int n recvfrom(sock_fd, buffer, BUFFER_SIZE - 1, 0, (struct sockaddr*)client_addr, client_len); if (n -1) { perror(recvfrom error); continue; } printf(收到来自 %s:%d 的数据%s\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buffer); // 4. 回复数据需要指定客户端地址 sendto(sock_fd, OK, 2, 0, (struct sockaddr*)client_addr, client_len); } close(sock_fd); return 0; }二、UDP 客户端完整代码#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define PORT 6000 #define BUFFER_SIZE 128 int main() { int sock_fd; struct sockaddr_in server_addr; socklen_t server_len; char buffer[BUFFER_SIZE]; // 1. 创建套接字 sock_fd socket(AF_INET, SOCK_DGRAM, 0); if (sock_fd -1) { perror(socket error); exit(1); } // 2. 设置服务器地址 memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(PORT); server_addr.sin_addr.s_addr inet_addr(127.0.0.1); server_len sizeof(server_addr); while (1) { printf(请输入消息输入end退出); fgets(buffer, BUFFER_SIZE, stdin); buffer[strlen(buffer) - 1] \0; if (strcmp(buffer, end) 0) { break; } // 3. 发送数据需指定目标地址 sendto(sock_fd, buffer, strlen(buffer), 0, (struct sockaddr*)server_addr, server_len); memset(buffer, 0, BUFFER_SIZE); // 4. 接收回复 recvfrom(sock_fd, buffer, BUFFER_SIZE - 1, 0, NULL, NULL); printf(服务器响应%s\n, buffer); } close(sock_fd); return 0; }三、UDP 核心函数详解recvfrom 函数ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);参数说明sockfd套接字描述符buf接收数据缓冲区len缓冲区大小flags标志位通常为0src_addr输出参数存储发送方地址addrlen输入输出参数地址结构大小sendto 函数ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);参数说明sockfd套接字描述符buf发送数据缓冲区len数据长度flags标志位通常为0dest_addr目标地址addrlen地址结构大小第四部分TCP vs UDP 核心差异一、协议特性对比特性TCPUDP连接性面向连接三次握手无连接可靠性确认重传、顺序保证尽最大努力可能丢包数据边界流式无边界数据报有边界拥塞控制有无传输效率较低较高编程复杂度较高较低二、编程模型对比操作TCPUDP创建套接字socket(AF_INET, SOCK_STREAM, 0)socket(AF_INET, SOCK_DGRAM, 0)绑定地址bind()bind()建立连接服务端listen()accept()不需要建立连接客户端connect()不需要发送数据send()/write()sendto()接收数据recv()/read()recvfrom()获取对端地址accept()返回recvfrom()返回三、数据接收特性对比TCP 流式服务// 发送端多次 send send(fd, hello, 5, 0); send(fd, world, 5, 0); // 接收端可能一次收到 helloworld也可能分次收到 // 数据没有边界UDP 数据报服务// 发送端每次 sendto 独立 sendto(fd, hello, 5, 0, ...); sendto(fd, world, 5, 0, ...); // 接收端每次 recvfrom 对应一次 sendto // 数据有边界必须单次完整读取 // 如果缓冲区太小剩余数据会被丢弃第五部分端口复用与并发一、端口复用规则场景是否可复用说明TCP TCP 同一端口❌ 不可端口已被占用UDP UDP 同一端口❌ 不可端口已被占用TCP UDP 同一端口✅ 可不同协议互不冲突# 验证TCP 6000 和 UDP 6000 可同时存在netstat -naupt | grep 6000二、UDP 的并发特性UDP 是无连接的单线程即可处理多个客户端总结一、TCP vs UDP 速查表对比项TCPUDP套接字类型SOCK_STREAMSOCK_DGRAM服务端流程socket→bind→listen→accept→recv/send→closesocket→bind→recvfrom→sendto→close客户端流程socket→connect→send/recv→closesocket→sendto→recvfrom→close数据边界无流有数据报并发实现需要多进程/多线程单线程即可二、代码运行测试# 编译 UDP 程序gcc udp_server.c -o udp_servergcc udp_client.c -o udp_client# 先启动服务器./udp_server# 另一终端启动客户端可多个./udp_client三、面试高频考点TCP 三次握手SYN → SYNACK → ACKTCP 四次挥手FIN → ACK → FIN → ACKTCP vs UDP 区别连接性、可靠性、数据边界端口复用不同协议可绑定同一端口UDP 数据报边界必须单次完整读取本文详细介绍了 TCP 和 UDP 网络编程包括TCP 服务端/客户端完整实现理解面向连接的通信模型UDP 服务端/客户端完整实现理解数据报服务的特点核心差异分析连接性、可靠性、数据边界端口复用与并发UDP 天然支持多客户端课后任务整理 TCP 和 UDP 的对比笔记动手运行两种协议的代码观察 UDP 数据报截断现象减小 recvfrom 缓冲区