python3之socket&socketserver网络编程
1、套接字与套接模块
套接字是为特定网络协议(例如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。
套接字模块是一个非常简单的基于对象的接口,它提供对低层BSD套接字样式网络的访问。使用该模块可以实现客户机和服务器套接字。要在python 中建立具有TCP和流套接字的简单服务器,需要使用socket模块。利用该模块包含的函数和类定义,可生成通过网络通信的程序。
python提供了两个级别访问的网络服务:
- 低级的网络服务支持基本的socket,它提供了标准的BSD sockets API,可以访问底层操作系统socket接口的全部方法
- 高级别的网络服务模块socketServer,它提供了服务器中心类,可以简化网络服务器的开发。
2、socket模块方法
socket.
socket
(family = AF_INET,type = SOCK_STREAM,proto = 0,fileno = None ) :使用给定的地址系列,套接字类型和协议号创建一个新套接字
socket.
socketpair
([ family [,type [,proto ] ] ] ):使用给定的地址系列,套接字类型和协议编号构建一对连接的套接字对象
socket.
create_connection
(address [,timeout [,source_address ] ] ):连接到侦听Internet 地址(2元组 )的TCP服务,然后返回套接字对象
socket.
fromfd
(fd,family,type,proto = 0 ):复制文件描述符fd(由文件对象的fileno()
方法返回的整数 ),并从结果中构建一个套接字对象
socket.
fromshare
(data):从该socket.share()
方法获得的数据实例化一个套接字。假设套接字处于阻塞模式。
socket.
SocketType:这是表示套接字对象类型的Python类型对象。这是一样的
type(socket(...))
socket.
getaddrinfo
(host,port,family = 0,type = 0,proto = 0,flags = 0 ):将主机 / 端口参数转换为5元组序列,其中包含创建连接到该服务的套接字的所有必要参数
socket.
getfqdn
([ name ] ):为名称返回完全限定的域名。如果名称被省略或为空,则被解释为本地主机
socket.
gethostbyname
(hostname):将主机名转换为IPv4地址格式。IPv4地址以字符串形式返回
socket.
gethostbyname_ex
(hostname):将主机名转换为IPv4地址格式,扩展接口。返回一个triple ,其中主机名是响应给定ip_address的主要主机名,aliaslist是同一地址的备用主机名(可能为空)列表,ipaddrlist是同一主机上同一接口的IPv4地址列表经常但不总是一个地址)。不支持IPv6名称解析,应该用于IPv4 / v6双栈支持
socket.
gethostname
():返回包含Python解释器当前正在执行的机器的主机名的字符串
socket.
gethostbyaddr
(ip_address ):返回一个triple ,其中hostname是响应给定ip_address的主要主机名,aliaslist是同一地址的备用主机名(可能为空)列表, ipaddrlist是同一个接口的IPv4 / v6地址列表主机(最有可能只包含一个地址)。要找到完全限定的域名,请使用该功能。支持IPv4和IPv6
socket.
getnameinfo
(sockaddr,flags ):将套接字地址sockaddr翻译成2元组。根据标志的设置,结果可以在主机中包含完全限定的域名或数字地址表示。同样,端口可以包含字符串端口名称或数字端口号。(host, port)
socket.
getprotobyname
(protocolname ):将Internet协议名称(例如,'icmp'
)转换为适合作为(可选)第三个参数传递给该socket()
函数的常量。这通常只需要以“原始”模式(SOCK_RAW
)打开的套接字; 对于正常的套接字模式,如果协议被省略或为零,则自动选择正确的协议
socket.
getservbyname
(servicename [,protocolname ] ):将Internet服务名称和协议名称转换为该服务的端口号。可选的协议名称,如果有,应该是'tcp'
或 'udp'
,否则任何协议将匹配
socket.
getservbyport
(port [,protocolname ] ):将Internet端口号和协议名称转换为该服务的服务名称。可选的协议名称,如果有,应该是'tcp'
或 'udp'
,否则任何协议将匹配
socket.
ntohl
(x ):将32位正整数从网络转换为主机字节顺序。在主机字节顺序与网络字节顺序相同的机器上,这是无操作的; 否则,它会执行一个4字节的交换操作。
socket.
ntohs
(x ):将16位正整数从网络转换为主机字节顺序。在主机字节顺序与网络字节顺序相同的机器上,这是无操作的; 否则,它执行一个2字节的交换操作
socket.
htonl
(x )将32位正整数从主机转换为网络字节顺序。在主机字节顺序与网络字节顺序相同的机器上,这是无操作的; 否则,它会执行一个4字节的交换操作。
socket.
htons
(x )将16位正整数从主机转换为网络字节顺序。在主机字节顺序与网络字节顺序相同的机器上,这是无操作的; 否则,它执行一个2字节的交换操作
socket.
inet_aton
(ip_string ):将IPv4地址从点分四字符串格式(例如“123.45.67.89”)转换为32位打包二进制格式,作为长度为4个字符的字节对象。当与使用标准C库的程序进行交谈并且需要类型对象
socket.getdefaulttimeout():返回新套接字对象的默认超时值(秒)(float)。值None表示新的套接字对象没有超时。首次导入套接字模块时,默认为None。
socket.setdefaulttimeout(timeout):为新的套接字对象设置默认的超时值(秒)(float)。首次导入套接字模块时,默认为None。请参阅 settimeout()可能的值和它们各自的含义。
socket.sethostname(name):将机器的主机名称设为名称。OSError如果你没有足够的权利,这将会提高 。
socket.if_nameindex():返回网络接口信息列表(index int,name string)元组。 OSError如果系统调用失败。
socket.if_nametoindex(if_name ):返回接口名称对应的网络接口索引号。 OSError如果没有给定名称的接口存在。
socket.if_indextoname(if_index ):返回接口索引号对应的网络接口名称。 OSError如果没有给定索引的接口存在。
3、socket链接
一般socket建立链接需要六个步骤,其中包括:socket.socket()创建socket对象、s.bind绑定地址到socket对象、s.listen监听地址端口、s.accept阻塞接受链接请求、s.send,s.recv方法处理通信数据、s.close关闭链接。
服务器创建套接字链接:
1)创建socket对象: socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
socket.socket(socket.AF_INET,socket.SOCK_STREAM)使用给定的地址族,套接字类型和协议号来创建一个新套接字.
>>> import socket #创建TCP socket: >>> sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建UDP socket: >>> sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
family为指定的地址族:
socket.AF_UNIX :只能够用于单一的Unix系统进程间通信
socket.AF_INET :服务器之间的网络通信(ipv4协议的TCP和UDP)ipv4,默认为这个
socket.AF_INET6 :服务器之间的网络通信ipv6
type为指定的套接字类型:
socket.SOCK_STREAM :面向连接的TCP,默认为这个
socket.SOCK_DGRAM :面向非连接的UDP
family和type参数是指定了一个协议,我们也可以使用proto第三个参数来直接指定使用的协议。我们也可以使用socket下的函数getprotobyname('tcp'),来代替IPPTOTO_XX变量.
>>> import socket >>> socket.getprotobyname('tcp') 6 >>> socket.IPPROTO_TCP 6
proto为指定的协议号,一般为0:
socket.IPPROTO_TCP :TCP传输协议
socket.IPPROTO_UDP :UDP传输协议
socket.IPPROTO_ICMP :ICMP链接
socket.IPPROTO_IP :IP链接
socket.IPPROTO_RAW :要构建IP头部和要发送的各种协议的头部和数据,包括ip头和协议和数据。
2)socket对象绑定地址及端口
地址必须是一个双元素的元组,包括(host,port)主机名或IP地址+端口号。如果端口号或地址错误将引发socke.error异常。
import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM,socket.IPPROTO_TCP) HostPort = ('127.0.0.1',8898) s.bind(HostPort) #绑定地址端口
3)socket对象监听地址端口链接
socket.listen(backlog)
backlog指定了最多连接数,至少为1,接到连接请求后,这些请求必须排队等候连接,如果队列已满,则拒绝请求。
import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM,socket.IPPROTO_TCP)
HostPort = ('127.0.0.1',8898) s.bind(HostPort) #绑定地址端口 s.listen(5) #监听最多5个连接请求
4)socket.accept对象阻塞等待接受链接
fd, addr = self._accept()
调用accept方法时,socket会进入‘waiting’阻塞状态,客户请求连接时,方法会建立连接并返回服务器。
accept方法会返回一个含有两个元素的元组,(fd,addr)。第一个元素是新的socket对象,服务器通过它与客户端通信。第二个元素是客户端的地址及端口信息。
import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) HostPort = ('127.0.0.1',8899) s.bind(HostPort) #绑定地址端口 s.listen(5) #监听最多5个连接请求 while True: print('server socket waiting...') obj,addr = s.accept() #阻塞等待链接 print('socket object:',obj) print('client info:',addr) #运行,链接输出信息 server socket waiting... socket object: <socket.socket fd=260, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8899), raddr=('127.0.0.1', 28237)> client info: ('127.0.0.1', 28237) server socket waiting...
5)处理阶段,服务器与客户端通过send和recv方法通信(传输数据)
调用新链接对象与客户端或者服务器通信:
socket.recv(buffersize) :接受客户端信或服务器数据,buffersize指定接收数据的大小,单位为字节。
socket.send(data) :发送信息给客户端或服务器,信息必须转换为字节才能发送。
import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) HostPort = ('127.0.0.1',8899) s.bind(HostPort) #绑定地址端口 s.listen(5) #监听最多5个连接请求 while True: print('server socket waiting...') obj,addr = s.accept() #阻塞等待链接,创建新链接对象(obj)和客户端地址(addr) while True: client_data = obj.recv(1024) #通过新链接对象接受数据 print(client_data) obj.send(client_data) #通过新链接对象发送数据
6)传输结束,关闭链接
socket.close() 关闭链接
客户端创建套接字链接:
1)s = socket.socket() 创建socket对象
2)s.connect('127.0.0.1','80') 绑定地址端口链接服务器
3)s.send(data) 发送数据到服务器
4)s.recv(1024) 接收服务器数据
5)s.close() 关闭链接
4、socket套接字对象方法
服务器段套接字:
s.bind() :绑定地址(host,port)到套接字,在AF_INET下,以元组(host,port)的形式表示地址。
s.listen() :开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
s.accept() :被动接受TCP客户端连接,(阻塞式)等待连接的到来
客户端套接字:
s.connect() :主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误
s.connect_ex() :connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数:
s.recv() :接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
s.send() :发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall() :完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
s.recvfrom() :接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
s.sendto() :发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close() :关闭套接字
s.getpeername() :返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)
s.getsockname() :返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value) :设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen]) :返回套接字选项的值。
s.settimeout(timeout) :设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
s.gettimeout() :返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None
s.fileno() :返回套接字的文件描述符
s.setblocking(flag) :如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
s.makefile() :创建一个与该套接字相关连的文件
5、socket实例
1)简单的TCP socket会话
服务器代码:
import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) HostPort = ('127.0.0.1',8898) s.bind(HostPort) #绑定地址端口 s.listen(5) #监听最多5个连接请求 while True: print('server socket waiting...') c,addr = s.accept() #阻塞等待链接,创建套接字c链接和地址信息addr while True: try: client_date = c.recv(1024) #接收客户端数据 if str(client_date,'utf8') == 'quit': c.close() break except Exception: break c.send(client_date) #发送数据给客户端 print('clientINFO:',str(client_date, 'utf8')) #打印数据,默认接收数据为bytes,需转换成str
此处使用try捕捉异常,是在客户端断开链接时,服务器端会抛出异常,为了实现多连接排队链接,必须捕捉异常让程序正常运行,这种现象只在windows系统下存在;在linux下的表现形式又不同,在linux下不会抛出异常,而是正常接收客户端数据而不会退出,只需要判断客户端数据长度,正常退出急可解决问题。
客户端代码:
import socket hostport = ('127.0.0.1',8898) s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建TCP socket s.connect(hostport) #链接套接字 while True: user_input = input('>>>:').strip() s.send(bytes(user_input,'utf8')) #发送数据到套接字 if not len(user_input):continue if user_input == 'quit': s.close() break server_reply = s.recv(1024) #接收套接字数据 print(str(server_reply, 'utf8')) #打印输出
会话结果:
#首先运行服务器代码,然后运行客户端代码链接服务器 #client output: >>>:hello python hello python >>>:socket network programming socket network programming >>>: #server output: server socket waiting... clientINFO: hello python clientINFO: socket network programming
2)简单的UDP socket会话
服务器接收端:
import socket HostPort = ('127.0.0.1',7777) sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #创建UDP套接字 sock.bind(HostPort) #服务器端绑定端口 while True: data,addr = sock.recvfrom(1024) #接收端口数据 print(str(data,'utf8')) #打印数据
客户端发送端:
import socket HostPort = ('127.0.0.1',7777) sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) while True: user_input = input('>>>:') if user_input == 'quit':break sock.sendto(user_input.encode(),HostPort) #指定地址端口发送数据,数据必须encode sock.close()
3)socke实现简单版ssh
服务器代码:
import subprocess import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) HostPort = ('192.168.146.129',8821) s.bind(HostPort) s.listen(5) while True: print('server socket waiting...') c,addr = s.accept() #阻塞等待连接,并创建套接字和地址端口对象 while True: client_date = c.recv(1024) #接收数据 if not client_date:break #判断数据为空则跳出循环 if str(client_date,'utf8') == 'quit': #指定正常退出链接接口 c.close() break strule = client_date.decode() #解码数据 strule_out = subprocess.Popen(strule,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) #交互系统执行命名,并将输出和错误输出到管道 strule_out_read = strule_out.stdout.read() #读取管道数据 if not strule_out_read: #判断命令错误时,数据为错误输出信息 strule_out_read = strule_out.stderr.read() #在发送数据前,先发送数据长度到客户端并确认 coun =bytes("cmd_result_size|%s" %len(strule_out_read),'utf8') #print(coun) c.send(coun) #发送数据长度 client_ack = c.recv(50) #接收客户端确认信息 #print(str(client_ack,'utf8')) if str(client_ack,'utf8') == 'client ready to recv': #确认客户端后开始发送数据 c.send(strule_out_read) #print(str(strule_out_read,'utf8')) #print('clientINFO:',str(client_date, 'utf8'))
客户端代码:
import socket hostport = ('192.168.146.129',8821) s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建TCP socket s.connect(hostport) #链接套接字 while True: user_input = input('>>>:').strip() if not len(user_input):continue if user_input == 'quit': s.send(bytes('quit','utf8')) s.close() break else: s.send(bytes(user_input, 'utf8')) # 发送数据到套接字 server_ack = s.recv(100) #接收数据长度 ack_msg = str(server_ack.decode()).split('|') if ack_msg[0] == "cmd_result_size": #判断是否为数据长度包 ack_size = int(ack_msg[1]) #获取数据长度 s.send('client ready to recv'.encode()) #发送客户端确认信息 tool = 0 count_info = '' while tool < ack_size: #判断接收数据长度是否小于总数据长度,则循环接收数据 server_reply = s.recv(1024) #接收套接字数据 count_info +=str(server_reply.decode()) tool += len(server_reply) else: print(count_info) #正常接收完数据后打印
6、socketserver框架
该socketserver模块简化了编写网络服务器的任务;共有四个基本的具体服务器类:
class socketserver.TCPServer(server_address,RequestHandlerClass,bind_and_activate=True)
class TCPServer(BaseServer): address_family = socket.AF_INET socket_type = socket.SOCK_STREAM request_queue_size = 5 allow_reuse_address = False def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): """Constructor. May be extended, do not override.""" BaseServer.__init__(self, server_address, RequestHandlerClass) self.socket = socket.socket(self.address_family, self.socket_type) if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() raise def server_bind(self): if self.allow_reuse_address: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(self.server_address) self.server_address = self.socket.getsockname() def server_activate(self): self.socket.listen(self.request_queue_size) def server_close(self): self.socket.close() def fileno(self): return self.socket.fileno() def get_request(self): return self.socket.accept() def shutdown_request(self, request): """Called to shutdown and close an individual request.""" try: #explicitly shutdown. socket.close() merely releases #the socket and waits for GC to perform the actual close. request.shutdown(socket.SHUT_WR) except OSError: pass #some platforms may raise ENOTCONN here self.close_request(request) def close_request(self, request): """Called to clean up an individual request.""" request.close()
它使用internet TCP协议,提供连续的数据流,如果bind_and_activate为True,则构造函数会自动尝试调用server_bind()和server_activate()。其他参数会传递给BaseServer基类。
class socketserver.UDPServer(server_address,RequestHandlerClass,bind_and_activate=True)
class UDPServer(TCPServer): """UDP server class.""" allow_reuse_address = False socket_type = socket.SOCK_DGRAM max_packet_size = 8192 def get_request(self): data, client_addr = self.socket.recvfrom(self.max_packet_size) return (data, self.socket), client_addr def server_activate(self): # No need to call listen() for UDP. pass def shutdown_request(self, request): # No need to shutdown anything. self.close_request(request) def close_request(self, request): # No need to close anything. pass
它使用数据报通信,不保证数据可靠性。
class socketserver.UnixStreamServer(server_address,RequestHandlerClass,bind_and_activate=True)
class socketserver.UnixDatagramServer(server_address,RequestHandlerClass,bind_and_activate=True)
这两个很少用,与TCP和UDP类相似,但只使用于UNIX域套接字,只能在Unix平台上使用。
这四个类都是同步处理请求,即完成一个请求后才开始接收下一个请求,会造成数据处理缓慢;解决方案是创建一个单独的进程或线程来处理每一个请求;可以使用ForkingMixIn和ThreadingMinIn混合型类来用于异步处理数据。
创建一个服务器需要几个步骤。首先,你必须创建一个请求处理程序类,通过继承这个BaseRequestHandler
类并覆盖它的handle()
方法; 这个方法将处理传入的请求。其次,您必须实例化其中一个服务器类,并将其传递给服务器的地址和请求处理程序类。然后调用服务器对象的 handle_request()
or serve_forever()
方法来处理一个或多个请求。最后,调用server_close()
关闭套接字。
socketserver类的继承图:
+ ------------ + | BaseServer | + ------------ + | v + ----------- + + ------------------ + | TCPServer | -------> | UnixStreamServer | + ----------- + + ------------------ + | v + ----------- + + -------------------- + | UDPServer | -------> | UnixDatagramServer | + ----------- + + -------------------- +
请注意,UnixDatagramServer
源自而UDPServer
不是来自 UnixStreamServer
- 一个IP和一个Unix流服务器之间的唯一区别是地址系列,这在两个Unix服务器类中简单地重复。
class socketserver.ForkingMixIn 进程类,它实现了异步多进程多客户端链接
class ForkingMixIn: """Mix-in class to handle each request in a new process.""" timeout = 300 active_children = None max_children = 40 def collect_children(self): """Internal routine to wait for children that have exited.""" if self.active_children is None: return while len(self.active_children) >= self.max_children: try: pid, _ = os.waitpid(-1, 0) self.active_children.discard(pid) except ChildProcessError: # we don't have any children, we're done self.active_children.clear() except OSError: break # Now reap all defunct children. for pid in self.active_children.copy(): try: pid, _ = os.waitpid(pid, os.WNOHANG) # if the child hasn't exited yet, pid will be 0 and ignored by # discard() below self.active_children.discard(pid) except ChildProcessError: # someone else reaped it self.active_children.discard(pid) except OSError: pass def handle_timeout(self): self.collect_children() def service_actions(self): self.collect_children() def process_request(self, request, client_address): """Fork a new subprocess to process the request.""" pid = os.fork() if pid: # Parent process if self.active_children is None: self.active_children = set() self.active_children.add(pid) self.close_request(request) return else: # Child process. # This must never return, hence os._exit()! try: self.finish_request(request, client_address) self.shutdown_request(request) os._exit(0) except: try: self.handle_error(request, client_address) self.shutdown_request(request) finally: os._exit(1)
class socketserver.ThreadingMixIn 线程类,它实现了异步多线程多客户端链接
class ThreadingMixIn: """Mix-in class to handle each request in a new thread.""" # Decides how threads will act upon termination of the # main process daemon_threads = False def process_request_thread(self, request, client_address): """Same as in BaseServer but as a thread. In addition, exception handling is done here. """ try: self.finish_request(request, client_address) self.shutdown_request(request) except: self.handle_error(request, client_address) self.shutdown_request(request) def process_request(self, request, client_address): """Start a new thread to process the request.""" t = threading.Thread(target = self.process_request_thread, args = (request, client_address)) t.daemon = self.daemon_threads t.start()
每种服务器的进程版和线程版都可以使用这两个混合类来创建,如创建一个ThreadingUDPServer:
class ThreadingUDPServer(ThreadingMixIn,UDPServer): pass
混合类优先,因为它覆盖了一个定义在UDPServer中的方法,设置各种属性也会改变底层服务器机制的行为。
TCP多进程:class socketserver.ForkingTCPServer
UDP多进程:class socketserver.ForkingUDPServer
TCP多线程:class socketserver.
ThreadingTCPServer
UDP多线程:class socketserver.
ThreadingUDPServer
它们实际上继承了ForkingMixIn或ThreadingMixIn类来处理异步通信,然后调用TCPServer服务器来实例化一个链接。
class ForkingUDPServer(ForkingMixIn, UDPServer): pass class ForkingTCPServer(ForkingMixIn, TCPServer): pass class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
这些类是使用混合类预定义的。
要实现一个服务,你必须派生一个类BaseRequestHandler
并重新定义它的handle()
方法。然后,您可以通过将其中一个服务器类与请求处理程序类相结合来运行各种版本的服务。对于数据报或流服务,请求处理程序类必须不同。这可以通过使用处理程序子类StreamRequestHandler
或隐藏 DatagramRequestHandler
。
1)服务器对象
class socketserver.
BaseServer
(server_address,RequestHandlerClass )
这是模块中所有服务器对象的超类。它定义了下面给出的接口,但不实现大多数在子类中完成的方法。两个参数被存储在相应的 server_address
和RequestHandlerClass
属性。
class BaseServer: timeout = None def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False def server_activate(self): pass def serve_forever(self, poll_interval=0.5): self.__is_shut_down.clear() try: with _ServerSelector() as selector: selector.register(self, selectors.EVENT_READ) while not self.__shutdown_request: ready = selector.select(poll_interval) if ready: self._handle_request_noblock() self.service_actions() finally: self.__shutdown_request = False self.__is_shut_down.set() def shutdown(self): self.__shutdown_request = True self.__is_shut_down.wait() def service_actions(self): pass def handle_request(self): timeout = self.socket.gettimeout() if timeout is None: timeout = self.timeout elif self.timeout is not None: timeout = min(timeout, self.timeout) if timeout is not None: deadline = time() + timeout with _ServerSelector() as selector: selector.register(self, selectors.EVENT_READ) while True: ready = selector.select(timeout) if ready: return self._handle_request_noblock() else: if timeout is not None: timeout = deadline - time() if timeout < 0: return self.handle_timeout() def _handle_request_noblock(self): try: request, client_address = self.get_request() except OSError: return if self.verify_request(request, client_address): try: self.process_request(request, client_address) except: self.handle_error(request, client_address) self.shutdown_request(request) else: self.shutdown_request(request) def handle_timeout(self): pass def verify_request(self, request, client_address): return True def process_request(self, request, client_address): self.finish_request(request, client_address) self.shutdown_request(request) def server_close(self): pass def finish_request(self, request, client_address): self.RequestHandlerClass(request, client_address, self) def shutdown_request(self, request): self.close_request(request) def close_request(self, request): pass def handle_error(self, request, client_address): print('-'*40) print('Exception happened during processing of request from', end=' ') print(client_address) import traceback traceback.print_exc() # XXX But this goes to stderr! print('-'*40)
fileno
()-
返回服务器正在侦听的套接字的整数文件描述符。这个功能通常被传递给
selectors
,允许在同一个进程中监视多个服务器。
handle_request
()-
处理一个请求。这个函数调用下面的方法依次是:
get_request()
,verify_request()
,和process_request()
。如果handle()
处理程序类的用户提供的 方法引发异常,handle_error()
则将调用服务器的方法。如果在timeout
几秒钟内没有收到请求,handle_timeout()
将会被调用并handle_request()
返回。
serve_forever
(poll_interval = 0.5 )-
处理请求,直到一个明确的
shutdown()
请求。轮询关闭每个poll_interval秒。忽略该timeout
属性。它还会调用service_actions()
子类或mixin可能用于提供给定服务的特定操作。例如,这个ForkingMixIn
类service_actions()
用来清理僵尸子进程。在版本3.3中更改:添加
service_actions
了对该serve_forever
方法的调用。
service_actions
()-
这在
serve_forever()
循环中被调用。这个方法可以被子类或mixin类覆盖,以执行特定于给定服务的操作,例如清理操作。3.3版本中的新功能
shutdown
()-
告诉
serve_forever()
循环停止并等待,直到它结束。
server_close
()-
清理服务器。可能会被覆盖。
address_family
-
服务器套接字所属的协议族。常见的例子是
socket.AF_INET
和socket.AF_UNIX
。
RequestHandlerClass
-
用户提供的请求处理程序类; 这个类的一个实例是为每个请求创建的。
server_address
-
服务器正在侦听的地址。地址格式因协议族而异,
socket
有关详细信息,请参阅该模块的文档。对于Internet协议,这是一个元组,其中包含一个给出地址的字符串和一个整数端口号:例如。('127.0.0.1',80)
socket
-
服务器将侦听传入请求的套接字对象。
服务器类支持以下类变量:
allow_reuse_address
-
服务器是否允许重用地址。这个默认为
False
,可以在子类中设置来更改策略。
request_queue_size
-
请求队列的大小。如果处理单个请求需要很长时间,则在服务器繁忙时到达的任何请求都被放入队列中,直至
request_queue_size
请求。一旦队列已满,来自客户端的进一步请求将会得到“连接被拒绝”错误。默认值通常是5,但这可以由子类覆盖。
socket_type
-
服务器使用的套接字的类型;
socket.SOCK_STREAM
并且socket.SOCK_DGRAM
是两个共同的价值。
timeout
-
超时持续时间,以秒为单位,或者
None
如果不需要超时。如果handle_request()
在超时期限内没有收到传入的请求,handle_timeout()
则调用该方法。
有许多服务器方法可以被基类服务器类的子类覆盖,比如TCPServer
:这些方法对服务器对象的外部用户没有用处。
finish_request
()-
实际上通过实例化
RequestHandlerClass
和调用它的handle()
方法来处理请求。
get_request
()-
必须接受来自套接字的请求,并返回包含 要用于与客户端通信的新套接字对象的2元组以及客户端的地址。
handle_error
(request,client_address )-
如果实例的
handle()
方法RequestHandlerClass
引发异常,则调用此函数。默认操作是将回溯打印到标准输出,并继续处理更多的请求。
handle_timeout
()-
当
timeout
属性被设置为一个非None
超时值时,这个函数被调用,超时时间已经过去,没有收到请求。派生服务器的默认动作是收集退出的任何子进程的状态,而在线程服务器中,这个方法什么也不做。
process_request
(request,client_address )-
调用
finish_request()
来创建一个实例RequestHandlerClass
。如果需要,这个函数可以创建一个新的进程或线程来处理请求; 在ForkingMixIn
和ThreadingMixIn
班做到这一点。
server_activate
()-
由服务器的构造函数调用以激活服务器。TCP服务器的默认行为只是
listen()
在服务器的套接字上调用。可能会被覆盖。
server_bind
()-
由服务器的构造函数调用,将套接字绑定到所需的地址。可能会被覆盖。
verify_request
(request,client_address )-
必须返回一个布尔值; 如果值是
True
,请求将被处理,如果是False
,请求将被拒绝。这个函数可以被覆盖来实现服务器的访问控制。默认的实现总是返回True
。
2)请求处理对象
class socketserver.
BaseRequestHandler
class BaseRequestHandler: def __init__(self, request, client_address, server): self.request = request self.client_address = client_address self.server = server self.setup() try: self.handle() finally: self.finish() def setup(self): pass def handle(self): pass def finish(self): pass
-
这是所有请求处理程序对象的超类。它定义了下面给出的接口。一个具体的请求处理子类必须定义一个新的
handle()
方法,并且可以覆盖任何其他的方法。为每个请求创建一个新的子类实例。setup
()-
在
handle()
方法执行任何所需的初始化操作之前调用。默认实现什么都不做。
handle
()-
该功能必须完成所有需要的工作来处理请求。默认实现什么都不做。有几个实例属性可用; 该请求可用于
self.request
; 客户地址为self.client_address
; 和服务器实例一样self.server
,以防需要访问每个服务器的信息。self.request
数据报或流服务的类型是不同的。对于流服务,self.request
是一个套接字对象; 对于数据报服务来说,self.request
是一对字符串和套接字。
class socketserver.
StreamRequestHandler,继承了BaseRequestHandler类,它只是重写了setup和finish方法。
class StreamRequestHandler(BaseRequestHandler): rbufsize = -1 wbufsize = 0 # A timeout to apply to the request socket, if not None. timeout = None # Disable nagle algorithm for this socket, if True. # Use only when wbufsize != 0, to avoid small packets. disable_nagle_algorithm = False def setup(self): self.connection = self.request if self.timeout is not None: self.connection.settimeout(self.timeout) if self.disable_nagle_algorithm: self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) self.rfile = self.connection.makefile('rb', self.rbufsize) self.wfile = self.connection.makefile('wb', self.wbufsize) def finish(self): if not self.wfile.closed: try: self.wfile.flush() except socket.error: # A final socket error may have occurred here, such as # the local error ECONNABORTED. pass self.wfile.close() self.rfile.close()
class socketserver.
DatagramRequestHandler,继承了BaseRequestHandler类,它只是重写了setup和finish方法。
class DatagramRequestHandler(BaseRequestHandler): def setup(self): from io import BytesIO self.packet, self.socket = self.request self.rfile = BytesIO(self.packet) self.wfile = BytesIO() def finish(self): self.socket.sendto(self.wfile.getvalue(), self.client_address)
-
这些
BaseRequestHandler
子类重写setup()
和finish()
方法,提供self.rfile
和self.wfile
属性。该self.rfile
和self.wfile
属性可以被读取或写入,分别获得请求的数据或者数据返回给客户端。
3)socketserver实例
- socketserver.TCPServer示例:实现同步多并发链接,一个进程只能在处理完一个链接后才能处理第二个链接。
服务器端:
import socketserver class MyTCPHandler(socketserver.BaseRequestHandler): ‘‘‘服务器处理请求类程序,每连接一次服务器实例化一次,并重写handle()方法来实现通信客户端’’’ def handle(self): print('new connection:',self.client_address) #获取客户端地址和端口 while True: self.data = self.request.recv(1024).strip() #request获取连接客户端的套接字,recv接收客户端数据 print('{}AddrAndPort:'.format(self.client_address)) print(self.data) self.request.sendall(self.data.upper()) #发送数据到客户端并转换大小写 if __name__ == "__main__": HOSTPORT = ('127.0.0.1',9988) #创建服务器,绑定地址端口和类 server = socketserver.TCPServer(HOSTPORT,MyTCPHandler) #运行服务器,相对于守护进程,直到按ctrl-c来结束程序 server.serve_forever()
- 使用预定义包装的类ThreadingTCPServer实现异步并发链接:
import socketserver class Mysocket(socketserver.StreamRequestHandler): #重写setup和finish方法 def handle(self): #处理链接 print('client:',self.client_address) while 1: data = self.request.recv(1024) self.request.send(data) print(data.decode()) if __name__ == '__main__': AddrPort = ('127.0.0.1',7899) server = socketserver.ThreadingTCPServer(AddrPort,Mysocket) #多线程创建实例链接 server.serve_forever() #循环接收链接
- windows系统下的一个bug:
import socketserver class Mysocket(socketserver.BaseRequestHandler): def handle(self): print('client:',self.client_address) while 1: data = self.request.recv(1024) self.request.send(data) print(data.decode()) if __name__ == '__main__': AddrPort = ('127.0.0.1',7899) #如果此处使用ForkingTCPServer类来创建多进程的实例化链接,在windows系统下会报错 server = socketserver.ForkingTCPServer(AddrPort,Mysocket) server.serve_forever() #链接后异常: File "Z:\Program Files\Python35\lib\socketserver.py", line 588, in process_request pid = os.fork() AttributeError: module 'os' has no attribute 'fork' #原因是: `os.fork` isn't available on Windows os.fork在windows上是不可用的。
- 简单版FTPServer:
#服务器代码: #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/1/12 11:02 # @Author : Py.qi # @File : SOCKSERVER_01.py # @Software: PyCharm from socketserver import ThreadingTCPServer,StreamRequestHandler import os,json host_path = os.getcwd() class MyHandler(StreamRequestHandler): def handle(self): print('clientaddr:',self.client_address) while True: try: data = self.request.recv(1024) except Exception: break if data.decode() == 'ls': #简单查看目录下的文件 lsdir = os.listdir(os.getcwd()) pi = json.dumps(lsdir) self.request.send(pi.encode()) if len(data.decode().split()) >= 2: action, filename = data.decode().split() print(action,filename) filename_cd = host_path + '\\' + filename if action == 'get': print(action) self.getfile(filename) print('getfiel..end') elif action == 'upt': #上传交给类方法处理 print(action) self.uptfile(filename) print('upt---end') elif action == 'cd': #简单cd命令 os.chdir(filename_cd) lsdir1 = os.listdir(filename_cd) p2 = json.dumps(lsdir1) self.request.send(p2.encode()) def getfile(self,filename): #文件下载 filename_path = host_path + '\\' + filename print(filename_path) with open(filename_path,'rb') as f: filedata = f.read() self.request.send(filedata) self.request.close() def uptfile(self,filename): #文件上传 self.request.send(bytes('ok','utf8')) filename_uptfile = host_path + '\\' + filename with open(filename_uptfile,'wb') as f1: uptdata = self.request.recv(1024) f1.write(uptdata) self.request.close() if __name__ == "__main__": hostprot = ('127.0.0.1',5556) server = ThreadingTCPServer(hostprot,MyHandler) print('connection to who...') server.serve_forever() #客户端代码: #!/usr/bin/env python #coding:utf8 #file:Administrator #time:20180104 import socket,os,json host_path = r'Z:\\' hostport = ('127.0.0.1',5556) s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建TCP socket #s.bind(hostport) s.connect(hostport) #链接套接字 while True: user_input = input('>>>:').strip() s.send(user_input.encode()) #发送数据到套接字 if not len(user_input):continue if user_input == 'quit': s.close() break server_reply = s.recv(1024) #接收套接字数据 if not server_reply:break if len(user_input.split()) > 1: if user_input.split()[0] == 'get': filename = user_input.split()[1] filename_path = host_path + filename with open(filename_path,'wb') as f: print('----') f.write(server_reply) if user_input.split()[0] == 'upt': #redy = s.recv(1024) print(server_reply) filename_u = user_input.split()[1] filename_u_path = host_path + filename_u with open(filename_u_path,'rb') as f1: upt_data = f1.read() print(upt_data) s.send(upt_data) elif len(user_input.split()) == 1: lsdata = server_reply.decode() print(lsdata)