当前位置: 首页 > news >正文

【C语言实战(73)】深入C语言网络编程:UDP与TCP的实战对决

目录

  • 一、引言
  • 二、UDP 协议基础
    • 2.1 UDP 特性
    • 2.2 UDP 核心函数
  • 三、UDP 实战应用
    • 3.1 UDP 客户端实现
    • 3.2 UDP 服务器实现
    • 3.3 实战:UDP 版 “聊天工具”
  • 四、TCP 与 UDP 对比实战
    • 4.1 差异分析
    • 4.2 场景适配
    • 4.3 实战对比:文件传输
  • 五、总结


一、引言

在当今数字化时代,网络编程已成为软件开发中不可或缺的一部分。C 语言作为一种高效、灵活且贴近底层的编程语言,在网络编程领域发挥着至关重要的作用。无论是开发网络服务器、客户端应用,还是实现各种网络协议,C 语言都凭借其出色的性能和对系统资源的精细控制能力,成为众多开发者的首选。

在网络编程的世界里,传输层协议是实现数据可靠传输和高效通信的关键。其中,TCP(传输控制协议)和 UDP(用户数据报协议)是最为常用的两种协议,它们各自具有独特的特点和适用场景。深入理解 TCP 和 UDP 的差异,并能根据实际需求选择合适的协议进行编程,是成为一名优秀网络开发者的必备技能。

本文将带领大家深入探索 C 语言网络编程中 UDP 与 TCP 的奥秘。我们将从 UDP 协议的基础知识入手,详细介绍其特性和核心函数,并通过实战应用展示如何使用 UDP 实现客户端和服务器之间的通信,以及构建一个简单的 UDP 版 “聊天工具” 来验证其实时性。接着,我们将对 TCP 和 UDP 进行全面的对比实战,分析它们在连接建立、可靠性、效率等方面的差异,探讨各自适合的应用场景,并通过实际的文件传输测试,直观地展示两者在传输时间和完整性上的表现。希望通过本文的学习,能帮助读者在 C 语言网络编程中更加熟练地运用 UDP 和 TCP,开发出更加高效、可靠的网络应用程序。

二、UDP 协议基础

2.1 UDP 特性

UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,它在网络通信中具有独特的特性,使其适用于特定的应用场景。

  • 无连接:UDP 在发送数据之前不需要像 TCP 那样在发送方和接收方之间建立连接。这意味着发送端只需知道接收端的 IP 地址和端口号,就可以直接将数据包发送出去 。这种特性使得 UDP 的传输效率更高,因为它省去了连接建立和拆除的开销,减少了通信的延迟,特别适合对实时性要求高的场景,如实时视频会议,参与者之间需要快速地传输音视频数据,UDP 的无连接特性能够让数据迅速地发送出去,确保画面和声音的流畅性。
  • 不可靠:UDP 不保证数据包的可靠传输。在数据传输过程中,如果数据包丢失、出错或乱序到达,UDP 本身不会采取任何重传或纠错措施。这是因为 UDP 设计的初衷是提供一种简单、快速的传输方式,将可靠性的保障交给了应用层来处理。在语音通话中,少量数据包的丢失可能只会导致短暂的声音卡顿,但不会对整体通话质量产生严重影响,而 UDP 的低延迟特性更能满足实时交流的需求。
  • 面向数据报:UDP 以数据报为单位进行数据传输,每个数据报都是独立的,包含完整的源地址、目的地址和数据内容。UDP 不会将多个小数据报合并成一个大数据报,也不会将一个大数据报拆分成多个小数据报进行传输,这就使得应用层需要自己处理数据报的边界问题。假设要发送一个包含 100 个字符的数据,UDP 会将这 100 个字符作为一个完整的数据报一次性发送出去,而不会将其拆分成多个小的数据报进行多次发送。
  • 头部开销小:UDP 的头部非常简洁,仅包含源端口、目的端口、数据长度和校验和等必要字段,总共只有 8 个字节。相比之下,TCP 的头部至少有 20 个字节。较小的头部开销减少了网络传输的负担,提高了传输效率,使得 UDP 在处理大量短小数据包时具有明显优势。

由于 UDP 具备这些特性,它被广泛应用于对实时性要求高、能容忍少量数据丢失的场景,如实时音视频传输、在线游戏、DNS 查询等。在这些场景中,UDP 能够快速地传输数据,满足用户对及时性的需求。

2.2 UDP 核心函数

在 C 语言的网络编程中,使用 UDP 协议进行通信时,需要掌握一些核心函数,这些函数为实现 UDP 通信提供了基础。

  • socket 函数:用于创建一个 UDP 套接字。其函数原型为:
int socket(int domain, int type, int protocol);

domain参数指定协议族,对于 IPv4 网络,通常使用AF_INET;type参数指定套接字类型,对于 UDP 协议,使用SOCK_DGRAM,表示这是一个面向数据报的套接字;protocol参数通常设置为 0,表示使用默认协议。例如:

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);
}

上述代码创建了一个 UDP 套接字,并检查是否创建成功。如果创建失败,perror函数会打印错误信息,exit函数会终止程序。

  • sendto 函数:用于向指定的目标地址发送数据。函数原型如下:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

sockfd是通过socket函数创建的套接字描述符;buf是指向要发送数据缓冲区的指针;len是要发送数据的长度;flags参数一般设置为 0,表示默认的发送方式;dest_addr是一个指向目标地址结构体的指针,包含目标 IP 地址和端口号等信息;addrlen是目标地址结构体的长度。例如:

struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(SERVER_PORT);
dest_addr.sin_addr.s_addr = inet_addr(SERVER_IP);char buffer[] = "Hello, UDP Server!";
ssize_t bytes_sent = sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
if (bytes_sent < 0) {perror("sendto failed");
}

这段代码设置了目标地址,然后使用sendto函数向目标地址发送数据,并检查发送是否成功。

  • recvfrom 函数:用于从 UDP 套接字接收数据,并获取发送方的地址信息。函数原型为:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

sockfd、buf、len、flags与sendto函数中的对应参数含义类似;src_addr是一个指向结构体的指针,用于存储发送方的地址信息;addrlen是一个指针,指向一个变量,用于返回发送方地址结构体的实际长度。例如:

struct sockaddr_in src_addr;
socklen_t addrlen = sizeof(src_addr);
char recv_buffer[BUFFER_SIZE];
ssize_t bytes_received = recvfrom(sockfd, recv_buffer, sizeof(recv_buffer) - 1, 0, (struct sockaddr *)&src_addr, &addrlen);
if (bytes_received < 0) {perror("recvfrom failed");
} else {recv_buffer[bytes_received] = '\0';printf("Received message: %s\n", recv_buffer);
}

此代码从 UDP 套接字接收数据,将发送方的地址信息存储在src_addr中,并打印接收到的消息。

这些核心函数是实现 UDP 通信的关键,通过合理使用它们,可以构建出功能丰富的 UDP 网络应用程序。

三、UDP 实战应用

3.1 UDP 客户端实现

UDP 客户端是与 UDP 服务器进行通信的一端,其主要功能是创建套接字,向服务器发送数据,并接收服务器的反馈。下面详细介绍 UDP 客户端的实现步骤和代码。

  • 创建套接字:首先,使用socket函数创建一个 UDP 套接字。如前文所述,socket函数的第一个参数指定协议族为AF_INET(IPv4),第二个参数指定套接字类型为SOCK_DGRAM(UDP),第三个参数设置为 0 表示使用默认协议。创建成功后,返回一个套接字描述符,后续的通信操作将基于这个描述符进行。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
#define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in server_addr;char buffer[BUFFER_SIZE];// 创建UDP套接字sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 填充服务器地址结构体memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
  • 发送数据:在创建好套接字并设置目标服务器地址后,就可以使用sendto函数向服务器发送数据。需要准备要发送的数据缓冲区,将数据放入缓冲区中,然后调用sendto函数,传入套接字描述符、数据缓冲区指针、数据长度、标志位(通常为 0)、目标地址结构体指针以及目标地址结构体的长度。

// 发送数据到服务器
char *message = “Hello, UDP Server!”;
ssize_t bytes_sent = sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (bytes_sent < 0) {
perror(“sendto failed”);
close(sockfd);
exit(EXIT_FAILURE);
}

  • 接收数据:发送数据后,客户端可以使用recvfrom函数接收服务器的反馈。创建一个接收缓冲区,用于存储接收到的数据,调用recvfrom函数,传入套接字描述符、接收缓冲区指针、接收缓冲区长度、标志位(通常为 0)、发送方地址结构体指针(用于获取发送方的地址信息)以及发送方地址结构体长度的指针。接收数据后,对接收到的数据进行处理,这里简单地将其打印出来。
    // 接收服务器反馈ssize_t bytes_received = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, NULL, NULL);if (bytes_received < 0) {perror("recvfrom failed");} else {buffer[bytes_received] = '\0';printf("Received from server: %s\n", buffer);}close(sockfd);return 0;
}

在上述代码中,客户端创建了一个 UDP 套接字,向指定的服务器地址发送了一条消息,然后等待接收服务器的回复,并将回复打印出来。最后,关闭套接字,释放资源。通过这些步骤,就实现了一个基本的 UDP 客户端。

3.2 UDP 服务器实现

UDP 服务器负责接收客户端发送的数据,并根据需求发送响应。其实现过程主要包括创建套接字、绑定端口、循环接收客户端数据以及发送响应。下面逐步介绍 UDP 服务器的实现步骤和代码。

  • 创建套接字:与 UDP 客户端类似,UDP 服务器首先需要使用socket函数创建一个 UDP 套接字。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>#define SERVER_PORT 8888
#define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in server_addr, client_addr;socklen_t client_addr_len = sizeof(client_addr);char buffer[BUFFER_SIZE];// 创建UDP套接字sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}
  • 绑定端口:创建套接字后,需要将套接字绑定到一个特定的端口上,以便接收客户端发送到该端口的数据。填充服务器地址结构体,包括协议族、端口号和 IP 地址(通常使用INADDR_ANY表示接收来自任何 IP 地址的数据),然后使用bind函数将套接字与地址结构体绑定。

// 填充服务器地址结构体
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;

// 绑定套接字到端口
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror(“bind failed”);
close(sockfd);
exit(EXIT_FAILURE);
}

  • 循环接收客户端数据并发送响应:绑定端口后,服务器进入一个循环,不断使用recvfrom函数接收客户端发送的数据。接收到数据后,对数据进行处理,这里简单地打印出接收到的数据和客户端的地址信息。然后,构造响应消息,使用sendto函数将响应发送回客户端。
    while (1) {// 接收客户端数据ssize_t bytes_received = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&client_addr, &client_addr_len);if (bytes_received < 0) {perror("recvfrom failed");continue;}buffer[bytes_received] = '\0';printf("Received from client (%s:%d): %s\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buffer);// 发送响应给客户端char *response = "Message received successfully!";ssize_t bytes_sent = sendto(sockfd, response, strlen(response), 0, (struct sockaddr *)&client_addr, client_addr_len);if (bytes_sent < 0) {perror("sendto failed");}}close(sockfd);return 0;
}

在上述代码中,UDP 服务器创建了一个 UDP 套接字并绑定到指定端口,然后进入一个无限循环,持续接收客户端发送的数据并发送响应。通过这种方式,实现了一个基本的 UDP 服务器,能够与 UDP 客户端进行通信。

3.3 实战:UDP 版 “聊天工具”

为了更好地展示 UDP 的实时性和应用场景,我们来实现一个 UDP 版的 “聊天工具”。这个聊天工具允许客户端发送消息,服务器将消息转发给其他客户端,模拟多人聊天的场景。

  • 客户端实现:客户端的主要功能是读取用户输入的消息,并将其发送到服务器。同时,客户端需要不断接收服务器转发的其他客户端的消息并显示出来。可以使用多线程来实现发送和接收功能的并发执行,确保用户在发送消息时不会阻塞接收消息的操作。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
#define BUFFER_SIZE 1024int sockfd;
struct sockaddr_in server_addr;void *send_message(void *arg) {char buffer[BUFFER_SIZE];while (1) {fgets(buffer, sizeof(buffer), stdin);buffer[strcspn(buffer, "\n")] = '\0'; // 去除换行符ssize_t bytes_sent = sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));if (bytes_sent < 0) {perror("sendto failed");}if (strcmp(buffer, "exit") == 0) {break;}}pthread_exit(NULL);
}void *receive_message(void *arg) {char buffer[BUFFER_SIZE];while (1) {socklen_t server_addr_len = sizeof(server_addr);ssize_t bytes_received = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&server_addr, &server_addr_len);if (bytes_received < 0) {perror("recvfrom failed");continue;}buffer[bytes_received] = '\0';printf("Received: %s\n", buffer);if (strcmp(buffer, "exit") == 0) {break;}}pthread_exit(NULL);
}int main() {sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);pthread_t send_thread, receive_thread;if (pthread_create(&send_thread, NULL, send_message, NULL) != 0) {perror("pthread_create for send failed");close(sockfd);exit(EXIT_FAILURE);}if (pthread_create(&receive_thread, NULL, receive_message, NULL) != 0) {perror("pthread_create for receive failed");pthread_cancel(send_thread);close(sockfd);exit(EXIT_FAILURE);}pthread_join(send_thread, NULL);pthread_join(receive_thread, NULL);close(sockfd);return 0;
}
  • 服务器实现:服务器的主要任务是接收客户端发送的消息,并将其转发给除发送者之外的其他所有客户端。服务器需要维护一个客户端地址列表,以便知道将消息转发到哪些地址。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>#define SERVER_PORT 8888
#define BUFFER_SIZE 1024
#define MAX_CLIENTS 10int sockfd;
struct sockaddr_in client_addrs[MAX_CLIENTS];
int client_count = 0;void forward_message(const char *message, struct sockaddr_in sender_addr) {for (int i = 0; i < client_count; i++) {if (memcmp(&client_addrs[i], &sender_addr, sizeof(sender_addr)) != 0) {ssize_t bytes_sent = sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&client_addrs[i], sizeof(client_addrs[i]));if (bytes_sent < 0) {perror("sendto failed");}}}
}int main() {sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("bind failed");close(sockfd);exit(EXIT_FAILURE);}char buffer[BUFFER_SIZE];while (1) {struct sockaddr_in sender_addr;socklen_t sender_addr_len = sizeof(sender_addr);ssize_t bytes_received = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&sender_addr, &sender_addr_len);if (bytes_received < 0) {perror("recvfrom failed");continue;}buffer[bytes_received] = '\0';int is_new_client = 1;for (int i = 0; i < client_count; i++) {if (memcmp(&client_addrs[i], &sender_addr, sizeof(sender_addr)) == 0) {is_new_client = 0;break;}}if (is_new_client && client_count < MAX_CLIENTS) {client_addrs[client_count++] = sender_addr;}forward_message(buffer, sender_addr);if (strcmp(buffer, "exit") == 0) {// 处理客户端退出逻辑,从客户端列表中移除for (int i = 0; i < client_count; i++) {if (memcmp(&client_addrs[i], &sender_addr, sizeof(sender_addr)) == 0) {for (int j = i; j < client_count - 1; j++) {client_addrs[j] = client_addrs[j + 1];}client_count--;break;}}}}close(sockfd);return 0;
}
  • 实时性验证:为了验证 UDP 版聊天工具的实时性,可以在多个客户端同时发送和接收消息,观察消息的传输延迟。由于 UDP 是无连接的协议,没有 TCP 那样的连接建立和确认过程,理论上能够实现更低的延迟。在实际测试中,可以发现当网络状况良好时,消息几乎能够实时地在各个客户端之间传输,体现了 UDP 在实时性要求高的场景中的优势。但同时也需要注意,由于 UDP 的不可靠性,在网络不稳定的情况下,可能会出现消息丢失的情况。

四、TCP 与 UDP 对比实战

4.1 差异分析

在网络编程中,TCP 和 UDP 作为两种重要的传输层协议,它们在多个方面存在显著差异,这些差异决定了它们各自的适用场景。

  • 连接建立:TCP 是面向连接的协议,在数据传输之前,需要通过三次握手来建立可靠的连接。这三次握手过程确保了通信双方都已准备好进行数据传输,并且可以协商一些连接参数,如初始序列号等。以一个简单的例子来说,就好比两个人打电话,在通话之前需要先拨通对方号码,等待对方接听,确认双方都能听到声音后才开始正式交流。而 UDP 是无连接的协议,发送方无需与接收方建立连接,直接将数据报发送出去,如同给对方寄明信片,写好地址就可以直接投递,不需要事先通知对方。
  • 可靠性:TCP 提供可靠的数据传输服务,它通过确认机制、重传机制和有序交付来保证数据的完整性和顺序性。当发送方发送数据后,会等待接收方的确认应答,如果在规定时间内没有收到确认,就会重传数据。并且 TCP 会对数据进行编号,接收方根据编号来重新排序数据,确保数据按序到达。在文件传输中,TCP 能保证文件的每个字节都准确无误地传输到接收方。而 UDP 不保证数据的可靠传输,它不会等待接收方的确认,也不对数据进行重传和排序,可能会出现丢包、重复或乱序的情况。在实时视频流传输中,少量数据包的丢失可能只会导致短暂的画面卡顿,而 UDP 的低延迟特性更能满足实时性的要求,所以即使存在一定的不可靠性,也依然适用。
  • 效率:由于 TCP 需要进行连接建立、维护连接状态、流量控制和拥塞控制等复杂操作,其传输开销较大,传输效率相对较低。特别是在网络拥塞的情况下,TCP 会降低发送速率,以避免网络进一步拥塞,这可能会导致数据传输的延迟增加。相比之下,UDP 没有这些复杂的机制,它的头部开销小,传输速度快、延迟低、资源占用少,在处理大量短小数据包时具有明显优势。在在线游戏中,玩家的操作指令需要快速传输到服务器,UDP 的高效性能够满足游戏对实时响应的要求。

4.2 场景适配

基于 TCP 和 UDP 的差异,它们各自适用于不同的应用场景。

  • TCP 适合的场景
    • 文件传输:在进行文件传输时,确保文件的完整性至关重要。TCP 的可靠传输机制能够保证文件的每个字节都准确无误地到达接收方,不会出现数据丢失或损坏的情况。无论是传输文档、图片还是视频文件,TCP 都能确保文件完整地传输,不会因为网络波动而导致文件损坏无法使用。
    • 登录认证:登录认证过程涉及用户的账号密码等重要信息,必须保证数据的可靠性和安全性。TCP 通过可靠传输和连接的稳定性,能够确保认证信息准确无误地在客户端和服务器之间传输,防止信息泄露和篡改,保障用户的账号安全。
  • UDP 适合的场景
    • 实时游戏:在实时游戏中,对延迟非常敏感,玩家的操作指令需要及时传输到服务器,服务器的反馈也需要快速返回给玩家。UDP 的无连接和低延迟特性能够满足实时游戏对数据传输速度的要求,虽然可能会出现少量数据包丢失的情况,但在可接受范围内,不会对游戏的流畅性产生太大影响。例如,在多人在线射击游戏中,玩家的移动、射击等操作能够迅速传输到服务器,保证游戏的实时交互性。
    • 广播消息:广播消息通常需要将数据发送给网络中的多个接收方,不需要与每个接收方建立连接。UDP 支持广播和多播传输,能够高效地将消息发送给多个目标,节省网络资源和时间。在网络中的设备发现协议,如 ARP(地址解析协议),就使用广播来发现设备的 MAC 地址,UDP 的特性使其能够快速完成广播任务。

4.3 实战对比:文件传输

为了更直观地展示 TCP 和 UDP 在文件传输方面的差异,我们分别使用 TCP 和 UDP 来实现文件传输功能,并测试相同文件的传输时间和完整性。

TCP 文件传输实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
#define BUFFER_SIZE 1024int main() {int sockfd, new_sockfd;struct sockaddr_in server_addr, client_addr;socklen_t client_addr_len = sizeof(client_addr);FILE *file;char buffer[BUFFER_SIZE];// 创建TCP套接字sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 填充服务器地址结构体memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = INADDR_ANY;// 绑定套接字到端口if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("bind failed");close(sockfd);exit(EXIT_FAILURE);}// 监听连接if (listen(sockfd, 5) < 0) {perror("listen failed");close(sockfd);exit(EXIT_FAILURE);}// 接受客户端连接new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);if (new_sockfd < 0) {perror("accept failed");close(sockfd);exit(EXIT_FAILURE);}// 打开文件用于写入file = fopen("received_file_tcp.txt", "wb");if (file == NULL) {perror("file open failed");close(new_sockfd);close(sockfd);exit(EXIT_FAILURE);}// 接收文件数据ssize_t bytes_read;while ((bytes_read = recv(new_sockfd, buffer, sizeof(buffer), 0)) > 0) {fwrite(buffer, 1, bytes_read, file);}fclose(file);close(new_sockfd);close(sockfd);return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
#define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in server_addr;FILE *file;char buffer[BUFFER_SIZE];// 创建TCP套接字sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 填充服务器地址结构体memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);// 连接到服务器if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("connect failed");close(sockfd);exit(EXIT_FAILURE);}// 打开文件用于读取file = fopen("test_file.txt", "rb");if (file == NULL) {perror("file open failed");close(sockfd);exit(EXIT_FAILURE);}// 发送文件数据ssize_t bytes_read, bytes_sent;while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {bytes_sent = send(sockfd, buffer, bytes_read, 0);if (bytes_sent < 0) {perror("send failed");break;}}fclose(file);close(sockfd);return 0;
}

UDP 文件传输实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
#define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in server_addr, client_addr;socklen_t client_addr_len = sizeof(client_addr);FILE *file;char buffer[BUFFER_SIZE];// 创建UDP套接字sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 填充服务器地址结构体memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = INADDR_ANY;// 绑定套接字到端口if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("bind failed");close(sockfd);exit(EXIT_FAILURE);}// 打开文件用于写入file = fopen("received_file_udp.txt", "wb");if (file == NULL) {perror("file open failed");close(sockfd);exit(EXIT_FAILURE);}// 接收文件数据ssize_t bytes_received;while ((bytes_received = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&client_addr, &client_addr_len)) > 0) {fwrite(buffer, 1, bytes_received, file);}fclose(file);close(sockfd);return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
#define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in server_addr;FILE *file;char buffer[BUFFER_SIZE];// 创建UDP套接字sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 填充服务器地址结构体memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);// 打开文件用于读取file = fopen("test_file.txt", "rb");if (file == NULL) {perror("file open failed");close(sockfd);exit(EXIT_FAILURE);}// 发送文件数据ssize_t bytes_read, bytes_sent;while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {bytes_sent = sendto(sockfd, buffer, bytes_read, 0, (struct sockaddr *)&server_addr, sizeof(server_addr));if (bytes_sent < 0) {perror("sendto failed");break;}}fclose(file);close(sockfd);return 0;
}

测试结果:在测试环境中,使用相同大小的文件进行传输测试。多次测试后,发现 UDP 的传输时间通常比 TCP 快,这是因为 UDP 无需建立连接和进行复杂的确认机制,能够快速地将数据发送出去。然而,在完整性方面,TCP 表现出色,传输的文件完整无缺,没有出现数据丢失的情况;而 UDP 在传输过程中,由于其不可靠性,可能会出现少量数据包丢失的现象,导致接收的文件与原文件存在细微差异。

通过以上实战对比,我们可以清晰地看到 TCP 和 UDP 在文件传输上的不同表现,这也进一步验证了它们各自的特点和适用场景。在实际应用中,我们应根据具体需求选择合适的协议来实现高效、可靠的网络通信。

五、总结

通过本文的深入探讨,我们对 C 语言网络编程中的 UDP 和 TCP 协议有了全面且深入的理解。UDP 协议以其无连接、不可靠但面向数据报和低开销的特性,在实时性要求高的场景中展现出独特的优势,如实时音视频传输和在线游戏等领域。我们通过实现 UDP 客户端、服务器以及 UDP 版 “聊天工具”,切实感受到了 UDP 在快速数据传输方面的高效性,其低延迟特性使得数据能够迅速在客户端和服务器之间传递,满足了实时交互的需求。

而 TCP 协议则凭借其面向连接、可靠传输以及复杂的控制机制,成为对数据完整性和顺序性要求严格场景的首选,如文件传输和登录认证等应用。在文件传输的实战对比中,TCP 确保了文件的准确无误传输,尽管其传输时间可能相对 UDP 较长,但在可靠性方面表现卓越。

在实际项目开发中,我们应根据具体的需求和场景来精心选择合适的协议。若应用对实时性要求极高,且能容忍一定程度的数据丢失,那么 UDP 无疑是理想之选;而当数据的完整性和可靠性至关重要时,TCP 则是不二之选。同时,我们也可以结合两者的优势,在同一应用中根据不同的功能模块需求,灵活运用 UDP 和 TCP 协议,以实现更高效、可靠的网络通信。

希望本文的内容能为读者在 C 语言网络编程的道路上提供有价值的参考和帮助,鼓励大家在实践中不断探索和创新,开发出更加优秀的网络应用程序。

http://www.dtcms.com/a/572828.html

相关文章:

  • 健身房预约系统SSM+Mybatis(五、预约展示)
  • 记录对某985证书站挖掘
  • 解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
  • 探索LoSA:动态低秩稀疏自适应——大模型高效微调的新突破
  • wordpress表格插件网站建设关键词优化价格
  • Gitlab+Jenkins+Docker+Harbor+K8s+Rancher集群搭建CICD平台
  • Linux服务器安装jdk和maven详解
  • 回归、预测、分类三者关系
  • 微信平台微网站开发乙肝能治好吗
  • Skill 与 Workflow:让自动化更“聪明”的系统架构
  • AI+Python近红外光谱分析机器学习与深度学习实战,覆盖提示词撰写、数据预处理、回归/神经网络/集成学习/迁移学习/可解释性可视化等
  • ESP8266植入程序实现MQTT控制
  • 突击宝典:pytorch面试高频考点精析
  • 建设公司网站的背景意义上海网站开发设计培训
  • 电子商务网站的建设和流程就业培训机构有哪些
  • ICML 2025|基于大语言模型的多比特文本水印方法
  • 在 iOS 18 自动填充密码失败,如何排查?
  • Facebook海外推广:什么样的Facebook账号更好爆量?
  • vue 使用vueCli 搭建vue2.x开发环境,并且指定ts 和less
  • 在 iOS 18 离线徒步地图,如何存储和调用?
  • 【iOS】UICollectionView
  • 广东电白建设集团有限公司网站宫免费网站
  • 混淆 iOS 类名与变量名的实战指南,多工具组合把混淆做成工程能力(混淆 iOS 类名变量名/IPA 成品混淆Ipa/Guard CLI 实操)
  • sysstat 概览与使用:sar/iostat/mpstat/pidstat(含基础原理)
  • 纯flex布局来写瀑布流
  • 智能网联汽车与低空经济‌:结合5G技术拓展新兴产业
  • RDD的特点、算子与创建方法
  • 删除小目标 cocojson
  • 汽车EDI:基于知行之桥的 Gnotec EDI解决方案
  • 垂直行业门户网站建设方案自己做的网站被黑了怎么办