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

写在前面

前面回声服务器/客户端介绍了如何通过对收发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;
}


总结

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

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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值