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:\Python\PythonLearing\web 的目录
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:\Python\PythonLearing\web
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("请输入要下载的文件名\n"
13                 "形如:get python.avi\n"
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个客户端链接(长连接)

 

 

 

 

 

 

 

 

 

 

 

 

  

 

posted @ 2018-06-08 14:54  VisonWong  阅读(242)  评论(0编辑  收藏  举报