民非企业网站建设费怎么记账深圳设计网站培训
- 作者: 五速梦信息网
- 时间: 2026年03月21日 10:22
当前位置: 首页 > news >正文
民非企业网站建设费怎么记账,深圳设计网站培训,网站怎么换服务器,wordpress响应案例3.3 实践与案例分析
在本章节中#xff0c;我们将通过一些具体的案例来展示如何在实际项目中进行网络编程。这些案例不仅能帮助你理解各个概念#xff0c;还能提升你的实践技能#xff0c;并为你将来的项目提供参考。
3.3.1 案例分析#xff1a;简单的聊天室
聊天室是网…3.3 实践与案例分析
在本章节中我们将通过一些具体的案例来展示如何在实际项目中进行网络编程。这些案例不仅能帮助你理解各个概念还能提升你的实践技能并为你将来的项目提供参考。
3.3.1 案例分析简单的聊天室
聊天室是网络编程中的经典案例通过这一案例您将学习如何构建一个基于TCP协议的服务器/客户端模型实现消息广播机制并处理多线程/进程并发问题。
3.3.1.1 TCP 服务器/客户端模型
在这个部分我们将介绍如何使用TCP协议建立服务器和客户端管理连接并传输数据。
服务器端代码示例
多线程TCP服务器示例
此示例程序展示了如何使用多线程通过TCP协议实现简单的服务器。服务器能够接受多个客户端连接并为每个连接创建一个线程来处理通信。
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include arpa/inet.h
#include pthread.h // 用于线程 [1]#define PORT 8080
#define MAX_CLIENTS 10void *handle_client(void *arg); // 处理客户端连接的函数声明int main() {int server_fd, new_socket;struct sockaddr_in address;int opt 1;int addrlen sizeof(address);pthread_t tid; // 线程标识符 [2]// 创建套接字 [3]if ((server_fd socket(AF_INET, SOCK_STREAM, 0)) 0) {perror(socket failed);exit(EXIT_FAILURE);}// 设置套接字选项允许端口重用 [4]if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, opt, sizeof(opt))) {perror(setsockopt);close(server_fd);exit(EXIT_FAILURE);}// 填写服务器地址信息 [5]address.sin_family AF_INET;address.sin_addr.s_addr INADDR_ANY; // 允许来自任何IP的连接address.sin_port htons(PORT); // 转换端口为网络字节顺序// 将套接字绑定到地址 [6]if (bind(server_fd, (struct sockaddr *)address, sizeof(address)) 0) {perror(bind failed);close(server_fd);exit(EXIT_FAILURE);}// 监听传入的连接 [7]if (listen(server_fd, 3) 0) {perror(listen);close(server_fd);exit(EXIT_FAILURE);}// 接受客户连接并创建线程处理 [8]while ((new_socket accept(server_fd, (struct sockaddr )address, (socklen_t)addrlen)) 0) {if (pthread_create(tid, NULL, handle_client, (void *)new_socket) ! 0) {perror(pthread_create);close(new_socket);}}if (new_socket 0) {perror(accept);close(server_fd);exit(EXIT_FAILURE);}return 0;
}void *handle_client(void *arg) {int sock *(int *)arg; // 客户端的套接字描述符 [9]char buffer[1024] {0};int valread;// 读取并处理来自客户端的数据 [10]while ((valread read(sock, buffer, 1024)) 0) {buffer[valread] \0;// 目前简单地打印接收到的消息printf(Received: %s\n, buffer);}// 关闭客户端套接字并退出线程close(sock);printf(Client disconnected\n);pthread_exit(NULL); // 退出线程 [11]
}[1] 用于线程#include pthread.h 引入了多线程编程的头文件允许使用pthread库来处理并发任务。[2] 线程标识符pthread_t tid 是一个线程标识符用于引用新创建的线程。[3] 创建套接字调用 socket() 函数创建一个套接字通过标记 AF_INET 和 SOCK_STREAM 来创建基于TCP的网络连接。[4] 设置套接字选项通过 setsockopt() 函数允许端口重用以便在程序重启时能立即重新绑定现有端口。[5] 填写服务器地址信息通过 struct sockaddr_in 结构填入IP和端口信息。[6] 套接字绑定通过 bind() 函数将套接字与服务器地址绑定使套接字与网络地址相关联。[7] 监听传入的连接通过 listen() 函数使套接字进入监听状态准备接受传入的客户端连接。[8] 创建线程处理连接使用 accept() 接受连接然后通过 pthread_create() 创建一个新线程为该连接处理通信。[9] 客户端的套接字描述符int sock *(int *)arg 将传递过来的套接字描述符参数转换回整型引用。[10] 读取和处理数据在 handle_client() 函数中使用 read() 循环读取客户端发送过来的数据并显示接收到的内容。[11] 退出线程通过 pthread_exit() 使处理完的线程正常退出和释放资源。
客户端代码示例
示例代码解析
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include arpa/inet.h#define PORT 8080 // 定义服务器端口号 [1]int main() {int sock 0;struct sockaddr_in serv_addr; // 定义服务器地址结构 [2]char *message Hello from client; // 客户端发送的消息 [3]char buffer[1024] {0}; // 缓冲区用于接收服务器响应 [4]// 创建套接字并检查是否成功if ((sock socket(AF_INET, SOCK_STREAM, 0)) 0) {printf(\n Socket creation error \n);return -1;}serv_addr.sin_family AF_INET; // 设置地址族为IPv4 [5]serv_addr.sin_port htons(PORT); // 设置端口号并进行网络字节序转换 [6]// 将IP地址从点分十进制转换为二进制格式if (inet_pton(AF_INET, 127.0.0.1, serv_addr.sin_addr) 0) {printf(\nInvalid address/ Address not supported \n);return -1;}// 建立与服务器的连接if (connect(sock, (struct sockaddr *)serv_addr, sizeof(serv_addr)) 0) {printf(\nConnection Failed \n);return -1;}// 发送数据到服务器send(sock, message, strlen(message), 0);printf(Message sent\n);// 从服务器接收数据int valread read(sock, buffer, 1024); // 读取服务器回应存入缓冲区 [7]printf(%s\n, buffer); // 打印服务器响应 [8]// 关闭套接字close(sock);return 0;
}[1] 服务器端口号这里定义的 PORT 8080 是客户端希望连接的服务器的端口号。[2] 服务器地址结构struct sockaddr_in 是一个包含地址信息的结构体用于存储服务器的IP地址和端口。[3] 客户端消息Hello from client 是客户端发送给服务器的消息内容。[4] 缓冲区char buffer[1024] 用于存储从服务器接收到的数据。[5] 地址族AF_INET 表示使用IPv4协议族。[6] 端口设置htons(PORT) 将主机字节序转换为网络字节序这是网络通讯的标准格式。[7] 读取响应read() 用于从服务器读取数据并存入 buffer 中buffer 的最大大小为1024字节。[8] 打印服务器响应printf(%s\n, buffer) 输出来自服务器的响应内容。
这段代码示例展示了一个简单的客户端程序它通过TCP协议与一个本地服务器假设运行在127.0.0.1端口8080通信发送消息并接收服务器的响应。
3.3.1.2 消息广播机制
消息广播是聊天室设计中的一个重要部分需要服务器将接收到的消息转发给所有已连接的客户端。
示例代码
要实现消息广播可以修改handle_client函数使其能够将接收到的消息发送给所有客户端
示例代码解析
#include pthread.h#define MAX_CLIENTS 10int client_sockets[MAX_CLIENTS]; // 保存客户端套接字 [1]
pthread_mutex_t clients_mutex PTHREAD_MUTEX_INITIALIZER; // 互斥锁初始化 [2]// 处理客户端连接的线程函数
void *handle_client(void *arg) {int sock *(int *)arg; // 客户端套接字 [3]char buffer[1024] {0}; // 缓冲区 [4]int valread;pthread_mutex_lock(clients_mutex); // 加锁修改共享资源 [5]for (int i 0; i MAX_CLIENTS; i) {if (client_sockets[i] 0) {client_sockets[i] sock; // 存储新连接的客户端 [6]break;}}pthread_mutex_unlock(clients_mutex); // 解锁完成修改 [7]// 接收和广播数据while ((valread read(sock, buffer, 1024)) 0) {buffer[valread] \0; // 添加字符串结束符 [8]printf(Received: %s\n, buffer);pthread_mutex_lock(clients_mutex); // 加锁广播消息 [9]for (int i 0; i MAX_CLIENTS; i) {if (client_sockets[i] ! 0) {send(client_sockets[i], buffer, strlen(buffer), 0); // 向所有客户端发送数据 [10]}}pthread_mutex_unlock(clients_mutex); // 解锁广播完成 [11]}close(sock); // 关闭客户端套接字 [12]pthread_mutex_lock(clients_mutex); // 加锁移除客户端 [13]for (int i 0; i MAX_CLIENTS; i) {if (client_sockets[i] sock) {client_sockets[i] 0; // 清除套接字记录 [14]break;}}pthread_mutex_unlock(clients_mutex); // 解锁移除完成 [15]printf(Client disconnected\n);pthread_exit(NULL); // 线程退出 [16]
}[1] 保存客户端套接字int client_sockets[MAX_CLIENTS] 是一个数组用来保存连接的客户端的套接字。[2] 互斥锁初始化pthread_mutex_t clients_mutex 定义了一个互斥锁用于线程同步访问共享资源。[3] 客户端套接字通过传入的参数 arg 提取客户端的套接字描述符。[4] 缓冲区char buffer[1024] 用于存储从客户端接收到的数据。[5] 加锁修改共享资源在添加新客户端记录之前加锁防止其他线程同时修改 client_sockets。[6] 存储新连接的客户端在共享数组 client_sockets 中记录当前客户端的套接字。[7] 解锁完成修改解锁以允许其他线程访问该共享资源。[8] 添加字符串结束符在缓冲区的结尾加上字符串结束符确保其是个有效的C字符串。[9] 加锁广播消息在向所有客户端发送数据前加锁确保数据一致性。[10] 向所有客户端发送数据利用 send() 函数广播消息到每个连接的客户端。[11] 解锁广播完成解锁以让其他线程可以访问该共享资源。[12] 关闭客户端套接字在完成数据读取或连接断开后关闭当前客户端的网络连接。[13] 加锁移除客户端在从 client_sockets 移除客户端套接字前加锁。[14] 清除套接字记录移除已断开连接的客户端记录。[15] 解锁移除完成解锁以允许其他线程相继访问。[16] 线程退出使用 pthread_exit() 结束处理客户端连接的线程。
3.3.1.3 多线程/进程并发处理
为了支持多个客户端的并发连接可以使用多线程或多进程模型。在上面的服务器代码中我们已经使用了多线程模型pthread_create来处理并发连接。
关于多线程的注意事项
在使用多线程时需要注意同步问题。例如在广播消息时访问客户端数组是一个临界区需要使用pthread_mutex进行同步。
多进程模型的替换
多进程模型的实现也类似可以在接受到新连接时创建新的子进程来处理。用fork替换pthread_create即可
示例代码解析
while ((new_socket accept(server_fd, (struct sockaddr )address, (socklen_t)addrlen)) 0) {if (fork() 0) { // 子进程执行 [1]close(server_fd); // 关闭服务器文件描述符 [2]handle_client((void *)new_socket); // 处理客户端请求 [3]exit(0); // 子进程退出 [4]} else { // 父进程继续监听 [5]close(new_socket); // 关闭新连接的 socket [6]}
}[1] 子进程执行 fork() 函数创建一个新的子进程。比较 fork() 的返回值如果为 0则意味着在子进程中执行后续代码。[2] 关闭服务器文件描述符 在子进程中通过 close(server_fd); 关闭父进程的服务器文件描述符 server_fd因为子进程不需要监听新的连接。[3] 处理客户端请求 调用 handle_client() 函数处理新接入的客户端请求传入 new_socket 的地址作为参数。[4] 子进程退出 处理完客户端请求后通过 exit(0); 退出子进程防止子进程执行不必要的父进程代码。[5] 父进程继续监听 如果 fork() 返回值大于 0则表示处于父进程逻辑中父进程将继续监听其他即将到来的客户端连接。[6] 关闭新连接的 socket 在父进程中通过 close(new_socket); 关闭为当前客户端生成的 socket因为此连接将由子进程处理父进程无需保留该连接。
这样子进程会处理每一个新的客户端连接而父进程将继续监听新的连接请求。
通过以上案例分析和代码示例相信你对如何建立一个简单的聊天室有了一定的了解。这包括了如何使用TCP协议创建服务器和客户端、如何实现消息广播机制以及如何处理多线程和多进程并发问题。在理解这些基础上你可以进一步扩展和完善这个项目例如添加用户认证、改进消息格式等。
3.3.2 案例分析文件传输程序
3.3.2.1 TCP 连接管理
在文件传输程序中首先需要保证客户端和服务器之间的连接。TCP是面向连接的协议它允许建立可靠的双向通信链路。下面展示了一个基本的客户端和服务器的TCP连接管理代码
服务器端代码解析
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include arpa/inet.h#define PORT 8080int main() {int server_fd, new_socket;struct sockaddr_in address;int addrlen sizeof(address);// 创建套接字if ((server_fd socket(AF_INET, SOCK_STREAM, 0)) 0) {perror(socket failed);exit(EXIT_FAILURE);}// 配置地址和端口address.sin_family AF_INET; // IPv4协议 [1]address.sin_addr.s_addr INADDR_ANY; // 监听所有接口 [2]address.sin_port htons(PORT); // 端口设置 [3]// 绑定套接字if (bind(server_fd, (struct sockaddr *)address, sizeof(address)) 0) {perror(bind failed);close(server_fd);exit(EXIT_FAILURE);}// 监听if (listen(server_fd, 3) 0) { // 监听队列长度为3 [4]perror(listen);close(server_fd);exit(EXIT_FAILURE);}// 接受连接if ((new_socket accept(server_fd, (struct sockaddr )address, (socklen_t)addrlen)) 0) {perror(accept);close(server_fd);exit(EXIT_FAILURE);}printf(连接成功\n);close(new_socket); // 关闭客户端套接字 [5]close(server_fd); // 关闭服务器套接字 [6]return 0;
}[1] IPv4协议address.sin_family AF_INET 表示使用IPv4地址族。 [2] 监听所有接口INADDR_ANY 让服务器监听所有可用的网络接口例如eth0、lo等。 [3] 端口设置htons(PORT) 将端口号转换为网络字节序这是网络协议所需的字节顺序。 [4] 监听队列长度listen(server_fd, 3) 设置了服务器的监听队列长度为3表示最多可积压3个未处理的连接请求。 [5] 关闭客户端套接字用 close(new_socket) 关闭为客户端创建的套接字。 [6] 关闭服务器套接字服务器不再需要监听其他连接时调用 close(server_fd) 关闭服务器套接字。 示例代码解析
// 客户端代码
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include arpa/inet.h#define PORT 8080 // 定义端口号 [1]int main() {int sock 0;struct sockaddr_in serv_addr;// 创建套接字if ((sock socket(AF_INET, SOCK_STREAM, 0)) 0) { // [2]printf(\nSocket creation error \n);return -1;}serv_addr.sin_family AF_INET; // 使用IPv4协议 [3]serv_addr.sin_port htons(PORT); // 设置端口号并转换字节序 [4]// 转换IP地址if(inet_pton(AF_INET, 127.0.0.1, serv_addr.sin_addr) 0) { // [5]printf(\nInvalid address/ Address not supported \n);return -1;}// 连接到服务器if (connect(sock, (struct sockaddr *)serv_addr, sizeof(serv_addr)) 0) { // [6]printf(\nConnection Failed \n);return -1;}printf(连接成功\n); // 输出连接成功信息 [7]close(sock); // 关闭套接字 [8]return 0;
}[1] 定义端口号#define PORT 8080 定义了用于连接的端口号这里是与服务器通信的重要参数。[2] 创建套接字socket() 函数用于创建一个套接字AF_INET 指定使用IPv4SOCK_STREAM 表示使用面向连接的TCP协议。[3] 使用IPv4协议serv_addr.sin_family AF_INET; 设置地址族为 AF_INET表示使用IPv4协议。[4] 设置端口号并转换字节序htons() 函数用于将主机字节序转换为网络字节序确保端口号的安全传输。[5] 转换IP地址inet_pton() 函数将点分十进制的IP地址转换为网络字节序的形式若转换失败返回错误信息。[6] 连接到服务器connect() 函数用于将客户端的套接字连接到指定的服务器地址。[7] 输出连接成功信息如果连接成功客户端输出“连接成功”这行为标识。[8] 关闭套接字使用 close(sock); 关闭客户端的套接字释放资源。
3.3.2.2 数据封包与文件分块传输
在传输大文件时可以将文件分成若干小块进行传输。为了确保数据的完整性和有效性可以使用自定义协议对数据进行封包与拆包
示例代码解析
#define CHUNK_SIZE 1024 // 定义数据块大小为1024字节 [1]void send_file(FILE *file, int sock) {char buffer[CHUNK_SIZE]; // 用于存储文件数据的缓冲区 [2]int bytes_read;// 读取文件并通过套接字发送while ((bytes_read fread(buffer, sizeof(char), CHUNK_SIZE, file)) 0) { // 从文件中读取数据 [3]if (send(sock, buffer, bytes_read, 0) 0) { // 通过套接字发送数据 [4]perror(send); // 发送失败输出错误信息 [5]break; // 终止循环}}
}void receive_file(FILE *file, int sock) {char buffer[CHUNK_SIZE]; // 用于接收套接字数据的缓冲区 [6]int bytes_received;// 从套接字接收数据并写入文件while ((bytes_received recv(sock, buffer, CHUNK_SIZE, 0)) 0) { // 从套接字接收数据 [7]fwrite(buffer, sizeof(char), bytes_received, file); // 将接收到的数据写入文件 [8]}
}[1] 数据块大小#define CHUNK_SIZE 1024 定义一个常量 CHUNK_SIZE用来指定每次传输的数据块大小为1024字节。这个大小可以根据网络和文件情况调整以达到更优的传输效率。[2] 发送缓冲区在 send_file 函数中char buffer[CHUNK_SIZE] 用来暂存从文件读取的数据准备发送。[3] 从文件中读取数据fread() 函数以块的方式读取数据其中每次最多读取 CHUNK_SIZE 字节的内容。[4] 通过套接字发送数据send() 函数用于将读取到的文件数据通过网络套接字进行发送数据长度为 bytes_read。[5] 发送失败处理若 send() 返回值小于0表示发送失败使用 perror() 打印错误信息并中断传输循环。[6] 接收缓冲区在 receive_file 函数中char buffer[CHUNK_SIZE] 用来暂存从网络接收到的数据。[7] 从套接字接收数据recv() 函数用于通过网络套接字接收数据每次接收至多 CHUNK_SIZE 字节。[8] 将接收到的数据写入文件fwrite() 将从套接字接收到的数据写入至指定文件长度为 bytes_received。
总体而言这段代码实现了从文件读取至缓冲区再通过套接字发送文件数据的功能以及从套接字读取数据并写入文件的接收功能。这是一个简单的文件传输机制。
3.3.2.3 进度显示与传输校验
在文件传输过程中实时显示传输进度可以提高用户体验。同时通过简单的校验如校验和来确保数据的完整性
代码解析
此代码片段展示了两个主要的功能一个是通过网络发送文件内容并实时显示其传输进度另一个是计算文件的简单校验和。
函数send_file_with_progress
void send_file_with_progress(FILE *file, int sock) {char buffer[CHUNK_SIZE];int bytes_read;int total_bytes_sent 0;fseek(file, 0, SEEK_END);int file_size ftell(file);fseek(file, 0, SEEK_SET);while ((bytes_read fread(buffer, sizeof(char), CHUNK_SIZE, file)) 0) {if (send(sock, buffer, bytes_read, 0) 0) {perror(send);break;}total_bytes_sent bytes_read;printf(进度: %.2f%%\n, (total_bytes_sent / (float)file_size) * 100);}
}作用发送文件内容并显示发送进度。参数file 是待发送的文件指针sock 是用于发送数据的套接字描述符。
关键知识点讲解
[1] 缓冲区初始化char buffer[CHUNK_SIZE]; 定义了一个缓冲区用来暂存每次从文件读取的部分数据。[2] 获取文件大小 使用fseek(file, 0, SEEK_END);将文件指针移动到文件末尾。int file_size ftell(file);利用ftell()获取当前文件指针的位置即文件大小。fseek(file, 0, SEEK_SET);将文件指针复位到文件开头位置。 [3] 循环读取与发送 使用fread(buffer, sizeof(char), CHUNK_SIZE, file)循环读取文件数据到缓冲区。使用send(sock, buffer, bytes_read, 0)发送读取到的字节。记录并计算已发送的数据量以展示传输进度。 [4] 显示进度 printf(进度: %.2f%%\n, (total_bytes_sent / (float)file_size) * 100); 利用总数据量与文件大小之比实时显示传输进度。
函数calculate_checksum
unsigned long calculate_checksum(FILE *file) {unsigned long checksum 0;char buffer[CHUNK_SIZE];int bytes_read;rewind(file);while ((bytes_read fread(buffer, sizeof(char), CHUNK_SIZE, file)) 0) {for (int i 0; i bytes_read; i) {checksum buffer[i];}}rewind(file);return checksum;
}作用计算文件数据的简单校验和。参数file是一个指向待计算校验和的文件的指针。
关键知识点讲解
[5] 初始化与重置unsigned long checksum 0; 初始化校验和变量rewind(file); 确保从文件开始位置进行计算。[6] 校验和计算逻辑 在 fread() 读取的数据块上执行循环累加字节到checksum。这是一个简单的字节加总算法不太适合高安全需求场景但用于简单验证或错误检测是足够的。 [7] 文件指针复位在计算完成后再次rewind(file);使得文件指针回到开头以方便后续其他操作。
在使用这些函数时务必确保文件已正确打开套接字已正常连接以避免潜在错误。
在文件传输前后计算文件的校验和并进行比对可确保数据无误传输
FILE file fopen(example_file, rb); // 打开文件以二进制只读模式 [1]
if (!file) {perror(fopen); // 打印错误信息 [2]return;
}unsigned long checksum calculate_checksum(file); // 计算文件校验和 [3]
send(sock, checksum, sizeof(checksum), 0); // 发送校验和 [4]send_file_with_progress(file, sock); // 发送文件并显示进度 [5]fclose(file); // 关闭文件 [6][1] 打开文件以二进制只读模式使用 fopen() 函数以二进制模式 (rb) 打开名为 “example_file” 的文件。函数返回一个 FILE 指针。如果文件成功打开返回指向该文件的指针否则返回 NULL。[2] 打印错误信息如果 fopen() 返回 NULL意味着文件打开失败perror() 将打印出对应的错误信息到标准错误输出并返回函数。[3] 计算文件校验和调用 calculate_checksum(file) 函数计算文件的校验和这通常用于验证文件内容的完整性。[4] 发送校验和使用 send() 函数通过 sock 套接字发送计算出的校验和数据。第三个参数表示要发送的数据的大小。[5] 发送文件并显示进度send_file_with_progress(file, sock) 函数用于通过 sock 套接字发送文件内容并在发送过程中显示进度。具体的实现细节取决于该函数的定义。[6] 关闭文件使用 fclose() 函数关闭先前打开的文件释放相关资源。
在接收端也计算和验证
unsigned long received_checksum;
recv(sock, received_checksum, sizeof(received_checksum), 0); // 接收校验和 [1]FILE *file fopen(received_file, wb); // 打开文件以写入模式 [2]
if (!file) {perror(fopen);return;
}receive_file(file, sock); // 接收文件数据 [3]unsigned long calculated_checksum calculate_checksum(file); // 计算接收数据的校验和 [4]
if (received_checksum calculated_checksum) {printf(文件传输成功校验和匹配。\n); // 校验和匹配 [5]
} else {printf(文件校验和不匹配传输可能损坏。\n); // 校验和不匹配 [6]
}fclose(file); // 关闭文件 [7][1] 接收校验和使用 recv() 函数从套接字 sock 接收一个 unsigned long 类型的校验和并将其存储在 received_checksum 中。 [2] 打开文件以写入模式fopen() 函数以二进制写入模式 (“wb”) 打开或创建文件 received_file。如果文件无法打开或创建使用 perror(fopen) 打印错误信息并返回以终止函数。 [3] 接收文件数据调用 receive_file() 函数从套接字 sock 接收文件数据并写入到打开的 file 指针所指向的文件中。该函数具体实现不在此示例中但它的职责是在传输过程中将数据流接收并保存。 [4] 计算接收数据的校验和通过调用 calculate_checksum(file) 函数来计算所接收的文件数据的校验和。该函数应该能够重新从头读取文件并得出与发送方协议一致的校验和。 [5] 校验和匹配如果接收的校验和 received_checksum 与计算出的校验和 calculated_checksum 相等则打印确认消息表明文件传输成功且完整。 [6] 校验和不匹配如果校验和不匹配意味着文件数据有可能在传输中损坏或丢失打印警告消息。 [7] 关闭文件fclose() 函数用于关闭打开的文件释放系统资源。
此代码块涉及数据接收和完整性验证的基本过程在实现文件传输时校验和机制可以有效检测传输错误以确保数据完整性。
这样一个简易的文件传输程序就基本完成了。通过实践读者可以更好地理解网络编程的概念和技巧。
3.3.3 案例分析HTTP客户端
3.3.3.1 基于TCP的HTTP请求与响应
在构建一个HTTP客户端时首先要了解HTTP协议是基于TCP协议建立连接的。因此我们需要先创建一个TCP套接字并通过这个套接字与服务器进行通信。
步骤介绍 创建TCP套接字使用socket函数创建一个流套接字。连接服务器使用connect连接到指定的服务器和端口。发送HTTP请求通过send函数发送HTTP请求。接收HTTP响应使用recv函数接收服务器的响应。
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include arpa/inet.hint main() {int sock;struct sockaddr_in server_addr;char request[] GET / HTTP/1.1\r\nHost: example.com\r\n\r\n;char response[4096];// 创建套接字if ((sock socket(AF_INET, SOCK_STREAM, 0)) 0) {perror(Socket creation failed);exit(EXIT_FAILURE);}server_addr.sin_family AF_INET;server_addr.sin_port htons(80); // HTTP默认端口 [1]// 将IP地址转换为二进制形式if (inet_pton(AF_INET, 93.184.216.34, server_addr.sin_addr) 0) { // example.com的IP地址 [2]perror(Invalid address/ Address not supported);exit(EXIT_FAILURE);}// 连接服务器if (connect(sock, (struct sockaddr *)server_addr, sizeof(server_addr)) 0) {perror(Connection failed);close(sock);exit(EXIT_FAILURE);}// 发送HTTP请求send(sock, request, strlen(request), 0); // 发送请求数据 [3]// 接收HTTP响应recv(sock, response, sizeof(response), 0); // 接收响应数据 [4]// 输出响应内容printf(HTTP Response:\n%s\n, response);// 关闭套接字close(sock);return 0;
}[1] HTTP默认端口htons(80) 将端口号80转换为网络字节序这个端口号是HTTP协议的默认端口。[2] IP地址转换inet_pton() 函数将IP地址“93.184.216.34”从文本形式转换为二进制形式并存储在 server_addr.sin_addr 中。[3] 发送请求数据send() 函数用于将HTTP请求的字符串通过套接字发送到服务器。[4] 接收响应数据recv() 函数用于接收服务器的响应数据并存储在 response 缓冲中。
该程序演示了一个简单的HTTP客户端使用套接字连接到指定的服务器地址并发送GET请求。收到响应后程序输出响应内容并关闭套接字。
3.3.3.2 URL解析与请求构造
URL解析是HTTP客户端的重要一步。我们需要将URL分解为主机名和路径以便在构造HTTP请求时使用。
步骤介绍 解析URL将URL拆解为协议、主机、端口和路径。构造HTTP请求根据解析出的信息生成完整的HTTP请求字符串。
#include stdio.h
#include stdlib.h
#include string.h// 入门级URL解析函数示例
void parse_url(const char *url, char *host, char *path) {// 查找 ://const char *host_start strstr(url, ://);if (host_start) {host_start 3; // 跳过协议部分 :// [1]} else {host_start url; // 如果没有找到则从url开头解析 [2]}// 查找路径开始位置const char *path_start strchr(host_start, /);if (path_start) {strncpy(host, host_start, path_start - host_start); // 提取主机名 [3]strcpy(path, path_start); // 提取路径 [4]} else {strcpy(host, host_start); // 如果没有路径默认为/ [5]strcpy(path, /);}
}// 构造HTTP GET请求
void construct_http_request(const char *host, const char *path, char *request) {// 使用sprintf构建HTTP请求字符串sprintf(request, GET %s HTTP/1.1\r\nHost: %s\r\n\r\n, path, host); // 格式化为HTTP请求 [6]
}int main() {// 定义URL以及存储主机名、路径和请求的缓冲区char url[] http://example.com/path/to/resource;char host[256], path[256], request[512];// 调用URL解析parse_url(url, host, path);// 构造HTTP GET请求construct_http_request(host, path, request);// 输出解析结果和HTTP请求printf(Host: %s\nPath: %s\nRequest:\n%s\n, host, path, request);return 0;
}[1] 跳过协议部分如果 url 中存在协议如 “http://”strstr 函数用于找到 :// 的位置然后指针 host_start 移动过这三个字符以便定位到主机名的开始位置。[2] 如果没有找到协议如果 url 中不包含协议部分host_start 将指向 url 的开头假设整个 url 都是主机部分。[3] 提取主机名使用 strncpy 复制从 host_start 到 path_start 位置的字符串这些字符组成主机名。[4] 提取路径用 strcpy 将路径部分从 path_start 开始的字符复制到 path形成路径字符串。[5] 主机名和默认路径当找不到路径不存在 /时将整个字符串作为主机名并将路径设定为默认值 “/”。[6] 格式化为HTTP请求sprintf 用于构建 HTTP GET 请求字符串将解析出的 path 和 host 置入适当位置构成一个完整的 HTTP 1.1 请求格式。
这段代码展示了如何简单解析一个 URL 并使用解析结果生成一个 HTTP GET 请求。这在构建基本 HTTP 客户端或学习网络协议时非常有用。
3.3.3.3 响应解析与数据处理
接收到HTTP响应后我们需要解析响应头和响应体以便提取有用的信息如状态码、内容类型和实际数据。
步骤介绍 解析响应头提取状态码、响应头字段和值。处理响应体根据内容类型进行适当的数据处理。
#include stdio.h
#include stdlib.h
#include string.h// 分析HTTP响应示例
void parse_http_response(const char *response) {// 查找响应头和响应体的分界点 [1]const char *header_end strstr(response, \r\n\r\n);if (header_end) {char headers[512]; // 用于存储HTTP头信息 [2]char body[4096]; // 用于存储HTTP响应体 [3]// 解析响应头strncpy(headers, response, header_end - response); // 复制头部信息到headers [4]headers[header_end - response] \0; // 添加字符串结束符 [5]printf(HTTP Headers:\n%s\n, headers);// 提取响应体strcpy(body, header_end 4); // 复制响应体到body [6]printf(HTTP Body:\n%s\n, body);} else {printf(Invalid HTTP response.\n); // 如果没有找到分界点则说明响应无效 [7]}
}int main() {// 虚拟HTTP响应示例 [8]char dummy_response[] HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 13\r\n\r\nh1Hello/h1;parse_http_response(dummy_response); // 调用解析函数 [9]return 0;
}[1] 查找响应头和响应体的分界点使用 strstr() 来查找相应的 CRLFCRLF 字符串 \r\n\r\n表示HTTP头与主体之间的分界。[2] 用于存储HTTP头信息headers 数组用于暂存解析后的HTTP头。[3] 用于存储HTTP响应体body 数组用于保存解析后的HTTP响应体数据。[4] 复制头部信息到 headers使用 strncpy() 函数从响应中提取头部信息到 headers 中。[5] 添加字符串结束符确保 headers 以空字符结尾以便正确形成C风格字符串。[6] 复制响应体到 body使用 strcpy() 从响应中抽取主体部分。[7] 如果没有找到分界点则说明响应无效如果找不到 \r\n\r\n表明响应格式不正确打印错误信息。[8] 虚拟HTTP响应示例dummy_response 是一个模拟的HTTP响应字符串用于测试。[9] 调用解析函数parse_http_response() 函数进行解析打印头信息和主体。
上述代码展示了如何解析HTTP响应提取响应头和响应体并进行基本的打印输出。
通过上述步骤构建一个基本的HTTP客户端能够完整地从URL解析、构造HTTP请求并解析返回的HTTP响应不仅可以深入理解HTTP协议还能提升网络编程能力。
- 上一篇: 庙行网站建设wordpress弹出相册
- 下一篇: 民间it网站建设常德天鹰建设有限公司网站
相关文章
-
庙行网站建设wordpress弹出相册
庙行网站建设wordpress弹出相册
- 技术栈
- 2026年03月21日
-
描述网站建设的基本流程早期网页游戏
描述网站建设的基本流程早期网页游戏
- 技术栈
- 2026年03月21日
-
描述网站的含义电商运营数据分析表格
描述网站的含义电商运营数据分析表格
- 技术栈
- 2026年03月21日
-
民间it网站建设常德天鹰建设有限公司网站
民间it网站建设常德天鹰建设有限公司网站
- 技术栈
- 2026年03月21日
-
民企厂房建设招标网站网站建设要学哪些软件有哪些
民企厂房建设招标网站网站建设要学哪些软件有哪些
- 技术栈
- 2026年03月21日
-
民权网站建设食品网站模板下载
民权网站建设食品网站模板下载
- 技术栈
- 2026年03月21日



