1、Python网络编程
Python作为一种高级编程语言,已经成为了众多开发人员的首选。其中,Python的网络编程功能在互联网时代中变得越来越重要。Python的网络编程也是Python编程语言在互联网世界中所扮演的重要角色之一。本文将深入介绍Python中的网络编程是什么,以及如何使用Python进行网络编程。
1.网络开发两大架构
早期数据交互的格式是没有网络的
两个文件之间的数据交互需要通过第三个文件
a,b文件同时和c文件交互,
a文件把数据先存放c文件中
b文件从c文件取,反之亦然
比如磁盘,硬盘,U盘。都是用来做文件交互的第三个文件
socket (套接字) 是一个收发数据的工具
有了网络之后
a文件中的数据,通过网络协议,转化101010…二进制进行发送
a文件借助socket发送数据
b文件借助socket接受数据
二大架构
c/s 架构 :
c => client (客户端)
王者荣耀 微信 qq wow dnf … APP类,需要下载的软件一般都是CS架构
s => server (服务端)
天河三号 (百亿亿次)
B/S 架构 :
b => Brower (浏览器)
谷歌,火狐,360,ie
通过输入网址, 访问对方的服务器, 对方服务器响应之后, 把数据返回, 浏览器通过返回的数据, 渲染页面, 用户看到最后的结果
s => server (服务端)
一旦开启,永不关机(除非宕机)
在未来,更倾向于使用B/s架构,成为新的趋势
小程序: 微信小程序, 支付宝小程序
(1) 省去复杂下载安装环节,节省手机电脑的空间
(2) 因为手机带来的便捷性, 随时随地使用到想要的应用, 提升用户的满意度
2.网络的概念
网段的作用:
主要用来划分同一区域里的某些机器是否能够互相通信。
在一个网段里可以不通过因特网,直接对话
判别的依据:
如果IP地址和子网掩码相与得到的值相同就是同一网段
逻辑与
全真则真,一假则假
逻辑或
一真则真,全假则假
ip1: 192.168,11.251
子网掩码:255.255.255.0
11000000 10101000 00001011 11111011
11111111 11111111 11111111 00000000
11000000 10101000 00001011 00000000 => 192.168.11.0 (网段)
ip2: 192.168,12.35
子网掩码:255.255.255.0
11000000 10101000 00001100 00100011
11111111 11111111 11111111 00000000
11000000 10101000 00001100 00000000 => 192.168.12.0 (网段)
ip1: 192.168,11.251
子网掩码:255.255.0.0
11000000 10101000 00001011 11111011
11111111 11111111 00000000 00000000
11000000 10101000 00000000 00000000 => 192.168.0.0 (网段)
ip2: 192.168,12.35
子网掩码:255.255.0.0
11000000 10101000 00001011 11111011
11111111 11111111 00000000 00000000
11000000 10101000 00000000 00000000 => 192.168.0.0 (网段)
下面的网络相同,意味着可以互相通信;
3.端口
端口:具体某个程序与外界通讯的出口 取值范围:0~65535
192.168.2.1:8000 访问这个世界上任何一个电脑里的任何一个软件
自定义端口时,最好命名8000以上的端口号
常见的一些端口号:
20 : FTP文件传输协议(默认数据口)
21 : FTP文件传输协议(控制)
22 : SSH远程登录协议
25 : SMTP服务器所开放的端口,用于发送邮件
80 : http用于网页浏览,木马Executor开放此端口
443: 基于TLS/SSL的网页浏览端口,能提供加密和通过安全端口传输的另一种HTTP => HTTPS
3306:MySQL开放此端口
端口和端口号:
每个运行的程序都会有一个端口,想要给对应的程序发送数据,找到对应的端口即可
端口是传输层服务访问点TSAP,端口的作用是让应用层的各种应用进程都能将其数据通过端口向下交付给传输层,
以及让传输层知道应当将其报文段中的数据向上通过端口交付给应用层的进程。
端口号存在于UDP和TCP报文的首部,而IP数据报则是将UDP或者TCP报文做为其数据部分,
再加上IP数据报首部,封装成IP数据报。而协议号则是存在这个IP数据报的首部。
比如,客户端发送一个数据包给IP,然后IP将进来的数据发送给传输协议(tcp或者udp),
然后传输协议再根据数据包的第一个报头中的协议号和端口号来决定将此数据包给哪个应用程序(也叫网络服务)。
也就是说,协议号+端口号唯一地确定了接收数据包的网络进程。
由于标志数据发送进程的“源端口号”和标志数据接受进程的“目的端口号”都包含在每个TCP段和UDP段的第一个分组中,
系统可以知道到底是哪个客户应用程序同哪个服务器应用程序在通讯,而不会将数据发送到别的进程中。
如果说IP地址让网络上的两个节点之间可以建立点对点的连接,那么端口号则为端到端的连接提供了可能。
理解端口号的概念,对于理解TCP/IP协议的通信过程有着至关重要的作用。
端口号的范围是从1~65535。其中0~1023是被RFC 3232规定好了的,
被称作“众所周知的端口”(Well Known Ports);从1025~65535的端口被称为动态端口(Dynamic Ports),
可用来建立与其它主机的会话,也可由用户自定义用途。
端口是数据传输的通道,是数据传输必经之路。端口的作用就是给运行的应用程序提供数据传输的通道
操作系统为了统一管理这么多端口,就对应端口进行了编号,这就是端口号
端口号可以标识唯一的一个端口
通过IP地址找到对应设备,通过端口号找到对应端口,然后通过端口号把数据传输给应用程序
端口号可以分为知名端口号和动态端口号
知名端口号:就是众所周知的端口号,范围从0——1023
动态端口号:一般程序员开发自定义的端口号,范围从1024-65535
如果程序员没有设置端口号,操作系统会在动态端口号范围内随机生成一个给开发的应用程序使用
当程序运行时,会占用一个端口号,程序退出时,释放这个端口号
socket:
socket(套接字)是进程之间通信的一个工具。进程之间进行网络通信需要基于socket
socket的作用:
负责进程之间的数据传输。好比数据的搬运工
4.osi 网络七层模型
应用层 (应用层,表示层,会话层)
封装数据:
根据不同的协议,封装不同格式的数据
http (超文本传输协议)
HTTPS (加密传输的超文本传输协议)
FTP (文件传输协议)
SMTP (调子邮件传输协议)
传输层:
封装端口:
指定传输协议(TCP协议/UDP协议)
网络层:
封装ip:
ipv4版本 / ipv6
数据链路层:
封装mac地址:
指定mac地址(arp协议[ip->mac] / rarp协议[mac->ip])
物理层:
打成数据包,变成二进制的字节流,通过网络进行传输
数据封装过程:
应用层 上层数据
传输层 tcp头部 上层数据
网络层 源,目标ip头部 tcp头部 上层数据
数据链路层 源,目标mac头部 ip头部 tcp头部 上层数据 先将物理层传来的数据解封,查看要发给哪个mac,再封装发到物理层发送
物理层 二进制流传输
从上往下封装协议的头部
数据解封过程:
应用层 上层数据
传输层 tcp头部 上层数据 根据端口号来传输给相应程序
网络层 ip头部 tcp头部 上层数据
数据链路层 mac头部 ip头部 tcp头部 上层数据 检测mac是不是自己,是自己去掉mac,传到网络层
物理层 二进制流传输
从下往上解封协议的头部
5.交换机与路由器 , 发送数据包流程
交换机: 从下到上拆2层,拆到数据链路层
路由器: 从下到上拆3层,拆到网络层(得到对应的网段)
arp协议: 通过ip -> mac
rarp协议: 通过mac -> ip
“”“arp协议整体是通过: 一次广播 + 一次单播 实现”“”
arp协议的完整过程:
电脑a发现目标主机没有mac,先发送arp广播包,把mac标记成全F的广播地址
交换机接受到arp的广播包,进行从下到上拆包,拆2层,拆到数据链路层看到全F广播地址,开始广播
把这个广播包发送给每一台主机
每台主机得到广播包后,都开始拆包,如果该数据包找寻的主机不是自己,自动舍弃
路由器得到arp广播包后,从下到上拆包,拆3层,拆到网络层,得到网段信息
通过路由器的对照信息表,找到网段对应的网关(接口)
对应网关的这台交换机得到arp广播包后,从下到上拆包,拆2层,发现全F广播地址进行广播
数据库主机收到广播包后,依次从下到上拆包,发现自己是目标要找的那台主机,
把自己的ip->mac对照信息封装,变成arp响应包,发送给对应的交换机
交换机得到arp响应包之后,依次进行单播,返回给最终的原主机
在回来的过程中,所有得到过相应arp广播包的主机都会自动更新自己的arp解析表,方便下次使用
6.TCP/UDP协议
tcp
TCP(Transmission Control Protocol)一种面向连接的、可靠的、传输层通信协议(比如:打电话)
优点:可靠,稳定,传输完整稳定,不限制数据大小
缺点:慢,效率低,占用系统资源高,一发一收都需要对方确认
应用:Web浏览器,电子邮件,文件传输,大量数据传输的场景
udp
UDP(User Datagram Protocol)一种无连接的,不可靠的传输层通信协议(比如:发短信)
优点:速度快,可以多人同时聊天,耗费资源少,不需要建立连接
缺点:不稳定,不能保证每次数据都能接收到
应用:IP电话,实时视频会议,聊天软件,少量数据传输的场景
TCP 三次握手
客户端发送一个请求消息,与服务端建立连接
服务端接受这个请求,发出响应消息,回应客户端,也要与客户端a建立连接(看下a是否同意)
客户端接受服务端的响应消息之后,发送回复消息(表达同意,到此客户端与服务端建立连接成功)
TCP 发送数据
每次发送一次数据,都会对应一个回执消息,如果发送方没有接受到回执消息,那么该数据包在发送一次;
TCP 四次挥手
客户端向服务端发送一个断开连接请求(代表客户端没有数据给服务端)
服务端接受请求,发出响应
等到服务端所有数据发送完毕之后
服务端向客户端发送断开连接请求
客户端接受请求,发出响应
等到2msl,msl(最大报文段生存时间)这么长时间之后 客户端与服务端彻底断开连接
“MSL是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
2MSL的大小一般为多少, 可否配置?
cat /proc/sys/net/ipv4/tcp_fin_timeout
60
这里是2MSL的值,所以MSL值为30
修改
echo 30 >/proc/sys/net/ipv4/tcp_fin_timeout
端口释放的更快
MSL是最大报文生存时间,如果报文超过这个时间,就认为这个报文丢掉了
比如客户端A到B进行通讯,对于4次断开(挥手);
如果客户A在最后一次发送ack给B,如果中途丢失,客户B会重新向A发送FIN=1,ACK=1,即4次断开第3步骤,如何判断ack是否丢了呢?就是通过MSL(最大报文生存时间)来决定的;
如果B发送FIN=1,ACK=1,B会等待2MSL时间,因为B向A发送需要1MSL时间,A再向B发送ACK需要1MSL时间;如果B在2MSL时间内还没有收到A返回的ACK,那么B会重传FIN=1,ACK=1的分包给A
这就是MSL的概念,MSL就决定了1个分包单向传输的最长时间,如果超过,就认为丢掉了
在图中客户A TIME_WAIT为什么需要等待2MSL呢?
因为客户A在收到B发送过来的FIN=1,ACK=1分包后,首先会向B发送ACK,这个时间最大需要MSL,但是A需要保证B收到ACK,如果在此期间A向B发送的ACK丢掉了,A在此期间是不知道的,A只有收到第3步骤B向A发送的FIN=1,ACK=1的时候,A才能知道,A自己在第4步发送给B的ACK丢掉了,因此在B等待第4步A发送ACK期间,B经历了2MSL时间,所以A在2MSL时间内都没有收到,B向A发送的第3步的FIN=1,ACK=1的重传分包,A就会认为B收到了第4步,A向B发送的ACK分包了;
所以A才能进入CLOSED的状态
如果B没有收到ACK,那么在2个MSL时间内,A肯定会在2个MSL时间内 收到B向A发送的第3步的
FIN=1,ACK=1的分包的; A收到第3步FIN=1,ACK=1分包,会重新进入TIME_WAIT等待时间2MSL,
继续发送ACK,重试的流程;在A等待2MSL时间B能收到,A向B发送的最后1个ACK分包
为了让B能释放端口,减少资源占用
TCP三次握手:
连接的建立过程
客户端–请求连接-syn=1 seq=x 服务端
服务端–收到请求向客户端响应syn=1 ack=x+1 seq=y 客户端
客户端–建立连接 ack=y+1 seq=x+1 服务端
第一次 SYN
第二次 SYN+ACK
第三次 ACK
四次断开:
第一次挥手:A数据传输完毕需要断开连接,A的应用进程向其TCP发出连接释放报文段(FIN = 1,序号seq = u),
并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1状态,等待B的确认。
第二次挥手:B收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),
B进入CLOSE-WAIT关闭等待状态,此时的TCP处于半关闭状态,A到B的连接释放。而A收到B的确认后,
进入FIN-WAIT-2状态,等待B发出的连接释放报文段。
第三次挥手:当B数据传输完毕后,B发出连接释放报文段(FIN = 1,ACK = 1,序号seq = w,确认号ack=u+1),
B进入LAST-ACK(最后确认)状态,等待A 的最后确认。
第四次挥手:A收到B的连接释放报文段后,对此发出确认报文段(ACK = 1,seq=u+1,ack=w+1),
A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,A才进入CLOSE状态。
2、TCP网络应用程序开发流程
socket:
socket(套接字)是进程之间通信的一个工具。进程之间进行网络通信需要基于socket
socket的作用:
负责进程之间的数据传输。好比数据的搬运工
三次握手需要socket来实现
TCP网络应用程序开发分为:
TCP客户端程序开发
TCP服务端程序开发
客户端程序指运行在用户设备上的程序
服务端程序指运行在服务器设备上的程序,专门为客户端提供数据服务
1.客户端开发步骤
1.创建客户端套接字对象 socket()
2.和服务端建立连接 connect()
3.发送数据 send()
4.接收数据 recv()
5.文件传输结束,关闭客户端套接字
方法说明:
connect((host,port))表示和服务器端套接字建立连接,host是服务器Ip,port是对应的端口号
send(data)表示发送数据,data是二进制数据,发送数据要先转码
recv(buffersize)表示接收数据,buffersize是每次接收数据的最大长度,超了的话,只截取设置的个数
导入套接字模块
import socket
创建套接字对象
socket.socket(family, Type)
family表示IP地址类型,分为ipv4和ipv6
Type 表示传输协议类型
# ### TCP协议 客户端 #导入socket模块 import socket #1.创建套接字对象,AF_INET表示ipv4类型的ip地址.socket.SOCK_STREAM表示tcp传输协议类型 tcp传输默认是这俩 tcp_client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) #2.和服务端建立连接,里面是元祖。第一个元素是ip,一般不指定,表示本机任何一个地址都可以。第二个元素是服务端此程序开的端口号 tcp_client.connect(('127.0.0.1', 8099)) #3.收发数据 tcp_content = '我爱你' # 只能发送接收二进制数据,发送前先转码。将数据转码成gbk格式 """发送的数据类型是二进制字节流""" """b开头的字符串是二进制字节流格式,要求字符类型必须是ascii编码""" #不写数据类型,默认是utf-8 send_data = tcp_content.encode() # 发送数据到服务端 tcp_client.send(send_data) # 接收数据 receive_data = tcp_client.recv(1024) # 对二进制进行解码 data = receive_data.decode() # 输出到屏幕上 print(data) # 4.关闭连接 tcp_client.close()
客户端接收到服务端的数据
2.服务端程序开发流程
.创建套接字对象,
2.绑定端口号 bind()
3.设置监听 listen()
4.等待接收客户端连接请求 accept() 如果没有客户端建立连接请求,一直等待
5.接收客户端数据 recv()
6.应答客户端 send()
7.关闭新生成的套接字,服务端套接字
方法说明:
bind((host, port))表示和客户端套接字建立连接,ip地址一般不指定,表示本机的任何一个ip都可以,port是对应的端口号
listen(backlog)表示设置监听,backlog表示最大等待建立连接的个数,单任务最多128
accept()表示等待接收客户端的请求
send(data)表示发送数据,data是二进制数据,发送数据要先转码recv(buffersize)表示接收数据,recv(buffersize)表示接收数据,buffersize是每次接收数据的最大长度,超了的话,只截取设置的个数
#导入socket模块 import socket import time #1.创建socket对象,创建套接字对象,AF_INET表示ipv4类型的ip地址.socket.SOCK_STREAM表示tcp传输协议类型。默认就是这个类型,所以如果是tcp传输。里面内容不用写 tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #2. 绑定ip和端口号,里面是个元祖。第一个参数是ip,一般不指定,表示本机任何一个地址都可以 """ 默认本地ip : 127.0.0.1 => localhost """ tcp_server.bind(('127.0.0.1', 8099)) #3. 设置监听,128表示最大等待建立连接个数 tcp_server.listen(128) #4.三次握手,使用accept # 等待接收客户端连接,每次当客户端和服务端建立连接成功,会返回一个新的套接字 # tcp_server只负责等待接收客户端请求,收发消息不使用该套接字 # accept()返回的是一个新的套接字和一个元祖,元组中有两个元素,第一个是客户端ip,第二个是端口号 # 收发消息都用新生成的套接字 # print(tcp_server.accept()) # 元祖拆包 new_client, ip_port = tcp_server.accept() print('客户端的ip和端口号为:', ip_port) # 5.接收发送客户端数据 receive_data = new_client.recv(1024) data = receive_data.decode('gbk') print(data) # 发送给客户端数据 time.sleep(3) send_content = '正在处理中' send_data = send_content.encode('gbk') new_client.send(send_data) #6.关闭连接,关闭与客户端服务的套接字,四次挥手 new_client.close() #7.关闭服务端套接字,表示服务端不在等待接收客户端连接请求,退还端口 tcp_server.close()
服务端收到客户端信息
服务端接收到客户端的程序。客户端端口号每次是随机分配的,如下图元祖
3.端口号复用
当客户端和服务端建立连接后,服务端退出后端口号不会立即释放,需要等待大概1-2分钟
解决办法:
1.更换服务端端口号
2.设置端口复用,也就是说让服务端程序退出后立即释放端口号
端口号复用代码:
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
参数1:当前套接字
参数2:设置端口号复用选项
参数3:设置端口号复用选项对应的值
服务端加上这句,就可以实现端口复用。
# 设置端口复用,设置场景,当a用户访问完数据库时,b用户再去访问数据库,因为端口判定还没有关闭# 所以进行端口复用的操作,当a用户的套接字关闭之后,不会影响下一个用户连接服务端
一发一收是一对,不匹配会导致数据异常
send 发送 recv 接受
4.TCP应用程序开发注意事项
4.TCP应用程序开发注意事项
1.客户端想和服务端通信时必须先建立连接
2.客户端程序一般不用绑定端口号,客户端是主动连接别人的
3.TCP服务端必须绑定端口号,否则客户端找不到这个服务端程序
4.listen后的套接字是被动套接字,只负责接收新的客户端连接,不能收发消息
5.客户端,服务端建立连接成功后,会生成一个新的套接字,收发客户端消息是该套接字
6.关闭新生成的套接字意味着和这个客户端通信完毕
7.关闭listen后的套接字意味着服务端套接字关闭,不在接收新的客户端连接请求。但之前连接成功的客户端还能正常通信
8.当客户端套接字调用close后,服务端的recv会阻塞,返回数据长度为0,服务端可以通过返回数据长度来判断客户端
是否在线,反之,关闭服务端套接字,客户端recv会阻塞。返回数据长度为0
上述操作,服务器与客户端发送完数据后,连接就断掉了,不符合实际使用场景。因此我们需要改进,实现循环发送接收
但要保证,发送数据和接收数据成对存在服务端:
#导入模块 import socket #1.床架技能socket对象 tcp_server = socket.socket() #2.设置端口复用 tcp_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #3.绑定ip和端口,bind里面放的是元祖 tcp_server.bind(("127.0.0.1",9122)) #4.设置监听,客户端的连接数 tcp_server.listen(128) #6.实现多次收发设置 while True: # 5.三次握手 conn, ip_port = tcp_server.accept() while True: #接收数据 res = conn.recv(1024) print(res.decode()) #发送数据 strvar = input("[服务端]:") conn.send(strvar.encode()) if strvar == "q": break #7.四次挥手 conn.close() #8.退还端口 tcp_server.close()
客户端:
#客户端实现循环收发 import socket #1.创建socket对象 tcp_client = socket.socket() #2.连接服务端,里面放的是元祖 tcp_client.connect(("127.0.0.1",9122)) #3.实现循环收发数据 while True: #发送数据 strvar = input("[客户端]:") tcp_client.send(strvar.encode()) #接收数据 res = tcp_client.recv(1024) if res == b"q": break print(res.decode()) #4.关闭连接 tcp_client.close()
服务端这样配置。可以保证,服务端把客户端断掉后,服务端还在,可以与其他客户端建立新的连接
可以实现循环收发数据,但每次只能发一次,等对方回复后,才能继续发下一次消息
消息滞后,客户端连续发多条消息,但是服务端刚开始只能收到一条,当服务端回复后,才能接着收到下面的消息
如果要实现无阻塞性的收发,需要服务端与客户端分开,并通过消息队列实现
把这两个都勾上,可以并发执行当前文件,原来的执行保持不变。 默认再次执行该文件,是终止当前,重新执行的。
好了,今天的python的TCP网络编程就聊到这里,接下来还会继续聊一聊UDP编程,tcpserver等等