02.07 TCP服务器与客户端的搭建

news/2025/2/10 19:05:29/

一.思维导图

二.使用动态协议包实现服务器与客户端

1. 协议包的结构定义

首先,是协议包的结构定义。在两段代码中,pack_t结构体都被用来表示协议包:

typedef struct Pack {int size;        // 记录整个协议包的实际大小enum Type type;  // 协议包的类型char buf[2048];  // 存储实际数据int count;       // 记录buf中已使用的字节数
} pack_t;

enum Type定义了协议包的类型,例如TYPE_REGISTTYPE_LOGIN,分别表示注册和登录操作。

2. 服务器端的协议包解析

服务器端代码中,read_pack函数负责解析从客户端接收到的协议包。该函数的主要步骤如下:

  1. 读取数据大小:从buf中读取前2个字节,表示接下来要读取的数据大小。

  2. 读取数据:根据读取到的数据大小,从buf中读取相应长度的数据。

  3. 处理数据:将读取到的数据打印出来。

    void read_pack(pack_t* pack) {char *buf = pack->buf;int readed_size = 0;while(1) {short data_size = *(short*)(buf + readed_size);if(data_size == 0) {printf("数据解析完毕\n");break;}readed_size += 2;char temp[data_size + 1];memset(temp, 0, data_size + 1);memcpy(temp, buf + readed_size, data_size);readed_size += data_size;printf("temp = %s\n", temp);}
    }

    3. 客户端的协议包构建

    在客户端代码中,append函数负责将数据按照协议格式添加到pack_t结构体中。该函数的主要步骤如下:

  4. 记录数据长度:将数据的长度存储在buf的前2个字节中。

  5. 存储数据:将数据本身存储在buf中。

  6. 更新协议包大小:更新pack_t结构体中的sizecount字段。

    void append(pack_t* pack, const char* data) {char* buf = pack->buf;int len = strlen(data);*(short*)(buf + pack->count) = len;pack->count += 2;memcpy(buf + pack->count, data, len);pack->count += len;pack->size = pack->count + 8;
    }

    4. 客户端与服务器的交互

    在客户端代码中,用户输入账号和密码后,append函数将数据添加到协议包中,然后通过write函数将协议包发送给服务器

    while(1) {pack_t pack = {0};pack.type = TYPE_LOGIN;char name[20] = "";char pswd[20] = "";printf("请输入账号:");scanf("%19s", name);while(getchar() != 10);printf("请输入密码:");scanf("%19s", pswd);while(getchar() != 10);append(&pack, name);append(&pack, pswd);write(client, &pack, pack.size);
    }

    服务器端,read函数接收客户端发送的协议包,并调用read_pack函数解析数据。

    while(1) {int pack_size = 0;read(client, &pack_size, 4);pack_t pack = {0};read(client, (char*)&pack + 4, pack_size - 4);pack.size = pack_size;read_pack(&pack);
    }

    5. 总结

    通过这两段代码,我们可以看到如何在C语言中实现一个简单的网络协议包的构建与解析。服务器端负责接收和解析协议包,而客户端则负责构建和发送协议包。这种设计模式在网络编程中非常常见,理解其原理对于开发网络应用程序至关重要。

      6.完整代码

1>服务器

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;enum Type {TYPE_REGIST,TYPE_LOGIN
};typedef struct Pack {int size;enum Type type;char buf[2048];int count;
} pack_t;void read_pack(pack_t *pack) {char *buf = pack->buf;int readed_size = 0;while (1) {short data_size = *(short *)(buf + readed_size);if (data_size == 0) {printf("数据解析完毕\n");break;}readed_size += 2;char temp[data_size + 1];memset(temp, 0, data_size + 1);memcpy(temp, buf + readed_size, data_size);readed_size += data_size;printf("temp = %s\n", temp);}
}int main(int argc, const char *argv[]) {if (argc != 2) {printf("请输入端口号\n");return 1;}int port = atoi(argv[1]);int server = socket(AF_INET, SOCK_STREAM, 0);addr_in_t addr = {0};addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr("0.0.0.0");if (bind(server, (addr_t *)&addr, sizeof(addr)) == -1) {perror("bind");return 1;}listen(server, 10);addr_in_t client_addr = {0};int client_addr_len = sizeof(client_addr);int client = accept(server, (addr_t *)&client_addr, &client_addr_len);if (client != -1) {printf("有客户端连接\n");}while (1) {int pack_size = 0;read(client, &pack_size, 4);pack_t pack = {0};read(client, (char *)&pack + 4, pack_size - 4);pack.size = pack_size;read_pack(&pack);}return 0;
}

2>客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;enum Type {TYPE_REGIST,TYPE_LOGIN
};typedef struct Pack {int size;enum Type type;char buf[2048];int count;
} pack_t;void append(pack_t *pack, const char *data) {char *buf = pack->buf;int len = strlen(data);*(short *)(buf + pack->count) = len;memcpy(buf + pack->count + 2, data, len);pack->count += 2;pack->count += len;pack->size = pack->count + 8;
}int main(int argc, const char *argv[]) {if (argc != 2) {printf("请输入端口号\n");return 1;}int port = atoi(argv[1]);int client = socket(AF_INET, SOCK_STREAM, 0);addr_in_t addr = {0};addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr("192.168.60.77");if (connect(client, (addr_t *)&addr, sizeof(addr)) == -1) {perror("connect");return 1;}while (1) {pack_t pack = {0};pack.type = TYPE_LOGIN;char name[20] = "";char pswd[20] = "";printf("请输入账号:");scanf("%19s", name);while (getchar() != '\n');printf("请输入密码:");scanf("%19s", pswd);while (getchar() != '\n');append(&pack, name);append(&pack, pswd);write(client, &pack, pack.size);}return 0;
}

三、基于以上代码,将读取到的一条条代码保存到链表中

1.服务器代码

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>// 定义网络地址结构体类型
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;// 定义协议包类型枚举
enum Type {TYPE_REGIST,  // 注册类型TYPE_LOGIN    // 登录类型
};// 定义协议包结构体
typedef struct Pack {int size;         // 协议包的总大小enum Type type;   // 协议包的类型char buf[2048];   // 数据缓冲区int count;        // 记录缓冲区中已使用的字节数
} pack_t;// 定义链表节点结构体
typedef struct Node {char* data;       struct Node* next; 
} Node;// 创建新节点
Node* create_node(const char* data) {Node* node = (Node*)malloc(sizeof(Node)); if (data != NULL) {size_t len = strlen(data) + 1;node->data = (char*)malloc(len * sizeof(char));if (node->data != NULL) {strcpy(node->data, data);} else {fprintf(stderr, "内存分配失败!\n");exit(EXIT_FAILURE);}
} else {// 处理输入为 NULL 的情况node->data = NULL;
} node->next = NULL; return node;
}// 将数据添加到链表
void append_to_list(Node** head, const char* data) {Node* new_node = create_node(data);if (*head == NULL) {*head = new_node; } else {Node* current = *head;while (current->next != NULL) {current = current->next;}current->next = new_node; }
}// 释放链表内存
void free_list(Node* head) {Node* current = head;while (current != NULL) {Node* next = current->next; free(current->data); free(current); current = next; }
}// 解析协议包并将数据保存到链表
void read_pack(pack_t* pack, Node** head) {char *buf = pack->buf; int readed_size = 0; // 记录已读取的字节数while(1) {short data_size = *(short*)(buf + readed_size); // 读取数据大小if(data_size == 0) {printf("数据解析完毕\n"); break;}readed_size += 2; char temp[data_size + 1]; memset(temp, 0, data_size + 1); memcpy(temp, buf + readed_size, data_size); readed_size += data_size; // 更新已读取的字节数printf("temp = %s\n", temp); append_to_list(head, temp); // 将数据添加到链表}
}// 主函数
int main(int argc, const char *argv[]) 
{if(argc != 2) {printf("请输入端口号\n"); return 1;}int port = atoi(argv[1]); // 将端口号字符串转换为整数// 创建服务器套接字int server = socket(AF_INET, SOCK_STREAM, 0);addr_in_t addr = {0};addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 绑定套接字到地址if(bind(server, (struct sockaddr*)&addr, sizeof(addr)) == -1) {perror("bind"); // 绑定失败return 1;}// 监听连接listen(server, 10);// 接受客户端连接addr_in_t client_addr = {0};int client_addr_len = sizeof(client_addr);int client = accept(server, (struct sockaddr*)&client_addr, &client_addr_len);if(client != -1) {printf("有客户端连接\n"); }Node* head = NULL; // 初始化链表头while(1) {int pack_size = 0;int ret = read(client, &pack_size, 4); // 读取协议包大小if (ret == -1) {printf("客户端断开连接\n"); break; // 退出循环}pack_t pack = {0};ret = read(client, (char*)&pack + 4, pack_size - 4); // 读取协议包数据if (ret == -1) {printf("客户端断开连接\n"); // 客户端断开连接break; // 退出循环}pack.size = pack_size; // 设置协议包大小read_pack(&pack, &head); // 解析协议包并保存数据到链表// 打印链表中的数据Node* current = head;printf("--------保存的数据如下--------\n");while (current != NULL) {printf("链表数据: %s\n", current->data);current = current->next;}printf("\n");}free_list(head); // 释放链表内存return 0;
}

2.客户端代码

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>typedef struct sockaddr_in addr_in_t;  
typedef struct sockaddr addr_t;        
typedef struct sockaddr_un addr_un_t;  // 定义消息类型枚举
enum Type{TYPE_REGIST,    // 注册类型TYPE_LOGIN      // 登录类型
};// 自定义数据包结构体
typedef struct Pack
{int size;           enum Type type;     char buf[2048];    int count;         
}pack_t;void append(pack_t* pack, const char* data)
{char *buf = pack->buf;int len = strlen(data);*(short*)(buf + pack->count) = len;  pack->count += 2;                   memcpy(buf + pack->count, data, len); pack->count += len;                   // 更新数据包总大小(头部8字节 + 数据长度)pack->size = pack->count + 8;         
}int main(int argc, const char *argv[])
{if(argc != 2){printf("Usage: %s <port>\n", argv[0]);return 1;}int port = atoi(argv[1]);  // 将端口参数转为整数// 创建TCP套接字int client = socket(AF_INET, SOCK_STREAM, 0);if(client == -1){perror("socket creation failed");return 1;}// 配置服务器地址信息addr_in_t addr = {0};addr.sin_family = AF_INET;                   addr.sin_port = htons(port);                addr.sin_addr.s_addr = inet_addr("192.168.126.233");// 连接服务器if(connect(client, (addr_t*)&addr, sizeof(addr)) == -1){perror("connect failed");return 1;}while(1){pack_t pack = {0};      pack.type = TYPE_LOGIN; char name[20] = "";char pswd[20] = "";// 获取用户名输入printf("请输入账号:");scanf("%19s", name);while(getchar() != '\n'); // 清空输入缓冲区// 获取密码输入printf("请输入密码:");scanf("%19s", pswd);while(getchar() != '\n'); // 清空输入缓冲区// 将用户名和密码打包到数据包append(&pack, name);append(&pack, pswd);// 发送整个数据包(发送大小为计算后的总大小)write(client, &pack, pack.size);}return 0;
}


http://www.ppmy.cn/news/1570935.html

相关文章

《软件工程》黑书——No.1软件工程的范畴

引言 软件错误&#xff1a;如果该问题确实是由软件引起&#xff0c;可能会给我们的文明社会带来不愉快和灾难性的结局。软件工程&#xff1a;一门学科&#xff0c;目的是生产出没有错误的软件&#xff0c;按时并且在预算内交付&#xff0c;满足用户的需求。更进一步&#xff0…

4. 【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--什么是微服务--微服务设计原则与最佳实践

相比传统的单体应用&#xff0c;微服务架构通过将大型系统拆分成多个独立的小服务&#xff0c;不仅提升了系统的灵活性和扩展性&#xff0c;也带来了许多设计和运维上的挑战。如何在设计和实现微服务的过程中遵循一系列原则和最佳实践&#xff0c;从而构建一个稳定、高效、易维…

MOSSE目标跟踪算法详解

1. 引言 MOSSE算法&#xff08;Multi-Object Spectral Tracking with Energy Regularization&#xff09;是多目标跟踪领域的一座里程碑式成果&#xff0c;被认为是开创性的工作&#xff0c;为后续研究奠定了重要基础。该算法通过创新性地结合频域特征分析与能量正则化方法&am…

npm中央仓库

1、官网地址 npm | Home 2、搜索依赖包

封装descriptions组件,描述,灵活

效果 1、组件1&#xff0c;dade-descriptions.vue <template><table><tbody><slot></slot></tbody> </table> </template><script> </script><style scoped>table {width: 100%;border-collapse: coll…

基于Flask的全国海底捞门店数据可视化分析系统的设计与实现

【FLask】基于Flask的全国海底捞门店数据可视化分析系统的设计与实现&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统系统采用Python语言结合Flask框架开发&#xff0c;利用Pandas、NumP…

Git 日志查看与版本回溯

引言 在软件开发的漫漫长路中&#xff0c;代码就如同我们搭建软件大厦的基石&#xff0c;而 Git 则是一位默默守护并精心管理这些基石的 “管家”。它不仅能记录代码的每一次变动&#xff0c;还提供了强大的日志查看和版本回溯功能&#xff0c;这些功能就像是给开发者配备了一…

客运自助售票小程序的设计与实现ssm+论文源码调试讲解

第3章 系统总体设计 3.1系统功能设计 3.1.1系统功能介绍 本系统的使用用户包括管理员和乘客、司机&#xff0c;管理员的功能为&#xff1a; 管理员管理功能&#xff0c;可以修改密码&#xff0c;来保证系统的安全&#xff0c;也可以管理管理员的账号信息&#xff1b; 乘客管…