写在前面
前面回声服务器/客户端介绍了如何通过对收发IO的控制实现回声服务器/客户端。
在服务器端应用层的处理(协议)可以看作是“回声操作”,即回发客户端发来的消息。而在客户端应用层的处理(协议)则只是简单显示服务器回发的内容。
而协议的通俗理解,就是为了完成数据交换而定好的约定。
本章将通过一个简单的示例展开,如何"自定义应用层协议"。
自定义应用层协议
首先描述下我们打算实现的简单需求:服务器端要根据客户端发来的数据和运算符进行相应运算,并把结果返回给客户端。
根据上述需求,我们需要自定义的应用层协议内容:
 客户端:
 ①要求用户先输入操作数的数量。
 ②然后要求用户输入①中数量的具体数值。
 ③最后要求客户输入运算符
 这里均假设用户输入的是一个合理的数量(当然也可进行相应的判断限制)。
输入完成后,一起打包发送给服务器。
服务器:
 ①按客户端的打包顺序解析接收到的数据。即接收数据的第一个字节大小的内容为操作数数量n,接着的n个字节的内容则为实际操作数数值,而最后一个字节的内容则为运算符。
 ②根据①中解析得到的内容进行相应运算
 ③将运算结果返回给客户端
通过规定输入前(客户端的应用层)、输出后(服务器端的应用层)的数据的处理方式,实现自定义的应用层协议。
效果预览

 
 
 因除法还需对除数进行相应非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;
}
总结
本章通过一个简单的运算示例介绍如何自定义应用层协议,所谓的协议就是为了完成数据交换而定好的约定,只不过这里的约定应用在了应用层,即输入前(客户端的应用层)、输出后(服务器端的应用层)的数据的处理方式。
万变不离其宗,后续若碰到类似需求,也可按实际情况扩展即可。


![[GFCTF 2021]ez_calc day3](https://img-blog.csdnimg.cn/4bce4b4c0b834d7ebc4cf75760c2f90f.png)















