当前位置:   article > 正文

作业:基于udp的tftp文件传输实例

作业:基于udp的tftp文件传输实例
  1. #include <head.h>
  2. #include <sys/types.h>
  3. #include <sys/socket.h>
  4. #include <arpa/inet.h>
  5. #include <errno.h>
  6. #define PORT 69 //服务器绑定的端口号
  7. #define IP "192.168.1.107" //服务器的IP地址
  8. int do_download(int cfd,struct sockaddr_in sin);
  9. int do_upload(int cfd, struct sockaddr_in sin);
  10. int main(int argc, const char *argv[])
  11. {
  12. //创建报式套接字
  13. int cfd = socket(AF_INET, SOCK_DGRAM, 0);
  14. if(cfd < 0)
  15. {
  16. ERR_MSG("socket");
  17. return -1;
  18. }
  19. printf("cfd = %d\n",cfd);
  20. //绑定客户端的地址信息结构体到套接字上--->非必须绑定
  21. //若不绑定,则操作系统会给客户端绑定运行主机的IP和随机的端口号
  22. //填充要连接的服务器地址信息结构体,真实的地址信息结构体根据地址族制定
  23. //要发给谁,就填谁的地址信息
  24. //AF_INET : man 7 ip
  25. struct sockaddr_in sin;
  26. socklen_t addrlen=sizeof(sin);
  27. sin.sin_family = AF_INET; //必须填AF_INET
  28. sin.sin_port = htons(PORT); //端口号:服务器绑定的端口号
  29. sin.sin_addr.s_addr = inet_addr(IP); //服务器绑定的IP
  30. char choose =0;
  31. while(1)
  32. {
  33. printf("*******************\n");
  34. printf("******1. 下载******\n");
  35. printf("******2. 上传******\n");
  36. printf("******3. 退出******\n");
  37. printf("*******************\n");
  38. printf("*******************\n");
  39. printf("请输入>>> ");
  40. choose = getchar();
  41. while(getchar() != 10); //循环获取字符,直到遇到\n结束循环
  42. switch(choose)
  43. {
  44. case '1':
  45. do_download(cfd,sin);
  46. break;
  47. case '2':
  48. do_upload(cfd,sin);
  49. break;
  50. case '3':
  51. goto END;
  52. break;
  53. default:
  54. printf("输入错误!请重新输入\n");
  55. }
  56. }
  57. END:
  58. //关闭文件描述符
  59. close(cfd);
  60. return 0;
  61. }
  62. int do_download(int cfd, struct sockaddr_in sin)
  63. {
  64. //组包准备发送下载请求
  65. char buf[516]="";
  66. char name[20]="";
  67. printf("请输入要下载的文件名>>> ");
  68. scanf("%s",name);
  69. while(getchar()!=10);
  70. unsigned short *p1=(short*)buf; //操作码
  71. *p1=htons(1);
  72. char *p2=buf+2; //文件名
  73. strcpy(p2,name);
  74. char *p3=p2+strlen(p2); //第一个0
  75. *p3=0;
  76. char *p4=p3+1; //模式
  77. strcpy(p4,"octet");
  78. size_t size=2+strlen(p2)+1+strlen(p4)+1; //操作码+文件名+0+模式+0
  79. //发送下载请求
  80. if(sendto(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,sizeof(sin)) < 0)
  81. {
  82. ERR_MSG("sendto");
  83. return -1;
  84. }
  85. //创建下载文件并清空
  86. int fd = -1; //必须初始化成一个无效的文件描述符
  87. socklen_t addrlen = sizeof(sin);
  88. ssize_t res = 0;
  89. unsigned short num = 0; //记录本地的块编号
  90. //发送下载请求
  91. while(1)
  92. {
  93. //接收数据
  94. bzero(buf,sizeof(buf));
  95. res = recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,&addrlen);
  96. if(res < 0)
  97. {
  98. ERR_MSG("recvfrom");
  99. return -1;
  100. }
  101. if(3 == buf[1]) //数据包
  102. {
  103. //判断服务器返回的数据包的块编号与本地记录的块编号是否一致
  104. if(*(unsigned short*)(buf+2) == htons((num+1)))
  105. {
  106. num++; //更新本地记录的块编号
  107. if(-1 == fd)
  108. {
  109. fd=open(name,O_WRONLY|O_CREAT|O_TRUNC,0664);
  110. if(fd < 0)
  111. {
  112. perror("open");
  113. return -1;
  114. }
  115. }
  116. //将数据写到文件中
  117. if(write(fd,buf+4,res-4) < 0 )
  118. {
  119. perror("write");
  120. return -1;
  121. }
  122. //发送ACK
  123. buf[1] = 4;
  124. //*p1=htons(4);
  125. if(sendto(cfd,buf,4,0,(struct sockaddr*)&sin,sizeof(sin)) < 0)
  126. {
  127. ERR_MSG("sendto");
  128. return -1;
  129. }
  130. //若接收到的数据小于512跳出循环,结束下载
  131. if(res-4 < 512)
  132. {
  133. printf("%s 文件下载完毕\n",name);
  134. break;
  135. }
  136. }
  137. }
  138. else if(5 == buf[1]) //错误包
  139. {
  140. printf("错误: %d %s\n",ntohs(*(short*)(buf+2)),buf+4);
  141. close(fd);
  142. return -1;
  143. }
  144. }
  145. close(fd);
  146. return 0;
  147. }
  148. int do_upload(int cfd, struct sockaddr_in sin)
  149. {
  150. //组包准备发送上传请求
  151. char buf[516]="";
  152. char name[20]="";
  153. printf("请输入要上传的文件名>>> ");
  154. scanf("%s",name);
  155. while(getchar()!=10);
  156. int fd = open(name,O_RDONLY);
  157. if(fd < 0)
  158. {
  159. if( errno == ENOENT)
  160. {
  161. printf(">>>文件不存在,请重新输入<<<\n");
  162. return -2;
  163. }
  164. else
  165. {
  166. ERR_MSG("open");
  167. return -1;
  168. }
  169. }
  170. //组包准备发送上传请求
  171. unsigned short *p1=(short*)buf; //操作码
  172. *p1=htons(2);
  173. char *p2=buf+2; //文件名
  174. strcpy(p2,name);
  175. char *p3=p2+strlen(p2); //第一个0
  176. *p3=0;
  177. char *p4=p3+1; //模式
  178. strcpy(p4,"octet");
  179. size_t size=2+strlen(p2)+1+strlen(p4)+1; //操作码+文件名+0+模式+0
  180. //发送上传请求
  181. if(sendto(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,sizeof(sin)) < 0)
  182. {
  183. ERR_MSG("sendto");
  184. return -1;
  185. }
  186. //循环接收发送数据包
  187. ssize_t res;
  188. unsigned short num = 0;
  189. socklen_t addrlen = sizeof(sin);
  190. while(1)
  191. {
  192. //将数据从文件中读取到buf中
  193. bzero(buf,sizeof(buf));
  194. res = recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,&addrlen);
  195. if(res < 0)
  196. {
  197. ERR_MSG("recvfrom");
  198. return -1;
  199. }
  200. //操作码的范围是1-5,因为是网络字节序
  201. //所以有效操作码存储在高位,即buf[1]的位置
  202. //printf("buf[1] = %d\n",buf[1]); //4 服务器返回应答包
  203. if(4 == buf[1]) //数据包
  204. {
  205. //判断当前数据包的编号是否等于应答包的编号
  206. //防止数据包在传送过程丢包或重复收包
  207. if(num == ntohs(*(unsigned short*)(buf+2)))
  208. {
  209. //修改操作码为数据包
  210. buf[1] = 3;
  211. //填充块编号
  212. num++;
  213. *(unsigned short*)(buf+2) = htons(num);
  214. //读取数据
  215. res = read(fd,buf+4,sizeof(buf)-4);
  216. if(res < 0)
  217. {
  218. ERR_MSG("read");
  219. return -1;
  220. }
  221. else if(0 == res)
  222. {
  223. printf("%s 文件上传完毕!\n",name);
  224. break;
  225. }
  226. //发送数据包
  227. //发送的数据包大小为,读取到的字节数res+操作码2byte+块编号2bytes
  228. if(sendto(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,sizeof(sin)) < 0)
  229. {
  230. ERR_MSG("sendto");
  231. return -1;
  232. }
  233. }
  234. else
  235. {
  236. printf("文件上传失败,请检查网络环境\n");
  237. break;
  238. }
  239. }
  240. else if(5 == buf[1]) //错误包
  241. {
  242. printf("错误: %d %s\n",ntohs(*(short*)(buf+2)),buf+4);
  243. close(fd);
  244. return -1;
  245. }
  246. }
  247. close(fd);
  248. return 0;
  249. }

导图:

面试题:

IP地址:网络中主机的标识符

TCP服务端通信流程:创建套接字,然后绑定服务器地址,然后开启被动监听,然后就是与客户端的数据收发,最后关闭套接字。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/293930
推荐阅读
相关标签
  

闽ICP备14008679号