python~socket编程学习总结 (三)

本文介绍SocketServer模块在Python中如何实现并发处理,包括进程模式、线程模式和线程池方式,以及如何使用SocketServer传输文件。通过示例代码详细解释了每种模式的实现步骤和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SockerServer

socket编程在模块创建时无法进行多进程得处理,需要大量请求时,请求就会阻塞在队列中,甚至发生请求丢弃;SocketServer可以实现功能。

SocketServer有4个类:TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer。这4个类是同步进行处理的,另外通过ForkingMixIn和ThreadingMixIn类来支持异步。

ForkingMixIn和ThreadingMixIn两个混合类,它们都提供Server类中process_request方法的新实现,前者在处理每次用户连接的时候都会开启新的进程,而后者会开启新的线程;从而实现并发处理

实现步骤

  1. 创建一个请求处理的类,是BaseRequestHandler的子类,并重写其handle方法
  2. 实例化一个服务器类,传入服务器的地址和请求处理的程序类
  3. 调用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 客户端给服务端发送数据)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值