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

news2025/2/9 1:40:49

一.思维导图

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

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.coloradmin.cn/o/2295016.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【CubeMX+STM32】SD卡 文件系统读写 FatFs+SDIO+DMA

本篇&#xff0c;将使用CubeMXKeil&#xff0c;创建一个SD卡的 FatFSSDIODMA 文件系统读写工程。 目录 一、简述 二、CubeMX 配置 FatFSSDIO DMA 三、Keil 编辑代码 四、实验效果 实现效果&#xff0c;如下图&#xff1a; 一、简述 上两篇&#xff0c;已循序渐进讲解了SD、…

51单片机之使用Keil uVision5创建工程以及使用stc-isp进行程序烧录步骤

一、Keil uVision5创建工程步骤 1.点击项目&#xff0c;新建 2.新建目录 3.选择目标机器&#xff0c;直接搜索at89c52选择&#xff0c;然后点击OK 4.是否添加起吊文件&#xff0c;一般选择否 5.再新建的项目工程中添加文件 6.选择C文件 7.在C文件中右键&#xff0c;添加…

aws(学习笔记第二十七课) 使用aws API Gateway+lambda体验REST API

aws(学习笔记第二十七课) 使用aws API Gatewaylambda体验REST API 学习内容&#xff1a; 使用aws API Gatewaylambda 1. 使用aws API Gatewaylambda 作成概要 使用api gateway定义REST API&#xff0c;之后再接收到了http request之后&#xff0c;redirect到lambda进行执行。…

5 前端系统开发:Vue2、Vue3框架(上):Vue入门式开发和Ajax技术

文章目录 前言一、Vue框架&#xff08;简化DOM操作的一个前端框架&#xff09;&#xff1a;基础入门1 Vue基本概念2 快速入门&#xff1a;创建Vue实例&#xff0c;初始化渲染&#xff08;1&#xff09;创建一个入门Vue实例&#xff08;2&#xff09;插值表达式&#xff1a;{{表…

快速在wsl上部署学习使用c++轻量化服务器-学习笔记

知乎上推荐的Tinywebserver这个服务器&#xff0c;快速部署搭建&#xff0c;学习c服务器开发 仓库地址 githubhttps://link.zhihu.com/?targethttps%3A//github.com/qinguoyi/TinyWebServerhttps://link.zhihu.com/?targethttps%3A//github.com/qinguoyi/TinyWebServer 在…

2025年Android NDK超全版本下载地址

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

React 设计模式:实用指南

React 提供了众多出色的特性以及丰富的设计模式&#xff0c;用于简化开发流程。开发者能够借助 React 组件设计模式&#xff0c;降低开发时间以及编码的工作量。此外&#xff0c;这些模式让 React 开发者能够构建出成果更显著、性能更优越的各类应用程序。 本文将会为您介绍五…

B站自研的第二代视频连麦系统(上)

导读 本系列文章将从客户端、服务器以及音视频编码优化三个层面&#xff0c;介绍如何基于WebRTC构建视频连麦系统。希望通过这一系列的讲解&#xff0c;帮助开发者更全面地了解 WebRTC 的核心技术与实践应用。 背景 在文章《B站在实时音视频技术领域的探索与实践》中&#xff…

使用Python实现PDF与SVG相互转换

目录 使用工具 使用Python将SVG转换为PDF 使用Python将SVG添加到现有PDF中 使用Python将PDF转换为SVG 使用Python将PDF的特定页面转换为SVG SVG&#xff08;可缩放矢量图形&#xff09;和PDF&#xff08;便携式文档格式&#xff09;是两种常见且广泛使用的文件格式。SVG是…

[渗透测试]热门搜索引擎推荐— — shodan篇

[渗透测试]热门搜索引擎推荐— — shodan篇 免责声明&#xff1a;本文仅用于分享渗透测试工具&#xff0c;大家使用时&#xff0c;一定需要遵守相关法律法规。 除了shodan&#xff0c;还有很多其他热门的&#xff0c;比如&#xff1a;fofa、奇安信的鹰图、钟馗之眼等&#xff0…

基于物联网技术的智能寻车引导系统方案:工作原理、核心功能及系统架构

本文专为IT技术员、软件开发工程师及智能停车领域专业人士打造&#xff0c;旨在深入剖析智能寻车引导系统的构建与优化过程。如需获取详细解决方案可前往文章最下方获取&#xff0c;如有项目需求及技术合作可私信作者。 智能寻车引导系统是一种集智能化、自动化于一体的停车管理…

【React】合成事件语法

React 合成事件是 React 为了处理浏览器之间的事件差异而提供的一种跨浏览器的事件系统。它封装了原生的 DOM 事件&#xff0c;提供了一致的事件处理机制。 合成事件与原生事件的区别&#xff1a; 合成事件是 React 自己实现的&#xff0c;封装了原生事件。合成事件依然可以通…

Redis02 - 持久化

Redis持久化 文章目录 Redis持久化一&#xff1a;持久化简介1&#xff1a;Redis为什么要进行持久化2&#xff1a;Redis持久化的方式 二&#xff1a;RDB持久化介绍1&#xff1a;手动触发RDB2&#xff1a;自动触发RDB3&#xff1a;redis.conf中进行RDB的配置4&#xff1a;RDB优缺…

初始JavaEE篇 —— Spring Web MVC入门(上)

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;JavaEE 目录 RequestMappingg 注解介绍 Postman的介绍与使用 PostMapping 与 GetMapping 注解 构造并接收请求 接收简单参数 接收对象…

Leetcode—487. 最大连续1的个数 II【中等】Plus

2025每日刷题&#xff08;210&#xff09; Leetcode—487. 最大连续1的个数 II 实现代码 class Solution { public:int findMaxConsecutiveOnes(vector<int>& nums) {int zeros 0;int ans 0;for(int l 0, r 0; r < nums.size(); r) {if(nums[r] 0) {zeros;…

【MySQL】窗口函数详解(概念+练习+实战)

文章目录 前言1. SQL窗口函数 1.1 窗口函数概念1.2 窗口函数语法1.3 常见窗口函数 1.3.1 聚合窗口函数1.3.2 专用窗口函数 1.4 窗口函数性能比较 2. LeetCode 例题 2.1 LeetCode SQL 178&#xff1a;分数排名2.2 LeetCode SQL 184&#xff1a;最高工资2.3 LeetCode SQL 185&am…

前端组件标准化专家Prompt指令的最佳实践

前端组件标准化专家Prompt 提示词可作为项目自定义提示词使用&#xff0c;本次提示词偏向前端开发的使用&#xff0c;如有需要可适当修改关键词和示例 推荐使用 Cursor 中作为自定义指令使用Cline 插件中作为自定义指令使用在力所能及的范围内使用最好的模型&#xff0c;可以…

18爬虫:关于playwright相关内容的学习

1.如何在python中安装playwright 打开pycharm&#xff0c;进入终端&#xff0c;输入如下的2个命令行代码即可自动完成playwright的安装 pip install playwright ——》在python中安装playwright第三方模块 playwright install ——》安装playwright所需的工具插件和所支持的…

docker Error response from daemon: Get “https://registry-1.docker.io/v2/ 的问题处理

docker Error response from daemon: Get "https://registry-1.docker.io/v2/ 的问题处理 最近pull 数据 发现 docker 有如下错误 文章目录 docker Error response from daemon: Get "https://registry-1.docker.io/v2/ 的问题处理报错问题检查网络连接解决方案&…

【Linux系统】线程:线程的优点 / 缺点 / 超线程技术 / 异常 / 用途

1、线程的优点 创建和删除线程代价较小 创建一个新线程的代价要比创建一个新进程小得多&#xff0c;删除代价也小。这种说法主要基于以下几个方面&#xff1a; &#xff08;1&#xff09;资源共享 内存空间&#xff1a;每个进程都有自己独立的内存空间&#xff0c;包括代码段…