基于TCP协议和基于UDP协议的通信流程图_udp工作流程图-CSDN博客
目录
[1 函数解析](#1 函数解析)
[1.1 TCP 套接字函数作用](#1.1 TCP 套接字函数作用)
[1.2 UDP 套接字作用](#1.2 UDP 套接字作用)
[三、与 TCP 的关键区别(基于图示)](#三、与 TCP 的关键区别(基于图示))
[2 代码示例](#2 代码示例)
[2.1 TCP](#2.1 TCP)
[2.1.1 TCP Server](#2.1.1 TCP Server)
[2.1.2 TCP Client](#2.1.2 TCP Client)
[2.2 UDP](#2.2 UDP)
[2.2.1 UDP Server](#2.2.1 UDP Server)
[2.2.2 UDP Client](#2.2.2 UDP Client)


基于TCP协议和基于UDP协议的通信流程图_udp工作流程图-CSDN博客
1 函数解析
1.1 TCP 套接字函数作用
一、服务器端函数流程
socket():创建一个套接字(Socket),它是网络通信的端点,相当于一个"文件描述符",用于后续绑定、监听和收发数据。bind():将创建的套接字与本地 IP 地址和端口号绑定,使服务器能在指定地址上接收连接请求。listen():将套接字设置为监听状态,开始等待客户端的连接请求。此时服务器进入"被动打开"状态,内核会维护一个连接队列。accept():从监听队列中取出一个已完成的连接,创建一个新的套接字用于与该客户端通信。原监听套接字继续等待其他连接。recv()/send():通过accept()返回的新套接字进行数据的接收和发送。TCP 是面向字节流的,因此send()和recv()不保证一次发送对应一次接收,需应用层自行处理消息边界。close():关闭套接字,释放资源,终止连接。TCP 会执行四次挥手过程以确保数据完整传输。
二、客户端函数流程
socket():同样创建一个套接字,作为客户端通信的端点。connect():主动向服务器的 IP 和端口发起连接请求,触发 TCP 三次握手。成功后,客户端与服务器建立可靠连接。send()/recv():连接建立后,通过该套接字发送和接收数据。close():通信结束后关闭套接字,释放资源。
三、关键机制说明
- 三次握手 :由
connect()和accept()触发,确保双方都准备好通信。 - 四次挥手 :由
close()触发,确保数据完整传输后断开连接。 - 阻塞与非阻塞 :默认情况下,
accept()、connect()、recv()等函数是阻塞的,即调用后会等待直到操作完成。可通过设置非阻塞模式或使用 I/O 多路复用(如select、epoll)优化性能。 - 套接字类型 :图中使用的是流式套接字(
SOCK_STREAM),对应 TCP 协议;若使用数据报套接字(SOCK_DGRAM),则对应 UDP 协议,无需listen()和accept()。
四、实际应用建议
- 服务器端:通常采用多线程或 I/O 多路复用处理多个客户端连接,避免单线程阻塞。
- 客户端 :连接前需确保服务器已启动并监听,否则
connect()会失败。 - 错误处理 :所有函数调用后都应检查返回值,处理可能的错误(如
ECONNREFUSED、ETIMEDOUT等)。
1.2 UDP 套接字作用
与之前的 TCP 流程图相比,最显著的区别在于 没有 listen()、accept() 和 connect() ,且数据收发使用的是 sendto() 和 recvfrom()。
- 服务器先启动并绑定端口等待。
- 客户端直接向服务器发送数据(
sendto)。 - 服务器收到数据(
recvfrom),得知客户端地址。 - 服务器利用获取到的地址向客户端回传数据(
sendto)。
以下是图中各函数的详细含义及 UDP 通信的特点解析:
一、服务器端
socket():创建一个 UDP 类型的套接字。在代码中通常体现为第二个参数使用SOCK_DGRAM(数据报套接字),而不是 TCP 的SOCK_STREAM。bind():将套接字绑定到特定的 IP 地址和端口。这是必须的,因为服务器需要一个固定的"地址"来接收数据,否则客户端不知道往哪里发。recvfrom():接收数据 。- 这是一个阻塞函数,程序会停在这里等待数据到来。
- 关键点 :它不仅接收数据内容,还会获取发送方的地址信息(IP 和端口)。这样服务器才知道是谁发的,以便稍后回复。
sendto():发送数据 。- 因为 UDP 是无连接的,服务器没有像 TCP 那样建立一个专属的"连接通道"。
- 所以每次发送数据时,必须明确指定目标地址 (即刚才通过
recvfrom获取到的客户端地址)。
close():关闭套接字,释放资源。
二、客户端
socket():创建一个 UDP 套接字。sendto():发送数据 。- 客户端不需要先建立连接(不需要
connect),直接调用此函数向指定的服务器 IP 和端口发送数据包。
- 客户端不需要先建立连接(不需要
recvfrom():接收数据 。- 等待并接收服务器的回复。虽然图中画在
sendto之后,但在实际编程中,客户端也可以先发制人,或者只发不收。
- 等待并接收服务器的回复。虽然图中画在
close():关闭套接字。
三、与 TCP 的关键区别(基于图示)
| 特性 | TCP (上一张图) | UDP (这张图) | 含义 |
|---|---|---|---|
| 连接建立 | 需要 connect, listen, accept |
无 | UDP 就像寄信/发电报,不需要先打电话确认对方在不在,直接发即可。 |
| 收发函数 | send / recv |
sendto / recvfrom |
TCP 建立了"专线",知道发给谁;UDP 每次发包都要在包裹上写明"收件人地址"(to),收包时也要看"寄件人地址"(from)。 |
| 可靠性 | 高(有重传、排序) | 低 | UDP 不保证数据一定到达,也不保证顺序,可能会丢包。 |
| 速度 | 较慢(握手、确认开销大) | 快 | 头部开销小,无需握手,实时性好(如视频直播、在线游戏)。 |
2 代码示例
2.1 TCP
2.1.1 TCP Server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
const char *hello = "Hello from Server";
// 1. socket(): 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置地址结构
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
address.sin_port = htons(PORT);
// 2. bind(): 绑定 IP 和端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 3. listen(): 开始监听,最大连接队列长度为 3
if (listen(server_fd, 3) < 0) {
perror("listen failed");
exit(EXIT_FAILURE);
}
printf("Server waiting for connections on port %d...\n", PORT);
// 4. accept(): 阻塞等待客户端连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept failed");
exit(EXIT_FAILURE);
}
// 5. recv(): 接收数据
int valread = read(new_socket, buffer, BUFFER_SIZE);
printf("Client message: %s\n", buffer);
// 6. send(): 发送数据
send(new_socket, hello, strlen(hello), 0);
printf("Hello message sent\n");
// 7. close(): 关闭连接
close(new_socket);
close(server_fd);
return 0;
}
2.1.2 TCP Client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char buffer[BUFFER_SIZE] = {0};
const char *hello = "Hello from Client";
// 1. socket(): 创建套接字
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("\n Socket creation error \n");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// 将 IPv4 地址从文本转换为二进制形式
if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
printf("\nInvalid address/ Address not supported \n");
return -1;
}
// 2. connect(): 发起连接(触发三次握手)
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("\nConnection Failed \n");
return -1;
}
// 3. send(): 发送数据
send(sock, hello, strlen(hello), 0);
printf("Hello message sent\n");
// 4. recv(): 接收数据
read(sock, buffer, BUFFER_SIZE);
printf("Server message: %s\n", buffer);
// 5. close(): 关闭连接
close(sock);
return 0;
}
2.2 UDP
2.2.1 UDP Server
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
// 1. socket(): 创建 UDP 套接字 (SOCK_DGRAM)
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
return EXIT_FAILURE;
}
// 配置服务器地址结构
struct sockaddr_in serverAddr, clientAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
memset(&clientAddr, 0, sizeof(clientAddr));
serverAddr.sin_family = AF_INET; // IPv4
serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
serverAddr.sin_port = htons(PORT); // 端口号
// 2. bind(): 绑定 IP 和端口
if (bind(sockfd, (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
perror("bind failed");
close(sockfd);
return EXIT_FAILURE;
}
std::cout << "Server waiting for data on port " << PORT << "..." << std::endl;
char buffer[BUFFER_SIZE];
socklen_t len = sizeof(clientAddr); // 用于存储客户端地址长度
while (true) {
// 3. recvfrom(): 接收数据并获取客户端地址
// 注意:这里会阻塞,直到收到数据
int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0,
(struct sockaddr *)&clientAddr, &len);
if (n > 0) {
buffer[n] = '\0'; // 确保字符串结束
std::cout << "Received: " << buffer << std::endl;
// 4. sendto(): 向刚才获取到的 clientAddr 发送回复
const char *reply = "Hello from Server!";
sendto(sockfd, reply, strlen(reply), 0,
(const struct sockaddr *)&clientAddr, len);
}
}
// 5. close(): 关闭套接字
close(sockfd);
return 0;
}
2.2.2 UDP Client
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
// 1. socket(): 创建 UDP 套接字
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
return EXIT_FAILURE;
}
// 配置服务器地址结构
struct sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
// 将 IP 地址转换为网络字节序 (假设服务器在本地)
inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
char buffer[BUFFER_SIZE];
const char *hello = "Hello from Client";
socklen_t len = sizeof(serverAddr);
// 2. sendto(): 直接向服务器发送数据
sendto(sockfd, hello, strlen(hello), 0,
(const struct sockaddr *)&serverAddr, len);
std::cout << "Message sent to server." << std::endl;
// 3. recvfrom(): 等待服务器回复
// 如果不需要接收回复,可以省略此步
int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0,
(struct sockaddr *)&serverAddr, &len);
if (n > 0) {
buffer[n] = '\0';
std::cout << "Server Reply: " << buffer << std::endl;
}
// 4. close(): 关闭套接字
close(sockfd);
return 0;
}