一、实验目的
定义一个自己的协议帧
二、实验内容
定义一个自己的协议帧,我将以充电控制协议为基础,建立一个自己的简单的帧,并以两台电脑为基础实现客户端服务端之间的通信。
三、实验过程
1.实验步骤:
2.帧格式及说明
帧格式如下表所示,发送帧的方式以字符串的形式发送,字符串固定长度为26个字符。本实验中,笔者只实现了客户端向服务器端发送帧的情况,完成了服务器端对帧的识别,差错校验等。鉴于本实验重在熟悉与理解协议的原理,而非关注于细节,所以笔者在进行帧识别的时候,仅仅检测客户端发过来的帧中是否包含帧头(charging),在差多校验上也没有使用复杂了CRC等校验算法,仅仅对大柜子编号、小柜子编号对应的字符串与校验部分的字符串进行简单匹配。
四、关键代码(为了便于阅读,笔者用伪代码说明算法思想)
五、实验结果
1、发送帧:charging1412121212charging(命令是1:请求充电)
客户端:
服务器端:
2、发送帧:charging2412121212charging(命令是2,查看电量)
客户端:
服务端:
3、发送帧:charging3412121212charging(命令是3,取走设备)
客户端:
服务器端:
六、总结
通过自定义协议帧格式,并动手编程,实现客户端与服务端之间传输,笔者对计算机网络中数据传输有了更加深刻的认识,了解到了底层网络中传输帧的原理,如何识别帧,如何进行校验,如何检测发送方的请求类型等等。至此,笔者想到计算机网络中定义了如此丰富多样的协议,不同的协议定义了不同的数据传输格式,这样一来,如此多的协议在计算机网络中运行,如此多的数据格式在计算机网络中运行,势必会使得计算机网络处理数据的复杂度变大,如何能够用较少的协议满足较多的需求是一个值得研究的课题!
七、实验改进(最终版实验代码2018.6.22)
经过反复多次实验后,对实验进行了一定改进,改进如下:
(1)改变执行程序的方式为命令行执行:
客户端(命令行执行):
服务器端:
(2)增加了加密解密机制(简单)
客户端加密:
for(unsigned int i=0;i<strlen(sendData);i++){
sendData[i]+='a';
}
服务端解密:
for(unsigned int i=0;i<strlen(revData);i++){
revData[i]-='a';
}
八、实验思考
在本实验中,笔者模仿充电协议,实现了一个简单的自定义帧,自己设计了一个简易的帧结构,实现了标记帧类型、帧传输的错误检测,传输过程中的加密等功能。通过这个实验的学习,我们可以很好的和教材中的内容结合在一起,这就是一个简单的协议或者说简单的帧,对协议的概念有了很深的认识,传输过程中进行了差错检测,这可以联系到教材中的CRC等错误检测方法,传输过程中进行了加密,这可以和DES,RSA等信息安全的知识联系起来,等等,通过实验有所获。
附上实验源代码:
client.cpp
//client
#include <WINSOCK2.H>
#include <STDIO.H>
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
WORD sockVersion = MAKEWORD(2,2);
WSADATA data;
if(WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET)
{
printf("invalid socket !");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error !");
closesocket(sclient);
return 0;
}
//char sendData[300];
//用户输入要向服务器请求的信息,为了简便,不再过多提示,只是实现通信功能即可
printf("帧格式:charging1412121212charging\n\n");
char *sendData=argv[1];
//scanf("%s",sendData);
//加密(简单加密一下)
for(unsigned int i=0;i<strlen(sendData);i++){
sendData[i]+='a';
}
send(sclient, sendData, strlen(sendData), 0);
char recData[255];
int ret = recv(sclient, recData, 255, 0);
if(ret > 0)
{
recData[ret] = 0x00;
printf(recData);
printf("\n");
}
closesocket(sclient);
WSACleanup();
return 0;
}
server.cpp
//server
#include <stdio.h>
#include <winsock2.h>
#include<Windows.h>
#include<string.h>
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
//初始化WSA
WORD sockVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(sockVersion, &wsaData)!=0)
{
return 0;
}
//创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(slisten == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
//绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error !");
}
//开始监听
if(listen(slisten, 5) == SOCKET_ERROR)
{
printf("listen error !");
return 0;
}
//循环接收数据
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[255];
while (true)
{
printf("等待连接...\n");
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if(sClient == INVALID_SOCKET)
{
printf("accept error !");
continue;
}
printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
//接收数据
int ret = recv(sClient, revData, 255, 0);
//简单加密的简单解密
for(unsigned int i=0;i<strlen(revData);i++){
revData[i]-='a';
}
if(ret > 0)
{
revData[ret] = 0x00;
printf(revData);printf("\n");
//用户请求进行ping
//这里做一个粗糙的判断,如果客户端发过来的含有ping,就执行ping命令,要求客户端发送的
//ping命令格式正确,并且服务端已经配置好了ping环境
if(strstr(revData,"ping")){
int len=strlen(revData);
for(int i=len-1;i>=4;i--){
revData[i+1]=revData[i];
}
revData[4]=' ';
revData[len+1]=0x00;
system(revData);
}
//用户请求进行充电
if(strstr(revData,"charging")){
//进行校验,检查是否有数据传输错误(粗糙检验)
bool flag=true;
for(int i=10;i<=13;i++){
if(revData[i]!=revData[i+4]){
flag=false;
break;
}
}
if(!flag){
char * sendData = "您的数据在传输过程中出错,请重新发送!\n";
send(sClient, sendData, strlen(sendData), 0);
printf("客户端数据出错!已经让客户端重发!\n");
}
else{
if(revData[8]=='1'){
char sendData[] = "欢迎来充电!您的设备存放位置是:大柜子编号是";
int len=strlen(sendData);
sendData[len]=revData[10];
sendData[len+1]=revData[11];
sendData[len+2]='\0';
char tmp[]="小柜子编号是";
int tmp_len=strlen(tmp);
int len2=strlen(sendData);
for(int i=0;i<tmp_len;i++){
sendData[i+len2]=tmp[i];
}
sendData[len2+tmp_len]='\0';
int len3=strlen(sendData);
sendData[len3]=revData[12];
sendData[len3+1]=revData[13];
sendData[len3+2]='\0';
send(sClient, sendData, strlen(sendData), 0);
printf("有客户来充电,设备存放在大柜子%c%c,小柜子%c%c\n",revData[10],revData[11],revData[12],revData[13]);
}
else if(revData[8]=='2'){
char sendData[] = "您手机的电量已经在显示屏上显示,请直接观看!";
send(sClient, sendData, strlen(sendData), 0);
printf("大柜子%c%c,小柜子%c%c设备的主人来查看电量了\n",revData[10],revData[11],revData[12],revData[13]);
}
else if(revData[8]=='3'){
char sendData[] = "感谢使用,欢迎再次光临!";
send(sClient, sendData, strlen(sendData), 0);
printf("大柜子%c%c,小柜子%c%c设备的主人把设备取走了!\n",revData[10],revData[11],revData[12],revData[13]);
}
}
}
}
//发送数据
closesocket(sClient);
}
closesocket(slisten);
WSACleanup();
return 0;
}