Python网络编程--UDP

来源:互联网 发布:c语言培训班多少钱 编辑:程序博客网 时间:2024/05/17 21:51

1. UDP

1.1 网络通信概述

1.1.1 网络

• 网络就是一种辅助双方或者多方能够连接在一起的工具
• 如果没有网络可想单机的世界是多么的孤单

1.1.2 使用网络的目的

就是为了联通多方然后进行通信用的,即把数据从一方传递给另外一方
前面的学习编写的程序都是单机的,即不能和其他电脑上的程序进行通信
为了让在不同的电脑上运行的软件,之间能够互相传递数据,就需要借助网络的功能

总结:
• 使用网络能够把多方链接在一起,然后可以进行数据传递
• 所谓的网络编程就是,让在不同的电脑上的软件能够进行数据传递,即进程之间的通信

2.TCP/IP协议

2.1 协议

有的说英语,有的说中文,有的说德语,说同一种语言的人可以交流,不同的语言之间就不行了
为了解决不同种族人之间的语言沟通障碍,现规定国际通用语言是英语,这就是一个规定,这就是协议

2.2 计算机网络沟通

那么不同种类之间的计算机到底是怎么进行数据传递的呢?
就像说不同语言的人沟通一样,只要有一种大家都认可都遵守的协议即可,那么这个计算机都遵守的网络通信协议叫做TCP/IP协议

2.3 TCP/IP协议(族)

早期的计算机网络,都是由各厂商自己规定一套协议,IBM、Apple和Microsoft都有各自的网络协议,互不兼容

为了把全世界的所有不同类型的计算机都连接起来,就必须规定一套全球通用的协议,为了实现互联网这个目标,互联网协议簇(Internet Protocol Suite)就是通用协议标准。

因为互联网协议包含了上百种协议标准,但是最重要的两个协议是TCP和IP协议,所以,大家把互联网的协议简称TCP/IP协议

常用的网络协议如下图所示:
这里写图片描述

说明:
网际层也称为:网络层
网络接口层也称为:链路层

3. 端口

3.1 定义

这里写图片描述
那么TCP/IP协议中的端口指的是什么呢?
端口就好一个房子的门,是出入这间房子的必经之路。
如果一个进程需要收发网络数据,那么就需要有这样的端口
在linux系统中,端口可以有65536(2的16次方)个之多!
既然有这么多,操作系统为了统一管理,所以进行了编号,这就是端口号

3.2 端口号

端口是通过端口号来标记的,端口号只有整数,范围是从0到65535

3.3 端口的分配

端口号不是随意使用的,而是按照一定的规定进行分配。

3.3.1 知名端口(Well Known Ports)

知名端口是众所周知的端口号,范围从0到1023。
如:
 •80端口分配给HTTP服务
 •21端口分配给FTP服务
一般情况下,如果一个程序需要使用知名端口的需要有root权限

3.3.2 动态端口(Dynamic Ports)

动态端口的范围是从1024到65535

之所以称为动态端口,是因为它一般不固定分配某种服务,而是动态分配。

动态分配是指当一个系统进程或应用程序进程需要网络通信时,它向主机申请一个端口,主机从可用的端口号中分配一个供它使用。

当这个进程关闭时,同时也就释放了所占用的端口号。

3.3.3 查看端口的方法

用“netstat -an”查看端口状态

3.4 总结

端口有什么用呢 ? 我们知道,一台拥有IP地址的主机可以提供许多服务,比如HTTP(万维网服务)、FTP(文件传输)、SMTP(电子邮件)等,这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区分不同的服务的。 需要注意的是,端口并不是一一对应的。比如你的电脑作为客户机访问一台WWW服务器时,WWW服务器使用“80”端口与你的电脑通信,但你的电脑则可能使用“3457”这样的端口。

4. IP地址

4.1 什么是地址

地址就是用来标记地点的

4.2 IP地址的作用

IP地址:用来在网络中标记一台电脑的一串数字,比如192.168.1.1;在本地局域网上是惟一的。

4.3 IP地址的分类

每一个IP地址包括两部分:网络地址和主机地址
这里写图片描述

4.3.1 A类IP地址

一个A类IP地址由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”,

地址范围1.0.0.1-126.255.255.254

二进制表示为:00000001.00000000.00000000.00000001 - 01111110.11111111.11111111.11111110

可用的A类网络有126个,每个网络能容纳1677214个主机

4.3.2 B类IP地址

一个B类IP地址由2个字节的网络地址和2个字节的主机地址组成,网络地址的最高位必须是“10”,

地址范围128.1.0.1-191.255.255.254

二进制表示为:10000000.00000001.00000000.00000001 - 10111111.11111111.11111111.11111110

可用的B类网络有16384个,每个网络能容纳65534主机

4.3.3 C类IP地址

一个C类IP地址由3字节的网络地址和1字节的主机地址组成,网络地址的最高位必须是“110”

范围: 192.0.1.1-223.255.255.254

二进制表示为: 11000000.00000000.00000001.00000001 - 11011111.11111111.11111110.11111110

C类网络可达2097152个,每个网络能容纳254个主机

4.3.4 D类IP地址用于多点广播

D类IP地址第一个字节以“1110”开始,它是一个专门保留的地址。
它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中
多点广播地址用来一次寻址一组计算机
地址范围224.0.0.1-239.255.255.254

4.3.5 E类IP地址

以“1111”开始,为将来使用保留
E类地址保留,仅作实验和开发用

4.3.6 私有IP

在这么多网络IP中,国际规定有一部分IP地址是用于我们的局域网使用,也就是属于私网IP,不在公网中使用的,它们的范围是:

10.0.0.0~10.255.255.255172.16.0.0~172.31.255.255192.168.0.0~192.168.255.255

4.3.7 注意

IP地址127.0.0.1~127.255.255.255用于回路测试,

如:127.0.0.1可以代表本机IP地址,用http://127.0.0.1就可以测试本机中配置的Web服务器。

5. 子网掩码

6. socket(套接字)

6.1 本地的进程间通信(IPC)的方式

• 队列
• 同步(互斥锁、条件变量等)
以上通信方式都是在一台机器上不同进程之间的通信方式,那么问题来了
网络中进程之间如何通信?

6.2 网络间的进程之间的通信

首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!

在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。

其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。

这样利用ip地址,协议,端口就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互

6.3 socket

socket,简称套接字,是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:
它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的
例如我们每天浏览网页、QQ 聊天、收发 email 等等

6.4 创建socket

在 Python 中 使用socket 模块的函数 socket 就可以完成:

socket.socket(AddressFamily, Type)

说明:
函数 socket.socket 创建一个 socket,返回该 socket 的描述符,该函数带有两个参数:

• Address Family:可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF_UNIX(用于同一台机器进程间通信),实际工作中常用AF_INET
• Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)

创建一个tcp socket(tcp套接字):

import sockets = socket.socket(socket.AF_INET, socket.SOCK_STREAM)print 'Socket Created'

创建一个udp socket(udp套接字):

import sockets = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)print 'Socket Created'

7. UDP介绍

UDP — 用户数据报协议,是一个无连接的简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
UDP是一种面向无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。

特点:
UDP是面向无连接的通讯协议,UDP数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送。 UDP传输数据时有大小限制,每个被传输的数据报必须限定在64KB之内。 UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方。

适用情况:
UDP是面向消息的协议,通信时不需要建立连接,数据的传输自然是不可靠的,UDP一般用于多点通信和实时的数据业务,比如:
 • 语音广播
 • 视频
 • QQ
 • TFTP(简单文件传送)
 • SNMP(简单网络管理协议)
 • RIP(路由信息协议,如报告股票市场,航空信息)
 • DNS(域名解释)

8. UDP网络程序–发送数据

socket 函数

mySocket = socket(family, type)

函数socket()的参数family用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族。通信协议族在文件sys/socket.h中定义。
这里写图片描述

函数socket()的参数type用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字)、SOCK——DGRAM(数据包套接字)等。
并不是所有的协议族都实现了这些协议类型,例如,AF_INET协议族就没有实现SOCK_SEQPACKET协议类型。
这里写图片描述

创建一个udp客户端程序的流程很简单,具体步骤如下:
 1. 创建客户端套接字
 2. 发送/接收数据
 3. 关闭套接字

示例:

from socket import *#1. 创建套接字udpSocket = socket(AF_INET, SOCK_DGRAM)#2. 准备接收方的地址sendAddr = ('192.168.11.74', 7788)#3. 从键盘获取数据sendData = input("请输入要发送的数据:")#4. 发送数据到指定的电脑上udpSocket.sendto(sendData.encode('gbk'), sendAddr)#5. 关闭套接字udpSocket.close()

在windows中运行“网络调试助手”:
这里写图片描述

9. UDP网络程序–发送、接收数据

示例:

from socket import *#1. 创建套接字udpSocket = socket(AF_INET, SOCK_DGRAM)#2. 准备接收方的地址sendAddr = ('192.168.1.103', 8080)#3. 从键盘获取数据sendData = input("请输入要发送的数据:")#4. 发送数据到指定的电脑上udpSocket.sendto(sendData, sendAddr)#5. 等待接收对方发送的数据recvData = udpSocket.recvfrom(1024) # 1024表示本次接收的最大字节数#6. 显示对方发送的数据print(recvData)#7. 关闭套接字udpSocket.close()

10. UDP网络程序–端口问题

重新运行多次脚本,然后在“网络调试助手”中,看到的现象如下:
这里写图片描述

说明:
• 每重新运行一次网络程序,上图中红圈中的数字,不一样的原因在于,这个数字标识这个网络程序,当重新运行时,如果没有确定到底用哪个,系统默认会随机分配
• 记住一点:这个网络程序在运行的过程中,这个就唯一标识这个程序,所以如果其他电脑上的网络程序如果想要向此程序发送数据,那么就需要向这个数字(即端口)标识的程序发送即可

11. UDP绑定信息

还记得在上一节课中,如果一个网络程序在每次运行的时候端口是随机变化的么?

一般情况下,在一天电脑上运行的网络程序有很多,而各自用的端口号很多情况下不知道,为了不与其他的网络程序占用同一个端口号,往往在编程中,udp的端口号一般不绑定

但是如果需要做成一个服务器端的程序的话,是需要绑定的,想想看这又是为什么呢?

如果报警电话每天都在变,想必世界就会乱了,所以一般服务性的程序,往往需要一个固定的端口号,这就是所谓的端口绑定

示例:

from socket import *#1. 创建套接字udpSocket = socket(AF_INET, SOCK_DGRAM)#2. 绑定本地的相关信息,如果一个网络程序不绑定,则系统会随机分配bindAddr = ('', 7788) # ip地址和端口号,ip一般不用写,表示本机的任何一个ipudpSocket.bind(bindAddr)#3. 等待接收对方发送的数据recvData = udpSocket.recvfrom(1024) # 1024表示本次接收的最大字节数#4. 显示接收到的数据print(recvData)#5. 关闭套接字udpSocket.close()

总结:
• 一个udp网络程序,可以不绑定,此时操作系统会随机进行分配一个端口,如果重新运行次程序端口可能会发生变化
• 一个udp网络程序,也可以绑定信息(ip地址,端口号),如果绑定成功,那么操作系统用这个端口号来进行区别收到的网络数据是否是此进程的

12. UDP网络通信过程

这里写图片描述

13. UDP应用

这里写图片描述
这里写图片描述

示例:

from socket import *#1. 创建套接字udpSocket = socket(AF_INET, SOCK_DGRAM)#2. 绑定本地的相关信息bindAddr = ('', 7788) # ip地址和端口号,ip一般不用写,表示本机的任何一个ipudpSocket.bind(bindAddr)num = 1while True:    #3. 等待接收对方发送的数据    recvData = udpSocket.recvfrom(1024) # 1024表示本次接收的最大字节数    #4. 将接收到的数据再发送给对方    udpSocket.sendto(recvData[0], recvData[1])    #5. 统计信息    print('已经将接收到的第%d个数据返回给对方,内容为:%s'%(num,recvData[0]))    num+=1#5. 关闭套接字udpSocket.close()

14. UDP应用:聊天室

示例:

from socket import *from time import ctime#1. 创建套接字udpSocket = socket(AF_INET, SOCK_DGRAM)#2. 绑定本地的相关信息bindAddr = ('', 7788) # ip地址和端口号,ip一般不用写,表示本机的任何一个ipudpSocket.bind(bindAddr)while True:    #3. 等待接收对方发送的数据    recvData = udpSocket.recvfrom(1024) # 1024表示本次接收的最大字节数    #4. 打印信息    print('【%s】%s:%s'%(ctime(),recvData[1][0],recvData[0]))#5. 关闭套接字udpSocket.close()

15. UDP广播

TCP/IP协议栈中, 传输层只有UDP可以广播

示例:

import socket, sysdest = ('<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))

16. UDP总结

 1. udp是TCP/IP协议族中的一种协议能够完成不同机器上的程序间的数据通信
 2. udp服务器、客户端
  • udp的服务器和客户端的区分:往往是通过请求服务和提供服务来进行区分
  • 请求服务的一方称为:客户端
  • 提供服务的一方称为:服务器
 3. udp绑定问题
  • 一般情况下,服务器端,需要绑定端口,目的是为了让其他的客户端能够正确发送到此进程
  • 客户端,一般不需要绑定,而是让操作系统随机分配,这样就不会因为需要绑定的端口被占用而导致程序无法运行的情况

17. UDP综合作业–模拟QQ

要求:
• 使用多线程完成一个全双工的QQ聊天程序

单工、半双工、全双工

单工数据传输只支持数据在一个方向上传输;

半双工数据传输允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;

全双工数据通信允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力.

代码:

from threading import Threadfrom socket import *# 1. 收数据,然后打印def recvData():    while True:        recvInfo = udpSocket.recvfrom(1024)        print(">>%s:%s" % (str(recvInfo[1]), recvInfo[0]))# 2. 检测键盘,发数据def sendData():    while True:        sendInfo = input("<<")        udpSocket.sendto(sendInfo.encode("gb2312"), (destIp, destPort))udpSocket = NonedestIp = ""destPort = 0def main():    global udpSocket    global destIp    global destPort    destIp = input("对方的ip:")    destPort = int(input("对方的ip:"))    udpSocket = socket(AF_INET, SOCK_DGRAM)    udpSocket.bind(("", 4567))    tr = Thread(target=recvData)    ts = Thread(target=sendData)    tr.start()    ts.start()    tr.join()    ts.join()if __name__ == "__main__":    main()

18. TFTP客户端

18.1 TFTP协议

TFTP(Trivial File Transfer Protocol,简单文件传输协议)
是TCP/IP协议族中的一个用来在客户端与服务器之间进行简单文件传输的协议
特点:
  • 简单
  • 占用资源小
  • 适合传递小文件
  • 适合在局域网进行传递
  • 端口号为69
  • 基于UDP实现

18.2 TFTP下载

TFTP服务器默认监听69号端口
当客户端发送“下载”请求(即读请求)时,需要向服务器的69端口发送
服务器若批准此请求,则使用一个新的、临时的 端口进行数据传输
这里写图片描述

当服务器找到需要现在的文件后,会立刻打开文件,把文件中的数据通过TFTP协议发送给客户端

如果文件的总大小较大(比如3M),那么服务器分多次发送,每次会从文件中读取512个字节的数据发送过来

因为发送的次数有可能会很多,所以为了让客户端对接收到的数据进行排序,所以在服务器发送那512个字节数据的时候,会多发2个字节的数据,用来存放序号,并且放在512个字节数据的前面,序号是从1开始的

因为需要从服务器上下载文件时,文件可能不存在,那么此时服务器就会发送一个错误的信息过来,为了区分服务发送的是文件内容还是错误的提示信息,所以又用了2个字节 来表示这个数据包的功能(称为操作码),并且在序号的前面

TFTP数据包的格式如下:
这里写图片描述
这里写图片描述

因为udp的数据包不安全,即发送方发送是否成功不能确定,所以TFTP协议中规定,为了让服务器知道客户端已经接收到了刚刚发送的那个数据包,所以当客户端接收到一个数据包的时候需要向服务器进行发送确认信息,即发送收到了,这样的包成为ACK(应答包)

为了标记数据已经发送完毕,所以规定,当客户端接收到的数据小于516(2字节操作码+2个字节的序号+512字节数据)时,就意味着服务器发送完毕了

参考代码:

import socketimport structimport osimport timedef main():    #创建文件    myFile = open('xx.avi','wb')    #socket对象,发送下载的请求信息,接收信息和发送确认信息    udpSocket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)    #服务器地址    destAddr = ('192.168.11.74',69)    #pack信息    data = struct.pack('!H7sb5sb', 1, b'110.avi', 0, b'octet', 0)    #发送    udpSocket.sendto(data,destAddr)    #定义一个变量,记录写的次数    num = 0    #循环    while True:        #接收        recvData,redvAddr = udpSocket.recvfrom(1024)        #unpack信息,获取操作码        operNum = struct.unpack('!H',recvData[:2])[0]        #判断        if operNum==3:                # unpack信息,获取块编号                blockNum = struct.unpack('!H', recvData[2:4])[0]                print(blockNum)                #判断                num = num+1                if num==65536:                    num = 0                if num == blockNum:                    num = blockNum                    #获取数据,写到文件中                    myFile.write(recvData[4:])                    #准备ack数据                    ackData = struct.pack('!HH', 4,blockNum)                    #发送ack确认到服务器                    udpSocket.sendto(ackData,redvAddr)            if len(recvData)<516:                break        if operNum==5:            print('发生异常啦......')            break    #关闭文件    myFile.close()if __name__ == '__main__':    main()
原创粉丝点击