• Python学习之路14——Socket


    一、Socket

        socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求。

      socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。

      socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)

      socket和file的区别:

      • file模块是针对某个指定文件进行【打开】【读写】【关闭】
      • socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】

      1、信息交互的程序

        服务器端:

     1 #!/user/bin/env ptyhon
     2 # -*- coding:utf-8 -*-
     3 # Author: VisonWong
     4 
     5 
     6 # server端
     7 
     8 import socket
     9 
    10 ip_port = ("127.0.0.1", 9999)  # 服务器端ip和服务端口  127.0.0.1本机ip地址
    11 server = socket.socket()  # 创建server
    12 # server = <socket.socket fd=344, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
    13 
    14 server.bind(ip_port)  # 绑定地址ip
    15 # server = <socket.socket fd=344, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999)>
    16 
    17 server.listen(5)  # 开始监听,允许5个客户端排队
    18 conn, addr = server.accept()  # 等待连接
    19 # conn就是客户端连接过来,服务器端为其生成的一个连接实例
    20 # addr就是客户端连接过来的地址
    21 # conn = <socket.socket fd=340, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999),
    22 # addr = ('127.0.0.1', 5117)
    23 print(addr)
    24 
    25 
    26 client_data = conn.recv(1024)  # 收取信息,1024字节
    27 # client_data = b'hello'
    28 conn.sendall(client_data.upper())  # 发送数据
    29 
    30 conn.close()  # 关闭程序

         客户端:

     1 #!/user/bin/env ptyhon
     2 # -*- coding:utf-8 -*-
     3 # Author: VisonWong
     4 
     5 
     6 #客户端
     7 # client端
     8 
     9 import socket
    10 
    11 ip_port = ("127.0.0.1", 9999)  # 要连接的服务器IP和端口
    12 client = socket.socket()  # 创建client
    13 
    14 client.connect(ip_port)  # 连接服务器端
    15 info = "hello"
    16 
    17 client.sendall(info.encode("utf-8"))  # 发送数据包,把str转换为bytes类型
    18 server_data = client.recv(1024)  # 收取数据包
    19 
    20 print(server_data.decode("utf-8"))
    21 
    22 client.close()

        注:如果客户端发送空字符给服务端,客户端发送没有问题,但是服务器是不会接受空字符的,服务器依然会停留在接受状态,程序会一直卡着。

       2、功能介绍

         server = socket.socket()

    参数一:地址簇
    
      socket.AF_INET IPv4(默认)
      socket.AF_INET6 IPv6
    
      socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
    
    参数二:类型
    
      socket.SOCK_STREAM  流式socket , for TCP (默认)
      socket.SOCK_DGRAM   数据报式socket , for UDP
    
      socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
      socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
      socket.SOCK_SEQPACKET 可靠的连续数据包服务
    
    参数三:协议
    
      0  (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议
    详情
     1 # 服务端
     2 import socket
     3 ip_port = ('127.0.0.1',9999)
     4 sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
     5 sk.bind(ip_port)
     6 
     7 while True:
     8     data,(host,port) = sk.recvfrom(1024)
     9     print(data,host,port)
    10     sk.sendto(bytes('ok', encoding='utf-8'), (host,port))
    11 
    12 
    13 #客户端
    14 import socket
    15 ip_port = ('127.0.0.1',9999)
    16 
    17 sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
    18 while True:
    19     inp = input('数据:').strip()
    20     if inp == 'exit':
    21         break
    22     sk.sendto(bytes(inp, encoding='utf-8'),ip_port)
    23     data = sk.recvfrom(1024)
    24     print(data)
    25 
    26 sk.close()
    UDP Demo

        ② server.bind(address)

          server.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。

          在AF_INET(IPV4)下,以元组(host,port)的形式表示地址。

        ③ server.listen(backlog)

          开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。

            backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5,这个值不能无限大,因为要在内核中维护连接队列。

        ④ server.setblocking(bool)

          是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。

        ⑤ conn,addr = server.accept() 

           接受连接并返回(conn,address)。

           其中conn是新的套接字对象,可以用来接收和发送数据;address是连接客户端的地址。

           接收TCP 客户的连接(阻塞式)等待连接的到来。

        ⑥ client.connect(address)

           连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

        ⑦ client.connect_ex(address)

           同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061。

        ⑧ client.close()

          关闭套接字。

        ⑨ client.recv(bufsize[,flag])

         接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。

        ⑩ client.recvfrom(bufsize[.flag])

         与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

        ⑪ server.send(string[,flag])

         将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。

        ⑫ server.sendall(string[,flag])  

         将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常;

                 内部通过递归调用send,将所有内容发送出去。

        ⑬ server.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()

         套接字的文件描述符

       3、聊天机器人

         server端

     1 #!/user/bin/env ptyhon
     2 # -*- coding:utf-8 -*-
     3 # Author: VisonWong
     4 
     5 
     6 #server
     7 
     8 import socket
     9 
    10 ip_port = ("127.0.0.1",8888)
    11 server = socket.socket()
    12 server.bind(ip_port)
    13 server.listen(5)
    14 
    15 while True:
    16     print("等待连接........")
    17     conn, addr = server.accept()     #等电话进来   子循环break后,可以接受下一个客户端
    18     print("接入一个新连接:",addr)
    19     while True:                     #建立子循环,可以接受当前客户端多次发送的消息
    20         try:
    21             data = conn.recv(1024).decode("utf-8")
    22             # if not data:
    23             #     print("你不和我聊天了吗?")
    24             #     break                      #linux下可以判断收到数据为空,代表客户端断开
    25             if data == "exit":
    26                 conn.sendall("exit".encode("utf-8"))
    27                 break
    28             elif data == "0":
    29                 conn.sendall("汪星人星球正要进攻地球。。。。".encode("utf-8"))
    30             else:
    31                 print(data)
    32                 conn.sendall("继续发送电波".encode("utf-8"))
    33         except ConnectionResetError as e:                #windows下需抓捕ConnectionResetError,判断跳出
    34             print("ConnectionResetErrorL:",e)
    35             break
    36     conn.close()

        client端

     1 #!/user/bin/env ptyhon
     2 # -*- coding:utf-8 -*-
     3 # Author: VisonWong
     4 
     5 
     6 #client
     7 
     8 import socket
     9 
    10 ip_port = ("127.0.0.1",8888)
    11 client = socket.socket()
    12 client.connect(ip_port)
    13 
    14 while True:
    15     info = input("->>>").strip()
    16     # send()或者sendall()函数不能发空,负责程序会陷入死循环
    17     if len(info) == 0:
    18         continue
    19     client.sendall(info.encode("utf-8"))
    20     data = client.recv(1024).decode("utf-8")
    21     if data == "exit":
    22         break
    23     else:
    24         print(data)
    25 client.close()

        注:1、客户端断开,为保证服务器端正常运行,windows下需抓捕ConnectionResetError,判断跳出。

          2、send()或sendall()函数不能发空,负责程序会卡住。

       4、ssh程序

        server端。

     1 #!/user/bin/env ptyhon
     2 # -*- coding:utf-8 -*-
     3 # Author: VisonWong
     4 
     5 #ssh server
     6 
     7 import socket,os
     8 
     9 ip_port = ("127.0.0.1",9999)
    10 server = socket.socket()
    11 server.bind(ip_port)
    12 server.listen(5)
    13 
    14 while True:
    15     conn,add = server.accept()
    16     while True:
    17         try:
    18             client_data = conn.recv(1024)
    19             recv_data = client_data.decode("utf-8")
    20             if recv_data == "exit":
    21                 break
    22             send_data = os.popen(recv_data).read()
    23             if not send_data:
    24                 conn.sendall(client_data+"命令不存在".encode("utf-8"))
    25             else:
    26                 conn.sendall(send_data.encode("utf-8"))
    27         except ConnectionResetError as e:
    28             print("ConnectionResetErrorL:",e)
    29             break
    30     conn.close()

           client端。

     1 #!/user/bin/env ptyhon
     2 # -*- coding:utf-8 -*-
     3 # Author: VisonWong
     4 
     5 #ssh client
     6 
     7 import socket
     8 
     9 ip_port = ("127.0.0.1",9999)
    10 client = socket.socket()
    11 client.connect(ip_port)
    12 
    13 while True:
    14     info = input("->>").strip()
    15     if not info:
    16         continue
    17     client.sendall(info.encode("utf-8"))
    18     if info == "exit":
    19         break
    20     server_data = client.recv(1024)
    21     print(server_data.decode("utf-8"))
    22 
    23 client.close()
    24 
    25 
    26 
    27 # ->>dir
    28 #  驱动器 E 中的卷是 文档
    29 #  卷的序列号是 0000-5431
    30 # 
    31 #  E:PythonPythonLearingweb 的目录
    32 # 
    33 # 2018/06/09  19:37    <DIR>          .
    34 # 2018/06/09  19:37    <DIR>          ..
    35 # 2018/06/09  19:19               530 chat_robot_client.py
    36 # 2018/06/09  19:16             1,313 chat_robot_server.py
    37 # 2018/06/08  14:12               489 socket_client.py
    38 # 2018/06/09  19:28               444 socket_client_ssh.py
    39 # 2018/05/25  13:42             1,067 socket_server.py
    40 # 2018/06/08  14:13             1,092 socket_server1.py
    41 # 2018/06/09  19:37               799 socket_server_ssh.py
    42 #                7 个文件          5,734 字节
    43 #                2 个目录 53,847,003,136 可用字节
    44 # 
    45 # ->>cd
    46 # E:PythonPythonLearingweb
    47 # 
    48 # ->>exit

        5、粘包

        ssh程序运行的时候会出现一个问题:当我们执行ipconfig /all 命令时,服务器给返回的信息是不完整的,

        当我们再次执行其他命令时,返回的信息依然是ipconfig /all上次未传完的数据。

        这是由于我们每次传输的数据只能是1024字节,未传完的数据只能等待下次传输,这个现象就是粘包现象

        那好,我们直接把1024字节调成无穷大不就好了,调大并不能解决问题,况且一次发送的数据也不能是无穷大的。

        解决方法是:发送数据前,先把数据包的大小发过来,循环收取,直到收到的数据与数据包大小一样。

        ssh服务端升级版。

     1 #!/user/bin/env ptyhon
     2 # -*- coding:utf-8 -*-
     3 # Author: VisonWong
     4 
     5 #ssh server
     6 
     7 import socket,os
     8 
     9 ip_port = ("127.0.0.1",9999)
    10 server = socket.socket()
    11 server.bind(ip_port)
    12 server.listen(5)
    13 
    14 while True:
    15     conn,add = server.accept()
    16     while True:
    17         print("等待接受指令")
    18         client_data = conn.recv(1024)
    19         client_data = client_data.decode("utf-8")
    20         print("收到指令",client_data)
    21         if client_data == "exit":           #收到exit 退出
    22             break
    23         send_data = os.popen(client_data).read()    #执行命令结果,要发送的数据
    24         if not send_data:
    25             conn.sendall(("warning:"+client_data + "命令不存在").encode("utf-8"))
    26         else:
    27             send_data = send_data.encode("utf-8")       #转换为bytes类型
    28 
    29             length = str(len(send_data))              #统计发送数据的长度
    30             conn.sendall(length.encode("utf-8"))      #长度以bytes类型发送过去
    31             print("共需返回%sbytes数据" %length)
    32 
    33             return_value = conn.recv(1024)
    34             return_value = return_value.decode("utf-8")
    35 
    36             if return_value == "start":
    37                   conn.sendall(send_data)
    38     conn.close()

        ssh客户端升级版。

     1 #!/user/bin/env ptyhon
     2 # -*- coding:utf-8 -*-
     3 # Author: VisonWong
     4 
     5 #ssh client
     6 
     7 import socket
     8 
     9 ip_port = ("127.0.0.1",9999)
    10 client = socket.socket()
    11 client.connect(ip_port)
    12 
    13 while True:
    14     cmd = input("->>").strip()
    15     if not cmd:                 #空字符 重新输入
    16         continue
    17     client.sendall(cmd.encode("utf-8"))     #要执行的命令发送过去
    18     if cmd == "exit":           #如果为exit 退出连接
    19         break
    20 
    21     server_data = client.recv(1024)     #数据长度
    22     server_data = server_data.decode("utf-8")
    23 
    24     if server_data.startswith("warning"):
    25         print(server_data)
    26         continue
    27     else:
    28         length = int(server_data)            #长度转换为int
    29 
    30     client.sendall("start".encode("utf-8"))     #发送字节start
    31 
    32     sum_data = b""                   #初始汇总的数据
    33     while length > 0:              #循环收数据
    34         server_data = client.recv(1024)
    35         length -=len(server_data)
    36         sum_data +=server_data
    37     print(sum_data.decode("utf-8"))                 #打印最终的执行数据
    38 
    39 client.close()

        注:int类型在socket传输中,要先把int类型转换为str格式,再转化为bytes类型。

         FTP下载服务器端。

     1 #!/user/bin/env ptyhon
     2 # -*- coding:utf-8 -*-
     3 # Author: VisonWong
     4 
     5 # ftp
     6 # server
     7 # 1.读取文件名
     8 # 2.检测文件是否存在
     9 # 3. 打开文件
    10 # 4.检测文件大小
    11 # 5.发送文件大小给客户端
    12 # 6.等客户端确认
    13 # 7.开始边读边发数据
    14 # 8.发送md5
    15 
    16 
    17 import socket,os,hashlib
    18 
    19 ip_port = ("127.0.0.1",9999)
    20 server = socket.socket()
    21 server.bind(ip_port)
    22 server.listen(5)
    23 
    24 while True:
    25     conn,add = server.accept()
    26     while True:
    27         print("开始")
    28         data = conn.recv(1024)
    29         cmd,filename = data.decode("utf-8").split()
    30         if os.path.isfile(filename):           #判断所得到文件名是否为一个文件
    31             with open(filename,"rb") as file:
    32                 file_size = os.stat(filename).st_size    #检测文件大小
    33                 print("返回文件%s大小为%s" %(filname,file_size))
    34                 conn.sendall(str(file_size).encode("utf-8"))   #发送文件大小
    35                 ack = conn.recv(1024)           #等待客户端确认状态,可以接收文件
    36                 m = hashlib.md5()
    37                 for line in file:
    38                     m.update(line)               #得到文件每行md5值,md5可连续更新
    39                     conn.sendall(line)           #将文件以行的形式拆分发送
    40             #此处需要注意的是,连续出现两个sendall()函数,依然有可能出现粘包现象
    41             #在客户端解决
    42             conn.sendall(m.hexdigest().encode())   #最后发送整个文件的md5值
    43         else:
    44             conn.sendall("文件不存在,请检查输入".encode("utf-8"))
    45     conn.close()

        FTP下载客户端。

     1 #!/user/bin/env ptyhon
     2 # -*- coding:utf-8 -*-
     3 # Author: VisonWong
     4 
     5 import socket,hashlib
     6 
     7 ip_port = ("127.0.0.1",9999)
     8 client = socket.socket()
     9 client.connect(ip_port)
    10 
    11 while True:
    12     cmd = input("请输入要下载的文件名
    "
    13                 "形如:get python.avi
    "
    14                 "->>").strip()
    15     if (not cmd) or (not cmd.startswith("get")) :   #判断发送指令,不能为空
    16         continue
    17     client.sendall(cmd.encode())     #要执行的命令发送过去
    18     if cmd.startswith("get"):
    19         filename = cmd.split()[1]
    20         server_response = client.recv(1024)
    21         server_response = server_response.decode("utf-8")
    22         if server_response.startswith("文件"):
    23             print(server_response)
    24             continue
    25         else:
    26             file_total_size = int(server_response)
    27             client.sendall("ok".encode("utf-8"))
    28             #因为在本机测试,服务器端下载文件和客户端得到文件在同一目录,故重命名
    29             filename = filename.split('.')
    30             with open("%s_new.%s"%(filename[0],filename[1]),"wb") as file:
    31                 revice_size = 0                #建立接受字节计数器
    32                 m = hashlib.md5()
    33                 while revice_size < file_total_size:
    34                     if file_total_size - revice_size > 1024:    #一次接受1024字节
    35                         size =1024
    36                     else:
    37                         size = file_total_size - revice_size     #最后一次接受,接受剩余字节
    38                     #接收到文件末尾后结束,防止多接受引起的粘包问题
    39                     data = client.recv(size)
    40                     revice_size += len(data)
    41                     file.write(data)
    42                     m.update(data)
    43                     print(file_total_size,revice_size,file_total_size - revice_size)
    44 
    45             new_file_md5 = m.hexdigest()
    46             server_file_md5 = client.recv(1024).decode("utf-8")
    47             print("new",new_file_md5)
    48             print("old",server_file_md5)
    49 
    50 
    51 client.close()
    52 
    53 
    54 
    55 # 请输入要下载的文件名
    56 # 形如:get python.avi
    57 # ->>get chat_robot_server.py
    58 # 1313 26 1287
    59 # 1313 1049 264
    60 # 1313 1117 196
    61 # 1313 1278 35
    62 # 1313 1297 16
    63 # 1313 1313 0
    64 # new 40a34e49fa1dc5022537c9c737e93616
    65 # old 40a34e49fa1dc5022537c9c737e93616
    66 # 请输入要下载的文件名
    67 # 形如:get python.avi
    68 # ->>

    二、socketserver 多连接

      正如前面的socket模块部分看到的一样,写一个简单套接字服务器不是很难,如果想实现超出继承的应用,最好寻求一些帮助。

       socketserver模块是标准库中很多服务器框架的基础,这些服务器架构包括

       BaseHTTPServer,

            SimpleHTTPServer,

            CGIHTTPServer,

       SimpleXMLRPCServer,

      DocXMLRPCServer,

      所有的这些服务器框架都为基础服务器增加了特定功能。

      socketserver内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。

      即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。

      ThreadingTCPServer(多线程,真并发

    ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互。

    使用ThreadingTCPServer:

      • 创建一个继承自 SocketServer.BaseRequestHandler 的类
      • 类中必须定义一个名称为 handle 的方法
      • 启动ThreadingTCPServer

    用socketserver对ssh程序做修改,实现多用户同时操作互不影响。

    ssh多用户服务器端。

     1 #!/user/bin/env ptyhon
     2 # -*- coding:utf-8 -*-
     3 # Author: VisonWong
     4 
     5 
     6 #scoketserver
     7 
     8 import socketserver,os
     9 
    10 class Myserver(socketserver.BaseRequestHandler):
    11     def handle(self):
    12         while True:
    13             conn = self.request
    14            # conn,add = server.accept()
    15             while True:
    16                 print("开始收")
    17                 client_data = conn.recv(1024)
    18                 client_data = client_data.decode("utf-8")
    19                 if client_data == "exit":           #收到exit 退出
    20                     break
    21                 send_data = os.popen(client_data).read()    #执行命令结果,要发送的数据
    22                 send_data = send_data.encode("utf-8")       #转换为bytes类型
    23 
    24                 length = str(len(send_data))              #统计发送数据的长度
    25                 conn.sendall(length.encode("utf-8"))      #长度以bytes类型发送过去
    26 
    27                 return_value = conn.recv(1024)
    28                 return_value = return_value.decode("utf-8")
    29 
    30                 if return_value == "start":
    31                     if not send_data:            # 如果执行结果为空,表示命令不存在
    32                         conn.sendall((client_data +"命令不存在").encode("utf-8"))
    33                     else:
    34                       conn.sendall(send_data)
    35             conn.close()
    36 
    37 if __name__ == '__main__':
    38     server = socketserver.ThreadingTCPServer(("127.0.0.1",8888),Myserver)
    39     server.serve_forever()

    ssh多用户客户端。

     1 #!/user/bin/env ptyhon
     2 # -*- coding:utf-8 -*-
     3 # Author: VisonWong
     4 
     5 
     6 #ssh client
     7 
     8 import socket
     9 
    10 ip_port = ("127.0.0.1",8888)
    11 client = socket.socket()
    12 client.connect(ip_port)
    13 
    14 while True:
    15     cmd = input("->>").strip()
    16     if not cmd:                 #空字符 重新输入
    17         continue
    18     client.sendall(cmd.encode("utf-8"))     #要执行的命令发送过去
    19     if cmd == "exit":           #如果为exit 退出连接
    20         break
    21 
    22     length = client.recv(1024)     #数据长度
    23     length = length.decode("utf-8")
    24     length = int(length)            #长度转换为int
    25 
    26     client.sendall("start".encode("utf-8"))     #发送字节start
    27 
    28     sum_data = ""                   #初始汇总的数据
    29     while length >= 0:              #循环收数据
    30         server_data = client.recv(1024)
    31         length -=1024
    32         sum_data +=server_data.decode("utf-8")
    33     print(sum_data)                 #打印最终的执行数据
    34 
    35 client.close()

       ThreadingTCPServer源码剖析 

      

        内部调用流程为:

        1、启动服务端程序

        2、执行 TCPServer.__init__ 方法,创建服务端Socket对象并绑定 IP 和 端口

        3、执行 BaseServer.__init__ 方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 MyRequestHandle赋值给 self.RequestHandlerClass

        4、执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 ...

        5、当客户端连接到达服务器

        6、执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求

        7、执行 ThreadingMixIn.process_request_thread 方法

        8、执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass()  即:

           执行 自定义 MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)

        相关源码:

    class TCPServer(BaseServer):
    
        """Base class for various socket-based server classes.
    
        Defaults to synchronous IP stream (i.e., TCP).
    
        Methods for the caller:
    
        - __init__(server_address, RequestHandlerClass, bind_and_activate=True)
        - serve_forever(poll_interval=0.5)
        - shutdown()
        - handle_request()  # if you don't use serve_forever()
        - fileno() -> int   # for select()
    
        Methods that may be overridden:
    
        - server_bind()
        - server_activate()
        - get_request() -> request, client_address
        - handle_timeout()
        - verify_request(request, client_address)
        - process_request(request, client_address)
        - shutdown_request(request)
        - close_request(request)
        - handle_error()
    
        Methods for derived classes:
    
        - finish_request(request, client_address)
    
        Class variables that may be overridden by derived classes or
        instances:
    
        - timeout
        - address_family
        - socket_type
        - request_queue_size (only for stream sockets)
        - allow_reuse_address
    
        Instance variables:
    
        - server_address
        - RequestHandlerClass
        - socket
    
        """
    
        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):
            """Called by constructor to bind the socket.
    
            May be overridden.
    
            """
            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):
            """Called by constructor to activate the server.
    
            May be overridden.
    
            """
            self.socket.listen(self.request_queue_size)
    
        def server_close(self):
            """Called to clean-up the server.
    
            May be overridden.
    
            """
            self.socket.close()
    
        def fileno(self):
            """Return socket file number.
    
            Interface required by select().
    
            """
            return self.socket.fileno()
    
        def get_request(self):
            """Get the request and client address from the socket.
    
            May be overridden.
    
            """
            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 socket.error:
                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()
    
    TCPServer
    
    TCPServer
    TCPServer
    class BaseServer:
    
        """Base class for server classes.
    
        Methods for the caller:
    
        - __init__(server_address, RequestHandlerClass)
        - serve_forever(poll_interval=0.5)
        - shutdown()
        - handle_request()  # if you do not use serve_forever()
        - fileno() -> int   # for select()
    
        Methods that may be overridden:
    
        - server_bind()
        - server_activate()
        - get_request() -> request, client_address
        - handle_timeout()
        - verify_request(request, client_address)
        - server_close()
        - process_request(request, client_address)
        - shutdown_request(request)
        - close_request(request)
        - handle_error()
    
        Methods for derived classes:
    
        - finish_request(request, client_address)
    
        Class variables that may be overridden by derived classes or
        instances:
    
        - timeout
        - address_family
        - socket_type
        - allow_reuse_address
    
        Instance variables:
    
        - RequestHandlerClass
        - socket
    
        """
    
        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):
            """Called by constructor to activate the server.
    
            May be overridden.
    
            """
            pass
    
        def serve_forever(self, poll_interval=0.5):
            """Handle one request at a time until shutdown.
    
            Polls for shutdown every poll_interval seconds. Ignores
            self.timeout. If you need to do periodic tasks, do them in
            another thread.
            """
            self.__is_shut_down.clear()
            try:
                while not self.__shutdown_request:
                    # XXX: Consider using another file descriptor or
                    # connecting to the socket to wake this up instead of
                    # polling. Polling reduces our responsiveness to a
                    # shutdown request and wastes cpu at all other times.
                    r, w, e = _eintr_retry(select.select, [self], [], [],
                                           poll_interval)
                    if self in r:
                        self._handle_request_noblock()
            finally:
                self.__shutdown_request = False
                self.__is_shut_down.set()
    
        def shutdown(self):
            """Stops the serve_forever loop.
    
            Blocks until the loop has finished. This must be called while
            serve_forever() is running in another thread, or it will
            deadlock.
            """
            self.__shutdown_request = True
            self.__is_shut_down.wait()
    
        # The distinction between handling, getting, processing and
        # finishing a request is fairly arbitrary.  Remember:
        #
        # - handle_request() is the top-level call.  It calls
        #   select, get_request(), verify_request() and process_request()
        # - get_request() is different for stream or datagram sockets
        # - process_request() is the place that may fork a new process
        #   or create a new thread to finish the request
        # - finish_request() instantiates the request handler class;
        #   this constructor will handle the request all by itself
    
        def handle_request(self):
            """Handle one request, possibly blocking.
    
            Respects self.timeout.
            """
            # Support people who used socket.settimeout() to escape
            # handle_request before self.timeout was available.
            timeout = self.socket.gettimeout()
            if timeout is None:
                timeout = self.timeout
            elif self.timeout is not None:
                timeout = min(timeout, self.timeout)
            fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
            if not fd_sets[0]:
                self.handle_timeout()
                return
            self._handle_request_noblock()
    
        def _handle_request_noblock(self):
            """Handle one request, without blocking.
    
            I assume that select.select has returned that the socket is
            readable before this function was called, so there should be
            no risk of blocking in get_request().
            """
            try:
                request, client_address = self.get_request()
            except socket.error:
                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)
    
        def handle_timeout(self):
            """Called if no new request arrives within self.timeout.
    
            Overridden by ForkingMixIn.
            """
            pass
    
        def verify_request(self, request, client_address):
            """Verify the request.  May be overridden.
    
            Return True if we should proceed with this request.
    
            """
            return True
    
        def process_request(self, request, client_address):
            """Call finish_request.
    
            Overridden by ForkingMixIn and ThreadingMixIn.
    
            """
            self.finish_request(request, client_address)
            self.shutdown_request(request)
    
        def server_close(self):
            """Called to clean-up the server.
    
            May be overridden.
    
            """
            pass
    
        def finish_request(self, request, client_address):
            """Finish one request by instantiating RequestHandlerClass."""
            self.RequestHandlerClass(request, client_address, self)
    
        def shutdown_request(self, request):
            """Called to shutdown and close an individual request."""
            self.close_request(request)
    
        def close_request(self, request):
            """Called to clean up an individual request."""
            pass
    
        def handle_error(self, request, client_address):
            """Handle an error gracefully.  May be overridden.
    
            The default is to print a traceback and continue.
    
            """
            print '-'*40
            print 'Exception happened during processing of request from',
            print client_address
            import traceback
            traceback.print_exc() # XXX But this goes to stderr!
            print '-'*40
    
    BaseServer
    
    BaseServer
    BaseServer
    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()
    
    ThreadingMixIn
    ThreadingMixIn
    class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
    ThreadingTCPServer
    class BaseRequestHandler:
    
        """Base class for request handler classes.
    
        This class is instantiated for each request to be handled.  The
        constructor sets the instance variables request, client_address
        and server, and then calls the handle() method.  To implement a
        specific service, all you need to do is to derive a class which
        defines a handle() method.
    
        The handle() method can find the request as self.request, the
        client address as self.client_address, and the server (in case it
        needs access to per-server information) as self.server.  Since a
        separate instance is created for each request, the handle() method
        can define arbitrary other instance variariables.
    
        """
    
        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
    
    SocketServer.BaseRequestHandler
    SocketServer.BaseRequestHandler

        对源码进行精简做一个程序:

     1 import socket
     2 import threading
     3 import select
     4  
     5  
     6 def process(request, client_address):
     7     print request,client_address
     8     conn = request
     9     conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
    10     flag = True
    11     while flag:
    12         data = conn.recv(1024)
    13         if data == 'exit':
    14             flag = False
    15         elif data == '0':
    16             conn.sendall('通过可能会被录音.balabala一大推')
    17         else:
    18             conn.sendall('请重新输入.')
    19  
    20 sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    21 sk.bind(('127.0.0.1',8002))
    22 sk.listen(5)
    23  
    24 while True:
    25     r, w, e = select.select([sk,],[],[],1)
    26     print 'looping'
    27     if sk in r:
    28         print 'get request'
    29         request, client_address = sk.accept()
    30         t = threading.Thread(target=process, args=(request, client_address))  # 每个连接过来创建一个线程
    31         t.daemon = False
    32         t.start()
    33  
    34 sk.close()

       如精简代码可以看出,SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于 select 和 Threading 两个东西,

       其实本质上就是在服务器端为每一个客户端创建一个线程,当前线程用来处理对应客户端的请求,所以,可以支持同时n个客户端链接(长连接)

     

      

  • 相关阅读:
    html+css动态篇
    html+css定位篇
    首页的css
    display详细说明
    html+css 布局篇
    html+css杂记
    JS与ES的关系
    H5本地存储
    JavaScript面向对象
    JavaScript执行上下文
  • 原文地址:https://www.cnblogs.com/visonwong/p/9155660.html
Copyright © 2020-2023  润新知