Part 9.网络编程--TFTP项目,Tcp编程
(一)wireshark抓包工具的使用
首先,我们要安装一个wireshark,他是一个抓包工具,方便我们后面学习下载传送的验证,网上可以很方便下载到,这里不再赘述。一下是其使用方法:
以上就是wireshark的基本使用方法。
(二)应用:TFTP客户端
1. TFTP协议介绍
TFTP(Trivial File Transfer Protocol,简单文件传输协议)
是TCP/IP协议族中的一个用来在客户端与服务器之间进行简单文件传输的协议
特点:
- 简单
- 占用资源小
- 适合传递小文件
- 适合在局域网进行传递
- 端口号为69
- 基于UDP实现
2. TFTP下载过程
TFTP服务器默认监听69号端口
当客户端发送“下载”请求(即读请求)时,需要向服务器的69端口发送
服务器若批准此请求,则使用一个新的、临时的端口进行数据传输
当服务器找到需要发送的文件后,会立刻打开文件,把文件中的数据通过TFTP协议发送给客户端
如果文件的总大小较大(比如3M),那么服务器分多次发送,每次会从文件中读取512个字节的数据发送过来
因为发送的次数有可能会很多,所以为了让客户端对接收到的数据进行排序,所以在服务器发送那512个字节数据的时候,会多发2个字节的数据,用来存放序号,并且放在512个字节数据的前面,序号是从1开始的
因为需要从服务器上下载文件时,文件可能不存在,那么此时服务器就会发送一个错误的信息过来,为了区分服务发送的是文件内容还是错误的提示信息,所以又用了2个字节 来表示这个数据包的功能(称为操作码),并且在序号的前面。
以下为其操作码:
因为udp的数据包不安全,即发送方发送是否成功不能确定,所以TFTP协议中规定,为了让服务器知道客户端已经接收到了刚刚发送的那个数据包,所以当客户端接收到一个数据包的时候需要向服务器进行发送确认信息,即发送收到了,这样的包成为ACK(应答包)
为了标记数据已经发送完毕,所以规定,当客户端接收到的数据小于516(2字节操作码+2个字节的序号+512字节数据)时,就意味着服务器发送完毕了
所以,TFTP数据包的格式如下:
3.参考代码
1 # -*- coding:utf-8 -*- 2 3 import struct 4 from socket import * 5 import time 6 import os 7 8 def main(): 9 10 11 #0. 获取要下载的文件名字: 12 downloadFileName = raw_input("请输入要下载的文件名:") 13 14 #1.创建socket 15 udpSocket = socket(AF_INET, SOCK_DGRAM) 16 17 requestFileData = struct.pack("!H%dsb5sb"%len(downloadFileName), 1, downloadFileName, 0, "octet", 0) 18 19 #2. 发送下载文件的请求 20 udpSocket.sendto(requestFileData, ("192.168.119.215", 69)) 21 22 flag = True #表示能够下载数据,即不擅长,如果是false那么就删除 23 num = 0 24 f = open(downloadFileName, "w") 25 26 while True: 27 #3. 接收服务发送回来的应答数据 28 responseData = udpSocket.recvfrom(1024) 29 30 # print(responseData) 31 recvData, serverInfo = responseData 32 33 opNum = struct.unpack("!H", recvData[:2]) 34 35 packetNum = struct.unpack("!H", recvData[2:4]) 36 37 print(packetNum[0]) 38 39 # print("opNum=%d"%opNum) 40 # print(opNum) 41 42 # if 如果服务器发送过来的是文件的内容的话: 43 if opNum[0] == 3: #因为opNum此时是一个元组(3,),所以需要使用下标来提取某个数据 44 45 46 #计算出这次应该接收到的文件的序号值,应该是上一次接收到的值的基础上+1 47 num = num + 1 48 49 # 如果一个下载的文件特别大,即接收到的数据包编号超过了2个字节的大小 50 # 那么会从0继续开始,所以这里需要判断,如果超过了65535 那么就改为0 51 if num==65536: 52 num = 0 53 54 # 判断这次接收到的数据的包编号是否是 上一次的包编号的下一个 55 # 如果是才会写入到文件中,否则不能写入(因为会重复) 56 if num == packetNum[0]: 57 # 把收到的数据写入到文件中 58 f.write(recvData[4:]) 59 num = packetNum[0] 60 61 #整理ACK的数据包 62 ackData = struct.pack("!HH", 4, packetNum[0]) 63 udpSocket.sendto(ackData, serverInfo) 64 65 elif opNum[0] == 5: 66 print("sorry,没有这个文件....") 67 flag = False 68 69 # time.sleep(0.1) 70 71 if len(recvData)<516: 72 break 73 74 if flag == True: 75 f.close() 76 else: 77 os.unlink(downloadFileName)#如果没有要下载的文件,那么就需要把刚刚创建的文件进行删除 78 79 if __name__ == '__main__': 80 main()
(三)udp广播
网络编程中的广播即是发送方只需发送一次数据到中间层然后中间层把它给所有该广播地址下的接收方。
我们前面说的对于一段IP地址:192.168.1.X 其中192.168.1.0表示网络号;192.168.1.255表示广播号。
1 #coding=utf-8 2 3 import socket, sys 4 5 dest = ('<broadcast>', 7788) #采用<broadcast>这个关键字实现通用的广播地址,就不用特定指定了。 6 7 # 创建udp套接字 8 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 9 10 # 对这个需要发送广播数据的套接字进行修改设置,否则不能发送广播数据 11 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1) 12 13 # 以广播的形式发送数据到本网络的所有电脑中 14 s.sendto("Hi", dest) 15 16 print "等待对方回复(按ctrl+c退出)" 17 18 while True: 19 (buf, address) = s.recvfrom(2048) 20 print "Received from %s: %s" % (address, buf)
(四)tcp介绍
tcp:传输控制协议
相对udp传送稳定但速度更慢,目前web服务器普遍使用tcp。
前面我们学习了udp协议,在udp通信模型中,在通信开始之前,不需要建立相关的链接,只需要发送数据即可,类似于生活中,"写信"":其过程如下
而在tcp通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,"打电话""。其流程如下:
(五)tcp服务器
生活中的电话机:
如果想让别人能更够打通咱们的电话获取相应服务的话,需要做一下几件事情:
- 买个手机
- 插上手机卡
- 设计手机为正常接听状态(即能够响铃)
- 静静的等着别人拨打
tcp服务器:
如同上面的电话机过程一样,在程序中,如果想要完成一个tcp服务器的功能,需要的流程如下:
- socket创建一个套接字——>买个手机
- bind绑定ip和port——>插上手机卡
- listen使套接字变为可以被动链接——>响铃模式
- accept等待客户端的链接——>等待电话
- recv/send接收发送数据
1 #coding=utf-8 2 from socket import * 3 4 # 创建socket 5 tcpSerSocket = socket(AF_INET, SOCK_STREAM) 6 7 # 绑定本地信息 8 address = ('', 7788) 9 tcpSerSocket.bind(address) 10 11 # 使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动的,这样就可以接收别人的链接了 12 tcpSerSocket.listen(5) 13 14 # 如果有新的客户端来链接服务器,那么就产生一个新的套接字专门为这个客户端服务 15 # newSocket用来为这个客户端服务 16 # tcpSerSocket就可以省下来专门等待其他新客户端的链接 17 newSocket, clientAddr = tcpSerSocket.accept() 18 19 # 接收对方发送过来的数据,最大接收1024个字节 20 recvData = newSocket.recv(1024) 21 print ('接收到的数据为:%s'%recvData) 22 23 # 发送一些数据到客户端 24 newSocket.send("thank you !") 25 26 # 关闭为这个客户端服务的套接字,只要关闭了,就意味着为不能再为这个客户端服务了,如果还需要服务,只能再次重新连接 27 newSocket.close() 28 29 # 关闭监听套接字,只要这个套接字关闭了,就意味着整个程序不能再接收任何新的客户端的连接 30 tcpSerSocket.close()
(六)tcp客户端
所谓的服务器端:就是提供服务的一方,而客户端,就是需要被服务的一方
tcp客户端构建流程
tcp的客户端要比服务器端简单很多,如果说服务器端是需要自己买手机、查手机卡、设置铃声、等待别人打电话流程的话,那么客户端就只需要找一个电话亭,拿起电话拨打即可,流程要少很多。
1 from socket import * 2 3 #创建套接字 4 clientSocket = socket(AF_INET, SOCK_STREAM) 5 #进行链接,注意connect参数为一个元组 6 clientSocket.connect(("192.168.119.153", 8989)) 7 8 #注意: 9 # 1. tcp客户端已经链接好了服务器,所以在以后的数据发送中,不需要填写对方的iph和port----->打电话 10 # 2. udp在发送数据的时候,因为没有之前的链接,所依需要 在每次的发送中 都要填写接收方的ip和port----->写信 11 clientSocket.send("haha".encode("gb2312")) 12 13 recvData = clientSocket.recv(1024) 14 15 print("recvData:%s"%recvData) 16 17 clientSocket.close()