作者:-qone0_784 | 来源:互联网 | 2023-12-14 18:16
本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。
本节内容
- 网络通信概念
- socket编程
- socket模块一些方法
- 聊天socket实现
- 远程执行命令及上传文件
- socketserver及其源码分析
1.网络通信概念
说到网络通信,那就不得不说TCP/IP协议簇的OSI七层模型了,这个东西当初在学校都学烂了。。。(PS:毕竟本人是网络工程专业出身。。。) 简单介绍下七层模型从底层到上层的顺序:物理层(定义物理设备的各项标准),数据链路层(mac地址等其他东西的封装),网络层(IP包头的的封装),传输层(TCP/UDP数据报头的封装),会话层(这一层涉及的东西不多,但是我觉得SSL(安全套接字)应该封装在这一层,对于应用是透明的),表示层(数据的压缩解压缩放在这一层),应用层(这一层就是用户使用的应用了)
OSI的七层模型从上到下是一层一层封装的过程,一个数据从一台计算机到另一台计算机是先从上到下封装,之后传输到达另一台计算机之后是从下到上一层一层解封装的过程。
好了,说了这么多,那么我们开发里面涉及到的网络通信包含一些什么东西呢?首先我们程序开发中大部分所使用的协议是TCP协议(传输层)UDP协议涉及的比较少。网络层是IPV4协议,当然IPV6可能是未来的主流,还有一些其他网络协议这里并不涉及。。。SSL(安全套接字)可能在web应用中为了提供数据安全性用得比较多,但是服务端应用程序和程序之间的数据传输不会使用到ssl进行数据安全性的保护。所以,我们平常用的基于网络编程所使用的网络协议一般就是使用传输层的TCP协议,网络层的IPV4协议,更底层的协议。。。并不需要我们考虑,那些是网络设备之间该关心的事情。。。哈哈哈
那么,为什么要用TCP协议呢?因为TCP协议提供了一个面向连接的,稳定的通道,数据是按照顺序到达的,TCP协议的机制保证了TCP协议传输数据是一个可靠的传输。但是TCP协议也有他的缺点,那就是保证可靠传输所牺牲的代价就是速度比较慢(当然也不是很离谱,除非在某些极端情况下:比如传输大量的小数据)TCP之所以是面向连接的是因为它的三次握手以及四次挥手(太出名了,这里不用介绍了。。。),保障数据包按照顺序到达是通过分片编号实现的,每一个数据包都有一个编号,接收端根据这个编号去将接收的数据按原来数据顺序组装起来。当然,TCP协议之所以是一个可靠的传输和他的重传机制也是分不开的,当对方没有收到你发送的一个数据包的时候,TCP协议会重新传输这个数据包到对方,直到对方确认收到了这个数据包为止。。。。当然TCP协议里面还有很多优秀的东西。这里就不一一赘述了。哈哈,不然这里就成了网络专场了。。。
好了,现在确定了我们使用TCP/IP来进行网络通信,那么要实现通信需要知道对方机器的IP地址和端口号(当然,这里指的端口号一般是指TCP的端口号,从1-65535,其中,1024及之前的端口被定义为知名端口,最好不要使用)
2.socket编程
前面的第一节只是前戏,为了引出我们今天介绍的socket编程,这前戏做的也是挺累的,哈哈哈。
python中的socket是一个模块,使用这个内置模块能够让我们实现网络通信。其实在unix/linux一切皆文件的思想下,socket也可以看作是一个文件。。。python进行网络数据通信也可以理解为一个打开文件读写数据的操作,只不过这是一个特殊的操作罢了。接下来是一个关于socket通信的流程图:
流程描述:
-
服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
-
服务器为socket绑定ip地址和端口号
-
服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
-
客户端创建socket
-
客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
-
服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直等到客户端返回连接信息后才返回,开始接收下一个客户端连接请求
-
客户端连接成功,向服务器发送连接状态信息
-
服务器accept方法返回,连接成功
-
客户端向socket写入信息(或服务端向socket写入信息)
-
服务器读取信息(客户端读取信息)
-
客户端关闭
-
服务器端关闭
3.socket模块一些方法
下面是socket模块里面提供的一些方法,让我们实现socke通信。
sk=socket.socket()
创建socket对象,这个时候可以指定两个参数,一个是family,另一个是type
family:AFINET(代表IPV4,默认参数),AFINET6(代表使用IPV6),AF_UNIX(UNIX文件系统通信)
type:SOCKSTREAM(TCP协议通信,默认参数),SOCKDGRAM(UDP协议通信) 默认不写的话,family=AFINET type=SOCKSTREAM
sk.bind(address)
sk.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。
sk.listen(backlog)
开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。 backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5 这个值不能无限大,因为要在内核中维护连接队列
sk.setblocking(bool)
是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。
sk.accept()
接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。
接收TCP 客户的连接(阻塞式)等待连接的到来
sk.connect(address)
连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
sk.connect_ex(address)
同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061
sk.close()
关闭套接字
sk.recv(bufsize[,flag])
接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。
sk.recvfrom(bufsize[.flag])
与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
sk.send(string[,flag])
将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。
sk.sendall(string[,flag])
将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
内部通过递归调用send,将所有内容发送出去。
sk.sendto(string[,flag],address)
将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。
sk.settimeout(timeout)
设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )
sk.getpeername()
返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
sk.getsockname()
返回套接字自己的地址。通常是一个元组(ipaddr,port)
sk.fileno()
套接字的文件描述符
4.聊天socket实现
好了,前戏做了那么多,到现在都还没写一行代码呢,接下来到了实战的步骤了。
1 #server端实现:
2 import socket # 导入socket模块
3 sk=socket.socket() # 实例化socket对象
4 address=("0.0.0.0",8000) # 定义好监听的IP地址和端口,可以绑定网卡上的IP地址,但是生产环境中一般绑定0.0.0.0这样所有网卡上都能够接收并处理这个端口的数据了
5 sk.bind(address) # 绑定IP地址和端口
6 sk.listen(5) # 设定侦听开始并设定侦听队列里面等待的连接数最大为5
7
8 while True: # 循环,为了建立客户端连接
9 conn,addr=sk.accept() # conn拿到的是接入的客户端的对象,之后服务端和客户端通信都是基于这个conn对象进行通信,add获取的是连接的客户端的IP和端口
10 while True: # 这个循环来实现相互之间聊天的逻辑
11 try: # 为什么要使用try包裹这一块呢?因为在通信过程中客户端突然退出了的话,服务端阻塞在recv状态时将会抛出异常并终止程序,为了解决这个异常,需要我们自己捕获这个异常,这是在windows上,linux上不会抛出异常
12 data=conn.recv(1024) # 定义接收的数据大小为1024个字节并接受客户端传递过来的数据,注意这里的数据在python3中是bytes类型的,在python3中网络通信发送和接收的数据必须是bytes类型的,否则会报错
13 if data: # 假如客户端传递数据过来时
14 print("-->",str(data,"utf-8")) # 打印客户端传递过来的数据,需要从bytes类型数据解码成unicode类型的数据
15 data=bytes(input(">>>"),"utf-8") # 接收输入的数据并转换成bytes类型的数据
16 conn.send(data) # 将bytes类型的数据发送给客户端
17 else: # 否则关闭这个客户端连接的对象,当客户端正常退出,执行了sk.close()时将不会发送数据到服务端,也就是说recv获取到的数据是空的
18 conn.close() # 这时关闭这个conn对象并退出当前循环等待下一个客户端对象来连接
19 break
20 except ConnectionResetError as e: # 捕获到异常之后,打印异常出来并退出循环等待下一个客户端连接
21 print(e)
22 break
23
24 #client端实现
25 import socket # 导入socket模块
26 sk=socket.socket() # 实例化客户端对象
27 address=("127.0.0.1",8000) # 设置客户端需要连接的服务端IP地址以及端口
28 sk.connect(address) # 连接服务端的IP地址以及端口
29 while True: # 循环实现对话
30 data=input(">>>").strip() # 获取用户输入的数据
31 if data=="exit": # 如果输入的是exit 关闭该对象并退出程序
32 sk.close() # 关闭对象
33 break # 退出循环
34 sk.send(bytes(data,"utf-8")) # 发送刚输入的数据,要先转换成bytes类型的数据
35 data=str(sk.recv(1024),"utf-8") # 接收服务端发送的数据,并将其转换成unicode数据类型
36 print("-->",data) # 打印服务端传输过来的数据
显示代码