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