网络编程五--自定义应用层协议

news2024/11/17 7:55:48

写在前面

前面回声服务器/客户端介绍了如何通过对收发IO的控制实现回声服务器/客户端。

在服务器端应用层的处理(协议)可以看作是“回声操作”,即回发客户端发来的消息。而在客户端应用层的处理(协议)则只是简单显示服务器回发的内容。

而协议的通俗理解,就是为了完成数据交换而定好的约定。

本章将通过一个简单的示例展开,如何"自定义应用层协议"。

自定义应用层协议

首先描述下我们打算实现的简单需求:服务器端要根据客户端发来的数据和运算符进行相应运算,并把结果返回给客户端。

根据上述需求,我们需要自定义的应用层协议内容:
客户端:
①要求用户先输入操作数的数量。
②然后要求用户输入①中数量的具体数值。
③最后要求客户输入运算符
这里均假设用户输入的是一个合理的数量(当然也可进行相应的判断限制)。

输入完成后,一起打包发送给服务器。

服务器:
①按客户端的打包顺序解析接收到的数据。即接收数据的第一个字节大小的内容为操作数数量n,接着的n个字节的内容则为实际操作数数值,而最后一个字节的内容则为运算符。
②根据①中解析得到的内容进行相应运算
③将运算结果返回给客户端

通过规定输入前(客户端的应用层)、输出后(服务器端的应用层)的数据的处理方式,实现自定义的应用层协议。

效果预览

效果预览1
效果预览2
效果预览3
因除法还需对除数进行相应非0判断,因此这里不再处理。

最后给出完整代码。

服务器

#include "stdafx.h"
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")

#define BUF_SIZE 1024
#define OPSZ 4

int calculate(int opnum, int opnds[], char op)
{
	int result = opnds[0], i;

	printf("操作数数量: %d, 第一个操作数: %d, 操作符: %c\n", opnum, result, op);

	switch(op)
	{
	case '+':
		for (i = 1; i < opnum; i++)
		{
			result += opnds[i];
		}
		break;

	case '-':
		for (i = 1; i < opnum; i++)
		{
			result -= opnds[i];
		}
		break;

	case '*':
		for (i = 1; i < opnum; i++)
		{
			result *= opnds[i];
		}
		break;

	default:
		for (i = 1; i < opnum; i++)
		{
			result += opnds[i];
		}
		break;
	}

	return result;
}

int _tmain(int argc, _TCHAR* argv[])
{
	if (argc != 2)
	{
		printf("arg error!");
		return -1;
	}

	WSADATA wsaData;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
	{
		printf("WSAStartup error!");
		return -1;
	}

	SOCKET srvSock = socket(PF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == srvSock)
	{
		printf("socket error!");
		WSACleanup();
		return -1;
	}

	SOCKADDR_IN srvAddr;
	memset(&srvAddr, 0, sizeof(srvAddr));
	srvAddr.sin_family = PF_INET;
	srvAddr.sin_addr.s_addr = htonl(ADDR_ANY);
	srvAddr.sin_port = htons(_ttoi(argv[1]));

	if (SOCKET_ERROR == bind(srvSock, (sockaddr*)&srvAddr, sizeof(srvAddr)))
	{
		printf("bind error!");
		closesocket(srvSock);
		WSACleanup();
		return -1;
	}
	
	if (SOCKET_ERROR == listen(srvSock, 5))
	{
		printf("listen error!");
		closesocket(srvSock);
		WSACleanup();
		return -1;
	}

	SOCKADDR_IN cltAddr;
	memset(&cltAddr, 0, sizeof(cltAddr));
	int cltAddrLen = sizeof(cltAddr);

	int opnd_cnt = 0;

	int recvLen = 0;
	char Msg[BUF_SIZE];
	int result = 0;
	for (int i = 0; i < 5; i++)
	{
		printf("Wait client Connect...\n");
		opnd_cnt = 0;
		SOCKET cltSock = accept(srvSock, (sockaddr*)&cltAddr, &cltAddrLen);
		if (INVALID_SOCKET == cltSock)
		{
			printf("accept error!");
			closesocket(srvSock);
			WSACleanup();
			return -1;
		}

		printf("Client %d Connected.\n", i + 1);

		//得到操作数量
		recv(cltSock, (char*)&opnd_cnt, 1, 0);

		recvLen = 0;
		while ( (opnd_cnt * OPSZ + 1) > recvLen )
		{
			int recv_cnt = recv(cltSock, &Msg[recvLen], BUF_SIZE - 1, 0);
			recvLen += recv_cnt;
		}
		
		printf("接收长度: %d, 操作符: %c\n", recvLen, Msg[recvLen - 1]);
		
		//printf("Msg: %s\n", Msg);
		result  = calculate(opnd_cnt, (int*)Msg, Msg[recvLen - 1]);
		send(cltSock, (char*)&result, sizeof(result), 0);

		closesocket(cltSock);
	}

	closesocket(srvSock);
	WSACleanup();

	return 0;
}

客户端

#include "stdafx.h"
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")

#define BUF_SIZE 1024
#define OPSZ 4
#define RLT_SIZE 4

int _tmain(int argc, _TCHAR* argv[])
{
	if (argc != 3)
	{
		printf("arg error!");
		return -1;
	}

	WSADATA wsaData;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
	{
		printf("WSAStartup error!");
		return -1;
	}

	SOCKET srvSock = socket(PF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == srvSock)
	{
		printf("socket error!");
		WSACleanup();
		return -1;
	}


	SOCKADDR_IN srvAddr;
	memset(&srvAddr, 0, sizeof(srvAddr));

	srvAddr.sin_family = PF_INET;
	srvAddr.sin_addr.s_addr = inet_addr(argv[1]);
	srvAddr.sin_port = htons(_ttoi(argv[2]));
	int srvAddrLen = sizeof(srvAddr);

	if (SOCKET_ERROR == connect(srvSock, (sockaddr*)&srvAddr, srvAddrLen))
	{
		printf("connect error!");
		closesocket(srvSock);
		WSACleanup();
		return -1;
	}

	printf("Connected...");

	int opnd_cnt = 0;
	fputs("操作数数量: ", stdout);
	scanf("%d", &opnd_cnt);

	char Msg[BUF_SIZE];
	Msg[0] = (char)opnd_cnt;

	for (int i = 0; i < opnd_cnt; i++)
	{
		printf("操作数: ", stdout);
		scanf("%d", (int*)&Msg[i * OPSZ + 1]);
	}

	//清除输入缓存
	fgetc(stdin);

	fputs("操作符: ", stdout);
	scanf("%c", &Msg[opnd_cnt * OPSZ + 1]);

	printf("Send to Server: %c\n", Msg[opnd_cnt * OPSZ + 1]);
	send(srvSock, Msg, opnd_cnt * OPSZ + 2, 0);
	
	int result = 0;
	recv(srvSock, (char*)&result, RLT_SIZE, 0);

	printf("操作结果: %d\n", result);
	closesocket(srvSock);
	WSACleanup();

	getchar();
	getchar();

	return 0;
}


总结

本章通过一个简单的运算示例介绍如何自定义应用层协议,所谓的协议就是为了完成数据交换而定好的约定,只不过这里的约定应用在了应用层,即输入前(客户端的应用层)、输出后(服务器端的应用层)的数据的处理方式。

万变不离其宗,后续若碰到类似需求,也可按实际情况扩展即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/503509.html

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

相关文章

Mysql获取指定时间范围数据

MySQL获取某个时间范围内的数据 TO_DAYS(date)函数。 to_days()&#xff1a;返回从0000年至当前日期的总天数。 目录 1、今天(TO_DAYS()) 2、今天昨天(TO_DAYS()) 3.近7天(DATE_SUB()) 5.本月(DATE_FORMAT()) 6.上一月(PERIOD_DIFF()) 7.本季度 8.上季度 9.本年 ​1…

MyBatis介绍、创建与使用

文章目录 一、MyBatis是什么二、学习 MyBatis 的意义三、配置 MyBatis 开发环境&#xff08;一&#xff09;配置 MyBatis 的相关依赖&#xff08;二&#xff09;配置数据库连接字符串和 MyBatis&#xff08;保存的 XML 目录&#xff09;1. 创建并编辑配置文件2. 配置 MyBatis 的…

[GFCTF 2021]ez_calc day3

目录 此时我脑袋产生了几个问题&#xff1a; 但是尝试了几个弱密码发现不对&#xff0c;找一下有没有代码泄露的点。 咦发现ctrlu查看的源码和f12显示的竟然不一样我丢&#xff0c;涨知识了。 js大小写有漏洞之前遇见过 <!--if(req.body.username.toLowerCase() ! admin…

Maven与spring学习

目录 该如何学习Maven&#xff0c;是先该学习spring还是先学习Maven 能讲一下该如何学习Maven吗&#xff1f; 火狐浏览器有能让网页翻译成为中文的插件吗 秋田和柴犬是同一个狗吗 该如何学习Maven&#xff0c;是先该学习spring还是先学习Maven 学习Maven可以与学习Spring同…

FPGA学习总结7:选择译码器实现

本博客以modelsim平台为例&#xff0c;实现了一个三八译码器&#xff1b; Step1.在modelsim创建新的工程&#xff1b; file-》new-》project&#xff0c;给工程命名&#xff0c;添加相应的文件&#xff1b; Step2.添加事先准备的源文件和Testbench文件&#xff1b; 3-8译码器…

p72 内网安全-域横向 CSMSF 联动及应急响应初识

数据来源 演示案例 MSF&CobaltStrike 联动 ShellWEB 攻击应急响应朔源-后门,日志WIN 系统攻击应急响应朔源-后门,日志,流量临时给大家看看学的好的怎么干对应 CTF 比赛 案例1 - MSF&CobaltStrike联动Shell CS下载与安装&#xff1a;cobaltstrike的安装与基础使用_co…

数字信号处理4

昨天是星期天&#xff0c;休息了一天&#xff0c;今天继续学习&#xff1a; 1、连续幅度信号的量化&#xff1a; 一个数字信号是一个数字序列&#xff0c;也就是说这个数字信号就可以用有限个数字来表示。 量化&#xff1a;通过把每个样本值表示为一个有限的数字&#xff0c…

CRM系统建设中需关注哪些关键节点?

随着数字化时代的到来&#xff0c;企业越来越依赖于互联网技术和数据管理&#xff0c;而客户关系管理&#xff08;CRM&#xff09;系统已经成为企业实现数字化转型和提升客户体验的关键工具之一。然而&#xff0c;在构建CRM系统的过程中&#xff0c;一些企业常常被一些关键节点…

键树(Keyword Tree)操作(插入删除查询)-双链树C语言实现_20230508

键树(Keyword Tree)操作&#xff08;插入/删除/查询)-双链树C语言实现_20230508 前言 键树称为数字查找树或者搜索提示树&#xff0c;树的度数d≥2&#xff0c;树中每个结点储存的不是完整的关键字&#xff0c;而是只含有组成关键字的符号&#xff0c;常见情况为字符或数字。…

wsl2 ubuntu ip 自动同步到windows端的vscode remote ssh(wsl2 ubuntu 静态ip 固定ip)

环境信息 ​ wsl2 中linux版本&#xff1a;ubuntu ​ windows版本&#xff1a; win10/win11 问题描述 最近装了wsl2&#xff0c;使用vscode远程连接wsl2时遇到了如下问题&#xff1a; 1、wsl2的ip无法固定 2、wsl2的ssh服务不能自动开启。 尝试了网上许多方法&#xff0c;…

Vue核心 Vue生命周期

1.18. Vue生命周期 1.18.1. 引出生命周期 生命周期 又名生命周期回调函数、生命周期函数、生命周期钩子是什么: Vue在关键时刻帮我们调用的一些特殊名称的函数。生命周期函数的名字不可更改&#xff0c;但函数的具体内容是程序员根据需求编写的。生命周期函数中的this指向是…

JUC并发编程与源码分析笔记13-AbstractQueuedSynchronizer之AQS

前置知识 公平锁和非公平锁可重入锁自旋思想LockSupport数据结构之双向链表设计模式之模板设计模式 AQS入门级别理论知识 是什么 AbstractQueuedSynchronizer&#xff1a;抽象的队列同步器。 用来实现锁或其他同步器组件的公共基础部分的抽象实现&#xff0c;是重量级基础框…

Hive知识回顾2

一、分桶表 1.1分桶表的概念 分桶表也叫做桶表&#xff0c;源自建表语法中bucket单词。是一种用于优化查询而设计的表类型。该功能可以让数据分解为若干个部分易于管理。 在分桶时&#xff0c;我们要指定根据哪个字段将数据分为几桶&#xff08;几个部分&#xff09;。默认规则…

人脸识别中的深度学习

深度学习在人脸识别中的应用 人脸识别的过程包括&#xff1a; 人脸检测人脸对齐特征提取&#xff08;在数学上&#xff0c;实质上是&#xff1a;空间变换&#xff09;特征度量 其中&#xff0c;特征提取与度量&#xff0c;是人脸识别问题中的关键问题&#xff0c;也是相关研究…

使用 ChatGPT 辅助学习——为自己找一个老师

我们每个人都有许多标签&#xff0c;例如高中生、成绩中等、文科&#xff0c;根据这些标签我和其他拥有相同标签的人分配了相同的教程、班级和老师&#xff0c;这可以带来效率上的提升&#xff0c;因为同一份教程、老师就可以服务几十上百人&#xff0c;而无须为每个人定制&…

软件测试工程师的核心竞争力究竟是什么?

对于测试员而言&#xff0c;了解自己岗位的核心竞争力是非常重要的。在职业初期&#xff0c;许多人认为掌握代码才是软件测试的核心竞争力&#xff0c;但是随着经验的增加&#xff0c;我们会发现真正的核心竞争力是由多个方面组成的。 首先&#xff0c;测试人员需要具备良好的测…

BERT 的面试题

BERT 的简介 1、BERT 是什么&#xff1f;它是用来做什么的&#xff1f; BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;是由Google开发的自然语言处理模型&#xff0c;是一种预训练模型&#xff0c;可以用于多种自然语言处理任务&…

【python自动化脚本—实现excel参数化循环调用判定结果】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、需求背景二、我要做什么三、已有接口四、脚本实现五、实现效果图 前言 为提升自己的python能力&#xff0c;记录在工作中实现的自动化脚本&#xff0c;温故…

51单片机(七)定时器

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

写文章的软件-一款写作文章的软件

写文章的软件 写文章的软件是一种工具&#xff0c;可以帮助用户轻松快速地创作高质量的文章。该软件一般包括以下几个主要功能&#xff1a; 写作界面&#xff1a;提供简洁、美观的写作界面&#xff0c;让用户专注于文章创作&#xff0c;同时可以进行排版、字体、颜色等调整。 …