应用: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个字节 来表示这个数据包的功能(称为操作码),并且在序号的前面
操作码 | 功能 |
---|---|
1 | 读请求,即下载 |
2 | 写请求,即上传 |
3 | 表示数据包,即DATA |
4 | 确认码,即ACK |
5 | 错误 |
因为udp的数据包不安全,即发送方发送是否成功不能确定,所以TFTP协议中规定,为了让服务器知道客户端已经接收到了刚刚发送的那个数据包,所以当客户端接收到一个数据包的时候需要向服务器进行发送确认信息,即发送收到了,这样的包成为ACK(应答包)
为了标记数据已经发送完毕,所以规定,当客户端接收到的数据小于516(2字节操作码+2个字节的序号+512字节数据)时,就意味着服务器发送完毕了
TFTP数据包的格式如下:
2. 参考代码如下:
#coding=utf-8 from socket import * import struct import sys if len(sys.argv) != 2: print('-'*30) print("tips:") print("python xxxx.py 192.168.1.1") print('-'*30) exit() else: ip = sys.argv[1] # 创建udp套接字 udpSocket = socket(AF_INET, SOCK_DGRAM) #构造下载请求数据 cmd_buf = struct.pack("!H8sb5sb",1,"test.jpg",0,"octet",0) #发送下载文件请求数据到指定服务器 sendAddr = (ip, 69) udpSocket.sendto(cmd_buf, sendAddr) p_num = 0 recvFile = '' while True: recvData,recvAddr = udpSocket.recvfrom(1024) recvDataLen = len(recvData) # print recvAddr # for test # print len(recvData) # for test cmdTuple = struct.unpack("!HH", recvData[:4]) # print cmdTuple # for test cmd = cmdTuple[0] currentPackNum = cmdTuple[1] if cmd == 3: #是否为数据包 # 如果是第一次接收到数据,那么就创建文件 if currentPackNum == 1: recvFile = open("test.jpg", "a") # 包编号是否和上次相等 if p_num+1 == currentPackNum: recvFile.write(recvData[4:]); p_num +=1 print '(%d)次接收到的数据'%(p_num) ackBuf = struct.pack("!HH",4,p_num) udpSocket.sendto(ackBuf, recvAddr) # 如果收到的数据小于516则认为出错 if recvDataLen<516: recvFile.close() print '已经成功下载!!!' break elif cmd == 5: #是否为错误应答 print "error num:%d"%currentPackNum break udpSocket.close()
运行现象:
udp广播
网络编程中的广播
#coding=utf-8 import socket, sys dest = ('<broadcast>', 7788) # 创建udp套接字 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 对这个需要发送广播数据的套接字进行修改设置,否则不能发送广播数据 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1) # 以广播的形式发送数据到本网络的所有电脑中 s.sendto("Hi", dest) print "等待对方回复(按ctrl+c退出)" while True: (buf, address) = s.recvfrom(2048) print "Received from %s: %s" % (address, buf)
tcp相关介绍
udp通信模型
udp通信模型中,在通信开始之前,不需要建立相关的链接,只需要发送数据即可,类似于生活中,"写信"
tcp通信模型
udp通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,"打电话"
tcp服务器
生活中的电话机
如果想让别人能更够打通咱们的电话获取相应服务的话,需要做一下几件事情:
- 买个手机
- 插上手机卡
- 设计手机为正常接听状态(即能够响铃)
- 静静的等着别人拨打
tcp服务器
如同上面的电话机过程一样,在程序中,如果想要完成一个tcp服务器的功能,需要的流程如下:
- socket创建一个套接字
- bind绑定ip和port
- listen使套接字变为可以被动链接
- accept等待客户端的链接
- recv/send接收发送数据
一个很简单的tcp服务器如下:
#coding=utf-8 from socket import * # 创建socket tcpSerSocket = socket(AF_INET, SOCK_STREAM) # 绑定本地信息 address = ('', 7788) tcpSerSocket.bind(address) # 使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动的,这样就可以接收别人的链接了 tcpSerSocket.listen(5) # 如果有新的客户端来链接服务器,那么就产生一个新的套接字专门为这个客户端服务器 # newSocket用来为这个客户端服务 # tcpSerSocket就可以省下来专门等待其他新客户端的链接 newSocket, clientAddr = tcpSerSocket.accept() # 接收对方发送过来的数据,最大接收1024个字节 recvData = newSocket.recv(1024) print '接收到的数据为:',recvData # 发送一些数据到客户端 newSocket.send("thank you !") # 关闭为这个客户端服务的套接字,只要关闭了,就意味着为不能再为这个客户端服务了,如果还需要服务,只能再次重新连接 newSocket.close() # 关闭监听套接字,只要这个套接字关闭了,就意味着整个程序不能再接收任何新的客户端的连接 tcpSerSocket.close()
运行流程:
<1>tcp服务器
<2>网络调试助手:
tcp客户端
所谓的服务器端:就是提供服务的一方,而客户端,就是需要被服务的一方
tcp客户端构建流程
tcp的客户端要比服务器端简单很多,如果说服务器端是需要自己买手机、查手机卡、设置铃声、等待别人打电话流程的话,那么客户端就只需要找一个电话亭,拿起电话拨打即可,流程要少很多
示例代码:
#coding=utf-8 from socket import * # 创建socket tcpClientSocket = socket(AF_INET, SOCK_STREAM) # 链接服务器 serAddr = ('192.168.1.102', 7788) tcpClientSocket.connect(serAddr) # 提示用户输入数据 sendData = raw_input("请输入要发送的数据:") tcpClientSocket.send(sendData) # 接收对方发送过来的数据,最大接收1024个字节 recvData = tcpClientSocket.recv(1024) print '接收到的数据为:',recvData # 关闭套接字 tcpClientSocket.close()
运行流程:
<1>tcp客户端
<2>网络调试助手:
客户端参考代码
#coding=utf-8 from socket import * # 创建socket tcpClientSocket = socket(AF_INET, SOCK_STREAM) # 链接服务器 serAddr = ('192.168.1.102', 7788) tcpClientSocket.connect(serAddr) while True: # 提示用户输入数据 sendData = raw_input("send:") if len(sendData)>0: tcpClientSocket.send(sendData) else: break # 接收对方发送过来的数据,最大接收1024个字节 recvData = tcpClientSocket.recv(1024) print 'recv:',recvData # 关闭套接字 tcpClientSocket.close()
服务器端参考代码
#coding=utf-8 from socket import * # 创建socket tcpSerSocket = socket(AF_INET, SOCK_STREAM) # 绑定本地信息 address = ('', 7788) tcpSerSocket.bind(address) # 使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动的,这样就可以接收别人的链接了 tcpSerSocket.listen(5) while True: # 如果有新的客户端来链接服务器,那么就产生一个信心的套接字专门为这个客户端服务器 # newSocket用来为这个客户端服务 # tcpSerSocket就可以省下来专门等待其他新客户端的链接 newSocket, clientAddr = tcpSerSocket.accept() while True: # 接收对方发送过来的数据,最大接收1024个字节 recvData = newSocket.recv(1024) # 如果接收的数据的长度为0,则意味着客户端关闭了链接 if len(recvData)>0: print 'recv:',recvData else: break # 发送一些数据到客户端 sendData = raw_input("send:") newSocket.send(sendData) # 关闭为这个客户端服务的套接字,只要关闭了,就意味着为不能再为这个客户端服务了,如果还需要服务,只能再次重新连接 newSocket.close() # 关闭监听套接字,只要这个套接字关闭了,就意味着整个程序不能再接收任何新的客户端的连接 tcpSerSocket.close()