云计算百科
云计算领域专业知识百科平台

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

一.思维导图

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

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;
    }

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 02.07 TCP服务器与客户端的搭建
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!