SockerServer
socket编程在模块创建时无法进行多进程得处理,需要大量请求时,请求就会阻塞在队列中,甚至发生请求丢弃;SocketServer可以实现功能。
SocketServer有4个类:TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer。这4个类是同步进行处理的,另外通过ForkingMixIn和ThreadingMixIn类来支持异步。
ForkingMixIn和ThreadingMixIn两个混合类,它们都提供Server类中process_request方法的新实现,前者在处理每次用户连接的时候都会开启新的进程,而后者会开启新的线程;从而实现并发处理
实现步骤
- 创建一个请求处理的类,是BaseRequestHandler的子类,并重写其handle方法
- 实例化一个服务器类,传入服务器的地址和请求处理的程序类
- 调用handle_request()一般是调用其他事件循环或者使用select或serve_forever
集成ThreadingMixIn类时需要处理异常关闭。daemon_threads指示服务器是否要等待线程终止,要是线程互相独立,必须要设置为True,默认是False。
SocketServer模块的Fork方式(进程模式)linux 下执行
多个连接同时到达服务器端的时候,每个连接主进程都生成一个子进程专门用来处理此连接,而主进程则依旧保持在侦听状态。因主进程和子进程是同时进行的,所以不会阻塞新的连接。但由于生成进程消耗的资源比较大,这种处理方式在有很多连接的时候会带来性能问题。
代码示例:
服务端程序
# encoding=utf-8
from socketserver import TCPServer, ForkingMixIn, StreamRequestHandler
import time
class Server(ForkingMixIn, TCPServer): # 自定义Server类
pass
class MyHandler(StreamRequestHandler):
def handle(self): # 重写父类的handle函数
addr = self.request.getpeername()
print(u'得到得请求是从客户端:', addr) # 打印客户端地址
data = self.rfile.readline().strip() # 客户端发送的信息必须带有回车,否则会一直等待客户端继续发送数据
print(data)
time.sleep(5) # 休眠5秒钟
if data:
self.wfile.write(u'这是从服务端进程中发出得消息'.encode("utf-8")) # 给客户端发送信息
host = ''
port = 8019
server = Server((host, port), MyHandler)
server.serve_forever() # 开始侦听并处理连接,循环处理
客户端程序
import socket
import time
if __name__ == '__main__':
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 8019))
time.sleep(2)
sock.send('ls -al /home/yuzhigang'.encode("utf-8")+"\n".encode("utf-8"))
print (sock.recv(1024).decode("utf-8"))
sock.close()
SocketServer模块的Fork方式(线程模式)linux 下执行
线程是一种轻量级的进程,比Fork消耗的资源更少,而且主线程和子线程之间具有相同的地址空间,处理效率高。但大量的使用线程会带来线程之间的数据同步问题,处理不好可能使服务程序失去响应。上述与Fork方式中代码基本相同,仅仅是采用的ThreadingMixIn类不同。
代码示例:
服务端程序
# encoding=utf-8
from socketserver import TCPServer, ThreadingMixIn, StreamRequestHandler
import time
class Server(ThreadingMixIn, TCPServer): # 自定义Server类
pass
class MyHandler(StreamRequestHandler):
def handle(self):
addr = self.request.getpeername()
print(u'得到得请求是从客户端:', addr) # 打印客户端地址
data = self.rfile.readline().strip() # 客户端发送的信息必须带有回车,否则会一直等待客户端继续发送数据
print(data)
time.sleep(1) # 休眠1秒钟
if data:
self.wfile.write(u'这是从服务端线程中发出得消息'.encode("utf-8")) # 给客户端发送信息
host = ''
port = 8001
server = Server((host, port), MyHandler)
server.serve_forever() # 开始侦听并处理连接
客户端程序
import socket
import time
if __name__ == '__main__':
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 8001))
time.sleep(2)
sock.send('ls -al /home/wxh'.encode("utf-8")+"\n".encode("utf-8"))
print (sock.recv(1024).decode("utf-8"))
sock.close()
SocketServer模块的Threading线程池方式
代码示例:
服务端程序
# encoding=utf-8
import socketserver
import threading
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
while True:
self.data = self.request.recv(1024).strip()
cur_thread = threading.current_thread()
print(cur_thread)
if not self.data:
print(u"客户端:%s 退出!" % self.client_address[0])
break
print(u"%s 内容:%s" % (self.client_address[0], self.data.decode("utf-8")))
self.request.sendall(self.data.upper())
if __name__ == "__main__":
HOST, PORT = "", 8001
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
server.serve_forever()
客户端程序
import socket
import time
if __name__ == '__main__':
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 8001))
time.sleep(2)
sock.send('ls -al /home/wxh'.encode("utf-8")+"\n".encode("utf-8"))
print(sock.recv(1024).decode("utf-8"))
sock.close()
使用socketserver 传送一个文件
代码示例:
服务端程序
#-*- coding: UTF-8 -*-
import socket,time,socketserver,struct,os
import time
host='127.0.0.1'
port=12302
ADDR=(host,port)
class MyRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
print(':', self.client_address)
while True:
fileinfo_size=struct.calcsize('128sl') #定义文件信息。128s表示文件名为128bytes长,l表示一个int或log文件类型,在此为文件大小
self.buf = self.request.recv(fileinfo_size)
if self.buf: #如果不加这个if,第一个文件传输完成后会自动走到下一句
self.filename,self.filesize =struct.unpack('128sl',self.buf) #根据128sl解包文件信息,与client端的打包规则相同
print(u'文件内容大小: ',self.filesize,u'文件名字大小: ',len(self.filename)) #文件名长度为128,大于文件名实际长度
self.filenewname = os.path.join('d:\\',('new_%s_' % (time.time())+ self.filename.decode("utf-8")).strip('\00')) #使用strip()删除打包时附加的多余空字符
print(self.filenewname,type(self.filenewname))
recvd_size = 0 #定义接收了的文件大小
file = open(self.filenewname,'wb')
print(u'开始接收...')
while not recvd_size == self.filesize:
if self.filesize - recvd_size > 1024:
rdata = self.request.recv(1024)
recvd_size += len(rdata)
else:
rdata = self.request.recv(self.filesize - recvd_size)
recvd_size = self.filesize
file.write(rdata)
file.close()
print(u'接收完毕')
#self.request.close()
tcpServ = socketserver.ThreadingTCPServer(ADDR, MyRequestHandler)
print(u'正在监听状态...' )
tcpServ.serve_forever()
客户端程序
# -*- coding: UTF-8 -*-
import socket, os, struct
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 12302))
while True:
filepath = input(u'文件绝对路径:\r\n')
if os.path.isfile(filepath):
#fileinfo_size = struct.calcsize('128sl') # 定义打包规则
# 定义文件头信息,包含文件名和文件大小
fhead = struct.pack('128sl', os.path.basename(filepath).encode("utf-8"), os.stat(filepath).st_size)
s.send(fhead)
print (u'客户端传输文件绝对路径: ', filepath)
# with open(filepath,'rb') as fo: 这样发送文件有问题,发送完成后还会发一些东西过去
fo = open(filepath, 'rb')
while True:
filedata = fo.read(1024)
if not filedata:
break
s.send(filedata)
fo.close()
print (u'传输完成...')
# s.close()
Select(单线程实现多线程效果核心)
代码示例:
服务端程序
#encoding=utf-8
import socket
import select
s = socket.socket()
s.bind(('127.0.0.1', 8889))
s.listen(5)
r_list = [s, ]
num = 0
while True:
print(u"开始进入监听状态...")
rl, wl, error = select.select(r_list, [], [], 10)
# 第一次执行循环体:客户端建立的连接的时候,rl和r_list分别是[s,]和[s,]执行连接之后,r_list变为了[s,conn]
# 第二次执行循环体:有需要读取的小时时候,rl和r_list分别是[conn,]和[s,conn],执行else逻辑
# 。。。。。
#第n次执行循环体:rl和r_list分别是[conn,]和[s,conn],执行else逻辑
#简单来说rl会在建立连接后,添加socket对象,但是以后就不会在添加socket对象了,
#因为建立连接的事件只会被select监听到一次。
#然后select就一直监听已经建立的连接对象是否有数据发来了。当有异常的时候,会把链接对象从rl中删除掉。
num += 1
print(u'执行次数%s'% num)
print("rl's length is %s" % len(rl))
print("r_list length %s" % len(r_list))
print([i for i in rl])
for fd in rl:
if fd == s:
conn, addr = fd.accept()
r_list.append(conn)
msg = conn.recv(200)
conn.sendall(('first----%s' % msg.upper()).encode("utf-8"))
else:
try:
msg = fd.recv(200)
fd.sendall(msg.upper())
except ConnectionAbortedError:
r_list.remove(fd)
s.close()
客户端程序
import socket
flag = 1
s = socket.socket()
s.connect(('127.0.0.1', 8889))
while flag:
input_msg = input('input>>>')
if input_msg == '0':
break
s.sendall(input_msg.encode())
msg = s.recv(1024)
print(msg.decode())
s.close()
思路:可以理解为定时任务,如果有数据传过来,我就处理,没有我就等着
1)初始状态
死循环:rl, wl, error = select.select(r_list, [], [], 10)
每隔10秒轮训一下; r_list中的对象内容:[s] ; rl是空列表
2)有一个客户端进行连接了:
r_list中的对象内容:[s];rl:[s]
这样就会 执行if逻辑,服务端和客户端建立了一个连接;这个链接对象叫做:conn 。此时r_list中的对象内容变为:[s,conn]
连接建立了,但是没有任何数据时,r_list中的对象内容:[s,conn];rl:[]
3)有一个客户端发了一个消息过来:
r_list中的对象内容:[s,conn];rl:[conn]
就会执行了else逻辑,将客户端发来的消息转换为大写,返还给客户端
4)客户端处于连接状态,但是停止发送数据了。
r_list中的对象内容:[s,conn];rl:[]
5)客户端断开链接:
r_list中的对象内容:[s];rl:[]
select的机制就是监听放在r_list中对象是否有事件发生
事件只有2种(1:建立连接 2 客户端给服务端发送数据)