一.思维导图![](https://i-blog.csdnimg.cn/direct/a5c4b6dd779348eb9317409a3c842fe6.png)
二.使用动态协议包实现服务器与客户端
1. 协议包的结构定义
首先,是协议包的结构定义。在两段代码中,pack_t
结构体都被用来表示协议包:
typedef struct Pack {
int size; // 记录整个协议包的实际大小
enum Type type; // 协议包的类型
char buf[2048]; // 存储实际数据
int count; // 记录buf中已使用的字节数
} pack_t;
enum Type
定义了协议包的类型,例如TYPE_REGIST
和TYPE_LOGIN
,分别表示注册和登录操作。
2. 服务器端的协议包解析
在服务器端代码中,read_pack
函数负责解析从客户端接收到的协议包。该函数的主要步骤如下:
-
读取数据大小:从
buf
中读取前2个字节,表示接下来要读取的数据大小。 -
读取数据:根据读取到的数据大小,从
buf
中读取相应长度的数据。 -
处理数据:将读取到的数据打印出来。
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
结构体中。该函数的主要步骤如下: -
记录数据长度:将数据的长度存储在
buf
的前2个字节中。 -
存储数据:将数据本身存储在
buf
中。 -
更新协议包大小:更新
pack_t
结构体中的size
和count
字段。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;
}