python学习笔记10 ----网络编程
网络编程
网络编程需要知道的概念
网络体系结构就是使用这些用不同媒介连接起来的不同设备和网络系统在不同的应用环境下实现互操作性,并满足各种业务需求的一种粘合剂。网络体系结构解决互质性问题彩是分层方法。
1.网络(OSI)的7层模型:
应用层--->为应用程序提供网络通信服务
表示层--->数据表示
会话层--->主机间通信(两个应用进程间)
传输层--->端到端的连接,隔离网络的上下层协议,使得网络应用与下层协议无关
网络层--->寻找最优路径,转发数据包
数据链路层--->无差错的链路连接
物理层--->二进制传输
2.端口
是一种抽象的软件结构,包括一些数据结构和I/O缓冲区。与协议有关。
3.套接字存在于通信区域中。
通信区域也叫地址族,它是一个抽象的概念,主要用于将通过套接字通信的进程的共有特性综合在一起。
为保证数据的正确性,在网络协议中需要制定网络字节顺序,采用统一的网络字节顺序。
网络通信三要素:
IP地址:用于表示主机(IP地址 = 网络ID+主机ID)
端口号:用于标识进程的逻辑端口
传输协议:TCP UDP
网络通信过程就是一个不断封装和解析的过程
Socket是连接应用程序与网络驱动程序的桥梁,Socket在应用程序中创建,通过绑定操作与驱动程序建立关系。
套接字
套接字是为特定网络协议(例如TCP/IP,ICMP/IP,UDP/IP等)套件对上的网络应用程序提供者提供当前可移植标准的对象。它们允许程序接受并进行连接,如发送和接受数据。为了建立通信通道,网络通信的每个端点拥有一个套接字对象极为重要。
套接字为BSD UNIX系统核心的一部分,而且他们也被许多其他类似UNIX的操作系统包括Linux所采纳。许多非BSD UNIX系统(如ms-dos,windows,os/2,mac os及大部分主机环境)都以库形式提供对套接字的支持。
三种最流行的套接字类型是:stream,datagram和raw。stream和datagram套接字可以直接与TCP协议进行接口,而raw套接字则接口到IP协议。但套接字并不限于TCP/IP。
套接字模块 -------SOCKET()模块
套接字模块是一个非常简单的基于对象的接口,它提供对低层BSD套接字样式网络的访问。使用该模块可以实现客户机和服务器套接字。要在python 中建立具有TCP和流套接字的简单服务器,需要使用socket模块。利用该模块包含的函数和类定义,可生成通过网络通信的程序。
SOCKET内建方法
函数 | 描述 |
服务器端套接字函数 | |
s.bind() |
绑定地址(主机,端口号对)到套接字 s.bind(address) 将套接字绑定到地址。 address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。 |
s.listen() |
开始TCP 监听 开始监听传入连接。添加参数是指定在拒绝连接之前,可以挂起的最大连接数量。 如果参数等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5 |
s.accept() |
被动接受TCP 客户的连接,(阻塞式)等待连接的到来 接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。 接收TCP 客户的连接(阻塞式)等待连接的到来 |
客户端套接字函数 | |
s.connect() |
主动初始化TCP 服务器连接,参数为地址(address) 连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。 |
s.connect_ex() |
connect()函数的扩展版本,出错时返回出错码,而不是抛异常 同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061 |
公共用途的套接字函数 | |
s.recv() |
接收TCP 数据 sk.recv(bufsize[,flag]) 接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。 |
s.send() |
发送TCP 数据 将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。 |
s.sendall() |
完整发送TCP 数据 将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。 内部通过递归调用send,将所有内容发送出去。 |
s.recvfrom() |
接收UDP 数据 与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。 |
s.sendto() |
发送UDP 数据 将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。 |
s.getpeername() |
连接到当前套接字的远端的地址 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。 |
s.getsockname() |
当前套接字的地址 返回套接字自己的地址。通常是一个元组(ipaddr,port) |
s.getsockopt() | 返回指定套接字的参数 |
s.setsockopt() | 设置指定套接字的参数 |
s.close() | 关闭套接字 |
面向模块的套接字函数 | |
s.setblocking() |
设置套接字的阻塞与非阻塞模式 是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。 |
s.settimeout()a |
设置阻塞套接字操作的超时时间 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s ) |
s.gettimeout()a | 得到阻塞套接字操作的超时时间 |
面向文件的套接字的函数 | |
s.fileno() | 套接字的文件描述符 |
s.makefile() | 创建一个与该套接字关连的文件 |
建立服务器连接需要六个步骤:
1.创建socket对象。调用socket构造函数。
socket=socket.socket(familly,type)
family的值可以是AF_UNIX(Unix域,用于同一台机器上的进程间通讯),也可以是AF_INET(对于IPV4协议的TCP和 UDP),至于type参数,SOCK_STREAM(流套接字)或者 SOCK_DGRAM(数据报文套接字),SOCK_RAW(raw套接字)。
2.则是将socket绑定(指派)到指定地址上,socket.bind(address)
address必须是一个双元素元组,((host,port)),主机名或者ip地址+端口号。如果端口号正在被使用或者保留,或者主机名或ip地址错误,则引发socke.error异常。
3.绑定后,必须准备好套接字,以便接受连接请求。
socket.listen(backlog)
backlog指定了最多连接数,至少为1,接到连接请求后,这些请求必须排队,如果队列已满,则拒绝请求。
4.服务器套接字通过socket的accept方法等待客户请求一个连接:
connection,address=socket.accept()
调用accept方法时,socket会进入'waiting'(或阻塞)状态。客户请求连接时,方法建立连接并返回服务器。accept方法返回 一个含有俩个元素的元组,形如(connection,address)。第一个元素(connection)是新的socket对象,服务器通过它与客 户通信;第二个元素(address)是客户的internet地址。
5. 处理阶段,服务器和客户通过send和recv方法通信(传输数据)。服务器调用send,并采用字符串形式向客户发送信息。send方法 返回已发送的字符个数。服务器使用recv方法从客户接受信息。调用recv时,必须指定一个整数来控制本次调用所接受的最大数据量。recv方法在接受 数据时会进入'blocket'状态,最后返回一个字符串,用它来表示收到的数据。如果发送的量超过recv所允许,数据会被截断。多余的数据将缓冲于接 受端。以后调用recv时,多余的数据会从缓冲区删除。
6. 传输结束,服务器调用socket的close方法以关闭连接。
建立一个简单客户连接则需要4个步骤。
第1步,创建一个socket以连接服务器 socket=socket.socket(family,type)
第2步,使用socket的connect方法连接服务器 socket.connect((host,port))
第3步,客户和服务器通过send和recv方法通信。
第4步,结束后,客户通过调用socket的close方法来关闭连接。
以下案例都是以TCP方式连接
import socket sk = socket.socket() address = ('127.0.0.1',8000) sk.bind(address) sk.listen(2) print("......") while True: conn, addr = sk.accept() print(addr) while True: try: date = conn.recv(1024) except Exception: break if not date:break print(str(date, "utf8")) inp = input(">>>>:") conn.send(bytes(inp,"utf8")) # conn, addr = sk.accept() # while True: # date = conn.recv(1024) # if not date: # conn, addr = sk.accept() # continue # print(str(date, "utf8")) # inp = input(">>>>:") # conn.send(bytes(inp,"utf8")) # conn.close() conn.close()
import socket sk = socket.socket() address = ("127.0.0.1",8000) sk.connect(address) while True: inp = input(">>>>:") if inp == "q": break sk.send(bytes(inp, "utf8")) date = sk.recv(1024) print(str(date,"utf8")) sk.close()
简单的模拟qq对话(socket模块)
import socket,subprocess sk = socket.socket() address = ('0.0.0.0',8000) sk.bind(address) sk.listen(2) print("......") while True: conn, addr = sk.accept() print(addr) while True: try: date = conn.recv(1024) except Exception: break if not date:break print(str(date, "utf8")) obj = subprocess.Popen(str(date, "utf8"),shell=True,stdout=subprocess.PIPE) cmd_result = obj.stdout.read() result_len = str(len(cmd_result)) print(result_len) conn.send(bytes(result_len,"utf8")) conn.send(cmd_result) conn.close()
import socket sk = socket.socket() address = ("127.0.0.1",8000) sk.connect(address) while True: inp = input(">>>>:") if inp == "q": break sk.send(bytes(inp, "utf8")) result_len = int(str(sk.recv(1024),"utf8")) print(result_len) date = bytes() while len(date) != result_len: da = sk.recv(1024) date+=da print(str(date,"gbk")) sk.close()
简单的FTP上传图片
import subprocess import socket import os sk=socket.socket() print(sk) address=('127.0.0.1',8000) sk.bind(address) sk.listen(3) print('waiting......') BASE_DIR=os.path.dirname(os.path.abspath(__file__)) while 1: conn, addr = sk.accept() while 1: data=conn.recv(1024) cmd,filename,filesize=str(data,'utf8').split('|') path=os.path.join(BASE_DIR,'tupian',filename) filesize=int(filesize) f=open(path,'ab') has_receive=0 while has_receive!=filesize: data=conn.recv(1024) f.write(data) has_receive+=len(data) f.close()
import socket import os sk=socket.socket() address=('127.0.0.1',8000) sk.connect(address) BASE_DIR=os.path.dirname(os.path.abspath(__file__)) while True: inp=input('>>>').strip()# post|11.png cmd,path=inp.split('|') path=os.path.join(BASE_DIR,path) filename=os.path.basename(path) file_size=os.stat(path).st_size file_info='post|%s|%s'%(filename,file_size) sk.sendall(bytes(file_info,'utf8')) f=open(path,'rb') has_sent=0 while has_sent!=file_size: data=f.read(1024) sk.sendall(data) has_sent+=len(data) f.close() print('上传成功')
socketserver 模块
socketserver是标准库中一个高级别的模块,用于简化网络客户与服务器的实现。模块中,已经实现了一些可供使用的类。
SocketServer模块中的类主要有以下几个:
1、BaseServer 包含服务器的核心功能与混合类(mix-in)的钩子功能。这个类主要用于派生,不要直接生成这个类的类对象,可以考虑使用TCPServer和UDPServer类。
2、TCPServer 基本的网络同步TCP服务器
3、UDPServer 基本的网络同步UDP服务器
4、ForkingMixIn 实现了核心的进程化功能,用于与服务器类进行混合(mix-in),以提供一些异步特性。不要直接生成这个类的对象。
5、ThreadingMixIn 实现了核心的线程化功能,用于与服务器类进行混合(mix-in),以提供一些异步特性。不要直接生成这个类的对象。
6、ForkingTCPServer ForkingMixIn与TCPServer的组合
7、ForkingUDPServer ForkingMixIn与UDPServer的组合
8、BaseRequestHandler
9、StreamRequestHandler TCP请求处理类的一个实现
10、DataStreamRequestHandler UDP请求处理类的一个实现
一、处理程序
使用本模块时,必须定义一个继承于基类(父类)BaseRequestHandler的处理程序类。BaseRequestHandler类的实例h可以实现以下方法:
1、h.handle() 调用该方法执行实际的请求操作。调用该函数可以不带任何参数,但是几个实例变量包含有用的值。h.request包含请求,h.client_address包含客户端地址,h.server包含调用处理程序的实例。对于TCP之类的数据流服务,h.request属性是套接字对象。对于数据报服务,它是包含收到数据的字节字符串。所有的逻辑函数都写在这个方法里。
2、h.setup() 该方法在handle()之前调用。默认情况下,它不执行任何操作。如果希望服务器实现更多连接设置(如建立SSL连接),可以在这里实现。
3、h.finish() 调用本方法可以在执行完handle()之后执行清除操作。默认情况下,它不执行任何操作。如果setup()和handle()方法都不生成异常,则无需调用该方法。
如果知道应用程序只能操纵面向数据流的连接(如TCP),那么应从StreamRequestHandler继承,而不是BaseRequestHandler。StreamRequestHandler类设置了两个属性,h.wfile是将数据写入客户端的类文件对象,h.rfile是从客户端读取数据的类文件对象。
如果要编写针对数据包操作的处理程序并将响应持续返回发送方,那么它应当从DatagramRequestHandler继承。它提供的类接口与StramRequestHandler相同。
二、服务器
socketserver 类提供的server类之间的关系
+------------+
| BaseServer | -----原生类(基础类)
+------------+
|
v
提供tcp连接的一些服务 与UDPserver只差一个参数(address_family = socket.AF_UNIX)
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
提供tcp连接的一些服务 与TCPserver只差一个参数(address_family = socket.AF_UNIX)
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
要使用处理程序,必须将其插入到服务器对象。定义了四个基本的服务器类。
(1)TCPServer(address,handler) 支持使用IPv4的TCP协议的服务器,address是一个(host,port)元组。Handler是BaseRequestHandler或StreamRequestHandler类的子类的实例。
(2)UDPServer(address,handler) 支持使用IPv4的UDP协议的服务器,address和handler与TCPServer中类似。
(3)UnixStreamServer(address,handler) 使用UNIX域套接字实现面向数据流协议的服务器,继承自TCPServer。
(4)UnixDatagramServer(address,handler) 使用UNIX域套接字实现数据报协议的服务器,继承自UDPServer。
所有四个服务器类的实例都有以下方法和变量:
1、s.socket 用于传入请求的套接字对象。
2、s.sever_address 监听服务器的地址。如元组("127.0.0.1",80)
3、s.RequestHandlerClass 传递给服务器构造函数并由用户提供的请求处理程序类。
4、s.serve_forever() 处理无限的请求
5、s.shutdown() 停止serve_forever()循环
6、s.fileno() 返回服务器套接字的整数文件描述符。该方法可以有效地通过轮询操作(如select()函数)使用服务器实例。
三、定义自定义服务器
服务器往往需要特殊的配置来处理不同的网络地址族、超时期、并发和其他功能,可以通过继承上面四个基本服务器类来自行定义。
可以通过混合类获得更多服务器功能,这也是通过进程或线程分支添加并发行的方法。为了实现并发性,定义了以下类:
(1)ForkingMixIn 将UNIX进程分支添加到服务器的混合方法,使用该方法可以让服务器服务多个客户。
(2)ThreadingMixIn 修改服务器的混合类,可以使用线程服务多个客户端。
要向服务器添加这些功能,可以使用多重继承,其中首先列出混了类。
由于并发服务器很常用,为了定义它,SocketServer预定义了以下服务器类:
(1)ForkingUDPServer(address,handler)
(2)ForkingTCPServer(address,handler)
(3)ThreadingUDPServer(address,handler)
(4)ThreadingTCPServer(address,handler)
创建socketserver服务端与客户端
import socketserver print("......") class Myserver(socketserver.BaseRequestHandler): def handle(self): #源码以定义好的方法,只需写具体的逻辑方法 print("服务器启动:") while True: conn = self.request print(self.client_address) while True: try: date = conn.recv(1024) except Exception: break if not date:break print(str(date, "utf8")) inp = input(">>>>:") conn.send(bytes(inp,"utf8")) conn.close() if __name__ == "__main__": server = socketserver.ThreadingTCPServer(("127.0.0.1",8010),Myserver) #元组创建对象,绑定address(),实现监听(源码默认为5) server.serve_forever() #程序启动
import socket sk = socket.socket() address = ("127.0.0.1",8010) sk.connect(address) while True: inp = input(">>>>:") if inp == "q": break sk.send(bytes(inp, "utf8")) date = sk.recv(1024) print(str(date,"utf8")) sk.close()