一、編寫基于TCP的客戶端實現以下功能:
通過鍵盤按鍵控制機械臂:w(紅色臂角度增大)s(紅色臂角度減小)d(藍色臂角度增大)a(藍色臂角度減小)按鍵控制機械臂
1.基于TCP服務器的機械臂,端口號是8888, ip是Windows的ip;
查看Windows的IP:按住Windows+r 按鍵,輸入cmd , 輸入ipconfig。
2.點擊軟件中的開啟監聽;
3.機械臂需要發送16進制數,共5個字節,協議如下
0xff 0x02 x y 0xff
0xff:起始結束協議,固定的;
0x02:控制機械手臂協議,固定的;
x:指定要操作的機械臂0x00 紅色擺臂0x01 藍色擺臂
y:指定角度
代碼:
#include<myhead.h>
#define SER_IP "192.168.125.64" //服務器IP
#define SER_PORT 8888 //服務器端口號
int main(int argc, const char *argv[])
{//創建套接字文件int sfd=socket(AF_INET,SOCK_STREAM,0);if(sfd==-1){perror("socket error");return -1;}//定義結構體存儲服務器地址struct sockaddr_in sin;sin.sin_family=AF_INET;sin.sin_port=htons(SER_PORT);sin.sin_addr.s_addr=inet_addr(SER_IP);//連接服務器if(connect(sfd,(struct sockaddr*)&sin,sizeof(sin))==-1){perror("connect error");return -1;}puts("connect success");//數據發送//定義控制紅色臂的數組并初始化,范圍是[-90,90]char rbuf[5]={0xff,0x02,0x00,0x00,0xff};//定義控制藍色臂的數組并初始化,范圍是[0,180]unsigned char bbuf[5]={0xff,0x02,0x01,0x00,0xff};//發送初始值send(sfd,rbuf,sizeof(rbuf),0);sleep(1);//防止沾包send(sfd,bbuf,sizeof(bbuf),0);char key=0;while(1){system("stty -icanon");//關閉緩沖區key=getchar();fflush(stdin);switch(key){case 'W':case 'w':{rbuf[3]+=2;//每次操作的角度偏移2度if(rbuf[3]>=90){rbuf[3]=90;}send(sfd,rbuf,sizeof(rbuf),0);}break;case 'S':case 's':{rbuf[3]-=2;if(rbuf[3]<=-90){rbuf[3]=-90;}send(sfd,rbuf,sizeof(rbuf),0);}break;case 'D':case 'd':{bbuf[3]+=2;if(bbuf[3]>=180){bbuf[3]=180;}send(sfd,bbuf,sizeof(bbuf),0);}break;case 'A':case 'a':{bbuf[3]-=2;if(bbuf[3]>180)//無符號整數小于零后會向255循環{bbuf[3]=0;}send(sfd,bbuf,sizeof(bbuf),0);}break;}}close(sfd);return 0;
}
運行:
思維導圖
二、 基于UDP的TFTP文件傳輸
1.tftp協議概述
簡單文件傳輸協議,適用于在網絡上進行文件傳輸的一套標準協議,使用UDP傳輸
特點:
是應用層協議
基于UDP協議實現
數據傳輸模式
octet:二進制模式(常用)
mail:已經不再支持
2.tftp下載模型
TFTP通信過程總結
- 服務器在69號端口等待客戶端的請求
- 服務器若批準此請求,則使用 臨時端口 與客戶端進行通信。
- 每個數據包的編號都有變化(從1開始)
- 每個數據包都要得到ACK的確認,如果出現超時,則需要重新發送最后的數據包或ACK包
- 數據長度以512Byte傳輸的,小于512Byte的數據意味著數據傳輸結束。
差錯碼:
0 未定義,差錯錯誤信息
1 File not found.
2 Access violation.
3 Disk full or allocation exceeded.
4 illegal TFTP operation.
5 Unknown transfer ID.
6 File already exists.
7 No such user.
8 Unsupported option(s) requested.
代碼:
#include<myhead.h>
#define SER_IP "192.168.125.57"
#define SER_POTR 69
int my_download(int cfd,struct sockaddr_in sin);
int my_upload(int cfd,struct sockaddr_in sin);
int my_save_data(char *p,int len);
int recv_ack(int cfd,struct sockaddr_in sin);int main(int argc, const char *argv[])
{//創建通信套接字int cfd=socket(AF_INET,SOCK_DGRAM,0);if(cfd==-1){perror("socket error");return -1;}//定義地址結構體 保存客戶端地址struct sockaddr_in sin;sin.sin_family=AF_INET;sin.sin_port=htons(SER_POTR);sin.sin_addr.s_addr=inet_addr(SER_IP);int menu=0;while(1){system("clear");puts("\t\t------請選擇選項------");printf("\t\t-------1.上傳-------\n");printf("\t\t-------2.下載-------\n");printf("\t\t-------0.退出-------\n");scanf("%d",&menu);getchar();switch(menu){case 0:goto END;case 1:{int res=my_upload(cfd,sin);if(res==-1){puts("my_upload error");return -1;}}break;case 2:{int res=my_download(cfd,sin);if(res==-1){puts("my_download error");return -1;}}break;default:{puts("輸入有誤,請重新輸入");}break;}puts("請輸入任意鍵按回車結束!");while(getchar()!='\n');}END:close(cfd);return 0;
}int my_download(int cfd,struct sockaddr_in sin)
{//定義收發數據容器char pack[516]="";//組建請求協議包//1.請求下載printf("請輸入要下載的文件名:");char txt[32]="";fgets(txt,sizeof(txt),stdin);txt[strlen(txt)-1]=0;short *p1=(short*)pack;*p1=htons(1);//存入前兩字節的操作碼1代表讀(下載)char *p2=pack+2;strcpy(p2,txt);//存入文件名及結尾的0char *p3=p2+strlen(p2)+1;strcpy(p3,"octet");//存入模式位及結尾的0int packlen=4+strlen(p2)+strlen(p3);//發送下載請求if(sendto(cfd,pack,packlen,0,(struct sockaddr*)&sin,sizeof(sin))==-1){perror("download request sendto error");return -1;}
/* //讀取服務器的回復消息int ack=recv_ack(cfd,sin);if(ack==-1){puts("recv_ack error");return -1;}
*/while(1){bzero(pack,sizeof(pack));int ack=recv_ack(cfd,sin);if(ack==-1){puts("my_download recv_ack error");return -1;}else if(ack==0)//下載完成{break;}*p1=htons(4);//設置ACK包*(p1+1)=htons(ack);//返回ACK包if(sendto(cfd,pack,sizeof(pack),0,(struct sockaddr*)&sin,sizeof(sin))==-1){perror("upload request sendto error");return -1;} }return 0;
}
int my_upload(int cfd,struct sockaddr_in sin)
{//定義收發數據容器char pack[516]="";//組建請求協議包//1.請求寫入printf("請輸入要上傳的文件名:");char txt[32]="";fgets(txt,sizeof(txt),stdin);txt[strlen(txt)-1]=0;short *p1=(short*)pack;*p1=htons(2);//存入前兩字節的操作碼2代表寫(上傳)char *p2=pack+2;strcpy(p2,txt);//存入文件名及結尾的0char *p3=p2+strlen(p2)+1;strcpy(p3,"octet");//存入模式位及結尾的0int packlen=4+strlen(p2)+strlen(p3);if(sendto(cfd,pack,packlen,0,(struct sockaddr*)&sin,sizeof(sin))==-1){perror("upload request sendto error");return -1;}//讀取服務器的回復消息int ack=recv_ack(cfd,sin);if(ack==-1){puts("recv_ack error");return -1;}//2.開始上傳數據//2.1只讀形式打開要上傳的文件int rfd=open(txt,O_RDONLY);if(rfd==-1){perror("upload open error");return -1;}int i=1;while(1){bzero(pack,sizeof(pack));//2.2設置發送數據的協議包*p1=htons(3);//前兩字節操作碼為3時代表此為數據包*(p1+1)=htons(i);//設置塊編碼從1開始int res=read(rfd,pack+4,512);//從文件中讀取512數據存入數據域packlen=4+res;//本次的數據包大小//發送數據if(sendto(cfd,pack,sizeof(pack),0,(struct sockaddr*)&sin,sizeof(sin))==-1){perror("upload data sandto error");return -1;}ack=recv_ack(cfd,sin);//讀取服務器的回復消息if(ack==-1){puts("recv_ack error");return -1;}else if(ack<i)//服務器返回的塊編碼小于當前發送的{//光標返回發送之前的位置重新發送lseek(rfd,-res,SEEK_CUR);}else if(ack==i)//服務器已接收該數據包可以發送下一個{i++;//塊編碼+1}if(res<512)//發送完成{break;}}close(rfd);return 0;
}
int my_save_data(char *p,int len)
{//追加寫的形式創建一個文件存儲下載的數據int wfd=open("./downtxt",O_WRONLY|O_APPEND|O_CREAT,0664);if(wfd==-1){perror("my_download open error");return -1;}write(wfd,p,len);close(wfd);return 0;
}
int recv_ack(int cfd,struct sockaddr_in sin)
{char pack[516]="";//接收服務器發來的消息 存入packint sinlen=sizeof(sin);int res=-1;if((res=(recvfrom(cfd,pack,sizeof(pack),0,(struct sockaddr*)&sin,&sinlen)))==-1){perror("recv_ack recvfrom error");return -1;}short *p=(short*)pack;short num=ntohs(*p);//獲取發來消息的操作碼switch(num){case 3:{printf("收到數據包,塊編碼:%d\n",ntohs(*(p+1)));//保存數據int seve=my_save_data(pack+4,res-4);if(seve==-1){puts("my_save_data error");return -1;}//如果讀取的長度小于516說明已經下載完成if(res<sizeof(pack)){//最后一次回復ACK*p=htons(4);sendto(cfd,pack,4,0,(struct sockaddr*)&sin,sizeof(sin));return 0;//正常下載完成返回0}}break;case 4:{printf("收到ACK,快編碼:%d\n",ntohs(*(p+1)));}break;case 5:{//收到錯誤碼 輸出差錯碼和差錯信息printf("ERROR:%d:%s\n",ntohs(*(p+1)),pack+4);return -1;}break;}return ntohs(*(p+1));//返回塊編碼
}
?