Socket

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

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)

socket和file的区别:

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

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. import socket
  5.  
  6. ip_port = ('127.0.0.1',9999)
  7.  
  8. sk = socket.socket()
  9. sk.bind(ip_port)
  10. sk.listen(5)
  11.  
  12. while True:
  13. print 'server waiting...'
  14. conn,addr = sk.accept()
  15.  
  16. client_data = conn.recv(1024)
  17. print client_data
  18. conn.sendall('不要回答,不要回答,不要回答')
  19.  
  20. conn.close()

socket server

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. import socket
  4. ip_port = ('127.0.0.1',9999)
  5.  
  6. sk = socket.socket()
  7. sk.connect(ip_port)
  8.  
  9. sk.sendall('请求占领地球')
  10.  
  11. server_reply = sk.recv(1024)
  12. print server_reply
  13.  
  14. sk.close()

socket client

WEB服务应用:

  1. #!/usr/bin/env python
  2. #coding:utf-8
  3. import socket
  4.  
  5. def handle_request(client):
  6. buf = client.recv(1024)
  7. client.send("HTTP/1.1 200 OK\r\n\r\n")
  8. client.send("Hello, World")
  9.  
  10. def main():
  11. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  12. sock.bind(('localhost',8080))
  13. sock.listen(5)
  14.  
  15. while True:
  16. connection, address = sock.accept()
  17. handle_request(connection)
  18. connection.close()
  19.  
  20. if __name__ == '__main__':
  21. main()

更多功能

sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)

参数一:地址簇

  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. import socket
  2. ip_port = ('127.0.0.1',9999)
  3. sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
  4. sk.bind(ip_port)
  5.  
  6. while True:
  7. data = sk.recv(1024)
  8. print data
  9.  
  10. import socket
  11. ip_port = ('127.0.0.1',9999)
  12.  
  13. sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
  14. while True:
  15. inp = raw_input('数据:').strip()
  16. if inp == 'exit':
  17. break
  18. sk.sendto(inp,ip_port)
  19.  
  20. sk.close()

UDP Demo

sk.bind(address)

  s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。

sk.listen(backlog)

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

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

sk.setblocking(bool)

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

sk.accept()

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

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

sk.connect(address)

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

sk.connect_ex(address)

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

sk.close()

  关闭套接字

sk.recv(bufsize[,flag])

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

sk.recvfrom(bufsize[.flag])

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

sk.send(string[,flag])

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

sk.sendall(string[,flag])

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

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

sk.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()

  套接字的文件描述符

  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. import socket
  14. ip_port = ('127.0.0.1',9999)
  15.  
  16. sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
  17. while True:
  18. inp = input('数据:').strip()
  19. if inp == 'exit':
  20. break
  21. sk.sendto(bytes(inp, encoding='utf-8'),ip_port)
  22. data = sk.recvfrom(1024)
  23. print(data)
  24.  
  25. sk.close()

UDP

实例:智能机器人

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. import socket
  5.  
  6. ip_port = ('127.0.0.1',8888)
  7. sk = socket.socket()
  8. sk.bind(ip_port)
  9. sk.listen(5)
  10.  
  11. while True:
  12. conn,address = sk.accept()
  13. conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
  14. Flag = True
  15. while Flag:
  16. data = conn.recv(1024)
  17. if data == 'exit':
  18. Flag = False
  19. elif data == '':
  20. conn.sendall('通过可能会被录音.balabala一大推')
  21. else:
  22. conn.sendall('请重新输入.')
  23. conn.close()

服务端

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. import socket
  5.  
  6. ip_port = ('127.0.0.1',8005)
  7. sk = socket.socket()
  8. sk.connect(ip_port)
  9. sk.settimeout(5)
  10.  
  11. while True:
  12. data = sk.recv(1024)
  13. print 'receive:',data
  14. inp = raw_input('please input:')
  15. sk.sendall(inp)
  16. if inp == 'exit':
  17. break
  18.  
  19. sk.close()

客户端

IO多路复用

I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

select
 
select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。
 
poll
 
poll在1986年诞生于System V Release3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。
 
epoll
 
直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

Python

Python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用。

Windows Python:
    提供: select
Mac Python:
    提供: select
Linux Python:
    提供: select、poll、epoll
注意:网络操作、文件操作、终端操作等均属于IO操作,对于windows只支持Select 操作,其他系统支持其他IO操作,但是无法检测 普通文件操作 自动上次读取是否已经变化。
 
对于select方法:
句柄列表11, 句柄列表22, 句柄列表33=select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间)
参数: 可接受四个参数(前三个必须)
返回值:三个列表
select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。
1、当 参数1序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1序列中
2、当 参数2序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2序列中
3、当 参数3序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3序列中
4、当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化
   当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。
  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. import select
  5. import threading
  6. import sys
  7.  
  8. while True:
  9. readable, writeable, error = select.select([sys.stdin,],[],[],1)
  10. if sys.stdin in readable:
  11. print 'select get stdin',sys.stdin.readline()

利用select监听终端操作实例

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. import socket
  5. import select
  6.  
  7. sk1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  8. sk1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  9. sk1.bind(('127.0.0.1',8002))
  10. sk1.listen(5)
  11. sk1.setblocking(0)
  12.  
  13. inputs = [sk1,]
  14.  
  15. while True:
  16. readable_list, writeable_list, error_list = select.select(inputs, [], inputs, 1)
  17. for r in readable_list:
  18. # 当客户端第一次连接服务端时
  19. if sk1 == r:
  20. print 'accept'
  21. request, address = r.accept()
  22. request.setblocking(0)
  23. inputs.append(request)
  24. # 当客户端连接上服务端之后,再次发送数据时
  25. else:
  26. received = r.recv(1024)
  27. # 当正常接收客户端发送的数据时
  28. if received:
  29. print 'received data:', received
  30. # 当客户端关闭程序时
  31. else:
  32. inputs.remove(r)
  33.  
  34. sk1.close()

利用select实现伪同时处理多个Socket客户端请求:服务端

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. import socket
  4.  
  5. ip_port = ('127.0.0.1',8002)
  6. sk = socket.socket()
  7. sk.connect(ip_port)
  8.  
  9. while True:
  10. inp = raw_input('please input:')
  11. sk.sendall(inp)
  12. sk.close()

利用select实现伪同时处理多个Socket客户端请求:客户端

此处的Socket服务端相比与原生的Socket,他支持当某一个请求不再发送数据时,服务器端不会等待而是可以去处理其他请求的数据。但是,如果每个请求的耗时比较长时,select版本的服务器端也无法完成同时操作。

  1. #!/usr/bin/env python
  2. #coding:utf8
  3.  
  4. '''
  5. 服务器的实现 采用select的方式
  6. '''
  7.  
  8. import select
  9. import socket
  10. import sys
  11. import Queue
  12.  
  13. #创建套接字并设置该套接字为非阻塞模式
  14.  
  15. server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  16. server.setblocking(0)
  17.  
  18. #绑定套接字
  19. server_address = ('localhost',10000)
  20. print >>sys.stderr,'starting up on %s port %s'% server_address
  21. server.bind(server_address)
  22.  
  23. #将该socket变成服务模式
  24. #backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
  25. #这个值不能无限大,因为要在内核中维护连接队列
  26.  
  27. server.listen(5)
  28.  
  29. #初始化读取数据的监听列表,最开始时希望从server这个套接字上读取数据
  30. inputs = [server]
  31.  
  32. #初始化写入数据的监听列表,最开始并没有客户端连接进来,所以列表为空
  33.  
  34. outputs = []
  35.  
  36. #要发往客户端的数据
  37. message_queues = {}
  38. while inputs:
  39. print >>sys.stderr,'waiting for the next event'
  40. #调用select监听所有监听列表中的套接字,并将准备好的套接字加入到对应的列表中
  41. readable,writable,exceptional = select.select(inputs,outputs,inputs)#列表中的socket 套接字 如果是文件呢?
  42. #监控文件句柄有某一处发生了变化 可写 可读 异常属于Linux中的网络编程
  43. #属于同步I/O操作,属于I/O复用模型的一种
  44. #rlist--等待到准备好读
  45. #wlist--等待到准备好写
  46. #xlist--等待到一种异常
  47. #处理可读取的套接字
  48.  
  49. '''
  50. 如果server这个套接字可读,则说明有新链接到来
  51. 此时在server套接字上调用accept,生成一个与客户端通讯的套接字
  52. 并将与客户端通讯的套接字加入inputs列表,下一次可以通过select检查连接是否可读
  53. 然后在发往客户端的缓冲中加入一项,键名为:与客户端通讯的套接字,键值为空队列
  54. select系统调用是用来让我们的程序监视多个文件句柄(file descrīptor)的状态变化的。程序会停在select这里等待,
  55. 直到被监视的文件句柄有某一个或多个发生了状态改变
  56. '''
  57.  
  58. '''
  59. 若可读的套接字不是server套接字,有两种情况:一种是有数据到来,另一种是链接断开
  60. 如果有数据到来,先接收数据,然后将收到的数据填入往客户端的缓存区中的对应位置,最后
  61. 将于客户端通讯的套接字加入到写数据的监听列表:
  62. 如果套接字可读.但没有接收到数据,则说明客户端已经断开。这时需要关闭与客户端连接的套接字
  63. 进行资源清理
  64. '''
  65.  
  66. for s in readable:
  67. if s is server:
  68. connection,client_address = s.accept()
  69. print >>sys.stderr,'connection from',client_address
  70. connection.setblocking(0)#设置非阻塞
  71. inputs.append(connection)
  72. message_queues[connection] = Queue.Queue()
  73. else:
  74. data = s.recv(1024)
  75. if data:
  76. print >>sys.stderr,'received "%s" from %s'% \
  77. (data,s.getpeername())
  78. message_queues[s].put(data)
  79. if s not in outputs:
  80. outputs.append(s)
  81. else:
  82. print >>sys.stderr,'closing',client_address
  83. if s in outputs:
  84. outputs.remove(s)
  85. inputs.remove(s)
  86. s.close()
  87. del message_queues[s]
  88.  
  89. #处理可写的套接字
  90. '''
  91. 在发送缓冲区中取出响应的数据,发往客户端。
  92. 如果没有数据需要写,则将套接字从发送队列中移除,select中不再监视
  93. '''
  94.  
  95. for s in writable:
  96. try:
  97. next_msg = message_queues[s].get_nowait()
  98.  
  99. except Queue.Empty:
  100. print >>sys.stderr,' ',s,getpeername(),'queue empty'
  101. outputs.remove(s)
  102. else:
  103. print >>sys.stderr,'sending "%s" to %s'% \
  104. (next_msg,s.getpeername())
  105. s.send(next_msg)
  106.  
  107. #处理异常情况
  108.  
  109. for s in exceptional:
  110. for s in exceptional:
  111. print >>sys.stderr,'exception condition on',s.getpeername()
  112. inputs.remove(s)
  113. if s in outputs:
  114. outputs.remove(s)
  115. s.close()
  116. del message_queues[s]

基于select实现socket服务端(python2)

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. # Author:Sparks
  4. import select
  5. import socket
  6.  
  7. sk1 = socket.socket()
  8. sk1.bind(('127.0.0.1',8001))
  9. sk1.listen()
  10.  
  11. inputs = [sk1, ]
  12. # 实现读写分离
  13. # 保存谁发送过消息
  14. outputs = []
  15. # 保存消息内容 可以用队列优化
  16. message_dict = {}
  17.  
  18. while True:
  19. # select 内部自动监听多个句柄,一旦某个句柄发生变化则会响应
  20. # r_list变化值, w_list不变值, e_list出错值
  21. # 难点:inputs里面放着两类socket,一类是server的socket,一类是client的socket
  22. r_list, w_list, e_list = select.select(inputs,outputs,inputs,1)
  23. print('正在监听的socket对象%d' % len(inputs))
  24. print(r_list)
  25. for sk1_or_conn in r_list:
  26. # 每连接对象
  27. if sk1_or_conn == sk1:
  28. conn, address = sk1.accept()
  29. inputs.append(conn)
  30. # {'小明':[]}
  31. message_dict[conn] = []
  32. else:
  33. # 有老用户发消息了
  34. try:
  35. data_bytes = sk1_or_conn.recv(1024)
  36. except Exception as ex:
  37. inputs.remove(sk1_or_conn)
  38. else:
  39. # 用户正常发送消息
  40. data_str = str(data_bytes,encoding='utf-8')
  41. message_dict[sk1_or_conn].append(data_str)
  42. outputs.append(sk1_or_conn)
  43.  
  44. # w_list 仅仅保存了谁给我发送过消息
  45. # w_list = [zhanghui,]
  46. for conn in w_list:
  47. recv_str = message_dict[conn][0]
  48. del message_dict[conn][0]
  49. conn.sendall(bytes(recv_str+"hao",encoding="utf-8"))
  50. outputs.remove(conn)

python3实现

SocketServer模块

SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。

ThreadingTCPServer

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

1、ThreadingTCPServer基础

使用ThreadingTCPServer:

  • 创建一个继承自 SocketServer.BaseRequestHandler 的类
  • 类中必须定义一个名称为 handle 的方法
  • 启动ThreadingTCPServer
  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. import SocketServer
  4.  
  5. class MyServer(SocketServer.BaseRequestHandler):
  6.  
  7. def handle(self):
  8. # print self.request,self.client_address,self.server
  9. conn = self.request
  10. conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
  11. Flag = True
  12. while Flag:
  13. data = conn.recv(1024)
  14. if data == 'exit':
  15. Flag = False
  16. elif data == '':
  17. conn.sendall('通过可能会被录音.balabala一大推')
  18. else:
  19. conn.sendall('请重新输入.')
  20.  
  21. if __name__ == '__main__':
  22. server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
  23. server.serve_forever()

SocketServer实现服务器

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. import socket
  5.  
  6. ip_port = ('127.0.0.1',8009)
  7. sk = socket.socket()
  8. sk.connect(ip_port)
  9. sk.settimeout(5)
  10.  
  11. while True:
  12. data = sk.recv(1024)
  13. print 'receive:',data
  14. inp = raw_input('please input:')
  15. sk.sendall(inp)
  16. if inp == 'exit':
  17. break
  18.  
  19. sk.close()

客户端

2、ThreadingTCPServer源码剖析

ThreadingTCPServer的类图关系如下:

内部调用流程为:

  • 启动服务端程序
  • 执行 TCPServer.__init__ 方法,创建服务端Socket对象并绑定 IP 和 端口
  • 执行 BaseServer.__init__ 方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 MyRequestHandle赋值给 self.RequestHandlerClass
  • 执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 ...
  • 当客户端连接到达服务器
  • 执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求
  • 执行 ThreadingMixIn.process_request_thread 方法
  • 执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass()  即:执行 自定义 MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)

ThreadingTCPServer相关源码:

  1. class BaseServer:
  2.  
  3. """Base class for server classes.
  4.  
  5. Methods for the caller:
  6.  
  7. - __init__(server_address, RequestHandlerClass)
  8. - serve_forever(poll_interval=0.5)
  9. - shutdown()
  10. - handle_request() # if you do not use serve_forever()
  11. - fileno() -> int # for select()
  12.  
  13. Methods that may be overridden:
  14.  
  15. - server_bind()
  16. - server_activate()
  17. - get_request() -> request, client_address
  18. - handle_timeout()
  19. - verify_request(request, client_address)
  20. - server_close()
  21. - process_request(request, client_address)
  22. - shutdown_request(request)
  23. - close_request(request)
  24. - handle_error()
  25.  
  26. Methods for derived classes:
  27.  
  28. - finish_request(request, client_address)
  29.  
  30. Class variables that may be overridden by derived classes or
  31. instances:
  32.  
  33. - timeout
  34. - address_family
  35. - socket_type
  36. - allow_reuse_address
  37.  
  38. Instance variables:
  39.  
  40. - RequestHandlerClass
  41. - socket
  42.  
  43. """
  44.  
  45. timeout = None
  46.  
  47. def __init__(self, server_address, RequestHandlerClass):
  48. """Constructor. May be extended, do not override."""
  49. self.server_address = server_address
  50. self.RequestHandlerClass = RequestHandlerClass
  51. self.__is_shut_down = threading.Event()
  52. self.__shutdown_request = False
  53.  
  54. def server_activate(self):
  55. """Called by constructor to activate the server.
  56.  
  57. May be overridden.
  58.  
  59. """
  60. pass
  61.  
  62. def serve_forever(self, poll_interval=0.5):
  63. """Handle one request at a time until shutdown.
  64.  
  65. Polls for shutdown every poll_interval seconds. Ignores
  66. self.timeout. If you need to do periodic tasks, do them in
  67. another thread.
  68. """
  69. self.__is_shut_down.clear()
  70. try:
  71. while not self.__shutdown_request:
  72. # XXX: Consider using another file descriptor or
  73. # connecting to the socket to wake this up instead of
  74. # polling. Polling reduces our responsiveness to a
  75. # shutdown request and wastes cpu at all other times.
  76. r, w, e = _eintr_retry(select.select, [self], [], [],
  77. poll_interval)
  78. if self in r:
  79. self._handle_request_noblock()
  80. finally:
  81. self.__shutdown_request = False
  82. self.__is_shut_down.set()
  83.  
  84. def shutdown(self):
  85. """Stops the serve_forever loop.
  86.  
  87. Blocks until the loop has finished. This must be called while
  88. serve_forever() is running in another thread, or it will
  89. deadlock.
  90. """
  91. self.__shutdown_request = True
  92. self.__is_shut_down.wait()
  93.  
  94. # The distinction between handling, getting, processing and
  95. # finishing a request is fairly arbitrary. Remember:
  96. #
  97. # - handle_request() is the top-level call. It calls
  98. # select, get_request(), verify_request() and process_request()
  99. # - get_request() is different for stream or datagram sockets
  100. # - process_request() is the place that may fork a new process
  101. # or create a new thread to finish the request
  102. # - finish_request() instantiates the request handler class;
  103. # this constructor will handle the request all by itself
  104.  
  105. def handle_request(self):
  106. """Handle one request, possibly blocking.
  107.  
  108. Respects self.timeout.
  109. """
  110. # Support people who used socket.settimeout() to escape
  111. # handle_request before self.timeout was available.
  112. timeout = self.socket.gettimeout()
  113. if timeout is None:
  114. timeout = self.timeout
  115. elif self.timeout is not None:
  116. timeout = min(timeout, self.timeout)
  117. fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
  118. if not fd_sets[0]:
  119. self.handle_timeout()
  120. return
  121. self._handle_request_noblock()
  122.  
  123. def _handle_request_noblock(self):
  124. """Handle one request, without blocking.
  125.  
  126. I assume that select.select has returned that the socket is
  127. readable before this function was called, so there should be
  128. no risk of blocking in get_request().
  129. """
  130. try:
  131. request, client_address = self.get_request()
  132. except socket.error:
  133. return
  134. if self.verify_request(request, client_address):
  135. try:
  136. self.process_request(request, client_address)
  137. except:
  138. self.handle_error(request, client_address)
  139. self.shutdown_request(request)
  140.  
  141. def handle_timeout(self):
  142. """Called if no new request arrives within self.timeout.
  143.  
  144. Overridden by ForkingMixIn.
  145. """
  146. pass
  147.  
  148. def verify_request(self, request, client_address):
  149. """Verify the request. May be overridden.
  150.  
  151. Return True if we should proceed with this request.
  152.  
  153. """
  154. return True
  155.  
  156. def process_request(self, request, client_address):
  157. """Call finish_request.
  158.  
  159. Overridden by ForkingMixIn and ThreadingMixIn.
  160.  
  161. """
  162. self.finish_request(request, client_address)
  163. self.shutdown_request(request)
  164.  
  165. def server_close(self):
  166. """Called to clean-up the server.
  167.  
  168. May be overridden.
  169.  
  170. """
  171. pass
  172.  
  173. def finish_request(self, request, client_address):
  174. """Finish one request by instantiating RequestHandlerClass."""
  175. self.RequestHandlerClass(request, client_address, self)
  176.  
  177. def shutdown_request(self, request):
  178. """Called to shutdown and close an individual request."""
  179. self.close_request(request)
  180.  
  181. def close_request(self, request):
  182. """Called to clean up an individual request."""
  183. pass
  184.  
  185. def handle_error(self, request, client_address):
  186. """Handle an error gracefully. May be overridden.
  187.  
  188. The default is to print a traceback and continue.
  189.  
  190. """
  191. print '-'*40
  192. print 'Exception happened during processing of request from',
  193. print client_address
  194. import traceback
  195. traceback.print_exc() # XXX But this goes to stderr!
  196. print '-'*40

BaseServer

  1. class TCPServer(BaseServer):
  2.  
  3. """Base class for various socket-based server classes.
  4.  
  5. Defaults to synchronous IP stream (i.e., TCP).
  6.  
  7. Methods for the caller:
  8.  
  9. - __init__(server_address, RequestHandlerClass, bind_and_activate=True)
  10. - serve_forever(poll_interval=0.5)
  11. - shutdown()
  12. - handle_request() # if you don't use serve_forever()
  13. - fileno() -> int # for select()
  14.  
  15. Methods that may be overridden:
  16.  
  17. - server_bind()
  18. - server_activate()
  19. - get_request() -> request, client_address
  20. - handle_timeout()
  21. - verify_request(request, client_address)
  22. - process_request(request, client_address)
  23. - shutdown_request(request)
  24. - close_request(request)
  25. - handle_error()
  26.  
  27. Methods for derived classes:
  28.  
  29. - finish_request(request, client_address)
  30.  
  31. Class variables that may be overridden by derived classes or
  32. instances:
  33.  
  34. - timeout
  35. - address_family
  36. - socket_type
  37. - request_queue_size (only for stream sockets)
  38. - allow_reuse_address
  39.  
  40. Instance variables:
  41.  
  42. - server_address
  43. - RequestHandlerClass
  44. - socket
  45.  
  46. """
  47.  
  48. address_family = socket.AF_INET
  49.  
  50. socket_type = socket.SOCK_STREAM
  51.  
  52. request_queue_size = 5
  53.  
  54. allow_reuse_address = False
  55.  
  56. def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
  57. """Constructor. May be extended, do not override."""
  58. BaseServer.__init__(self, server_address, RequestHandlerClass)
  59. self.socket = socket.socket(self.address_family,
  60. self.socket_type)
  61. if bind_and_activate:
  62. try:
  63. self.server_bind()
  64. self.server_activate()
  65. except:
  66. self.server_close()
  67. raise
  68.  
  69. def server_bind(self):
  70. """Called by constructor to bind the socket.
  71.  
  72. May be overridden.
  73.  
  74. """
  75. if self.allow_reuse_address:
  76. self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  77. self.socket.bind(self.server_address)
  78. self.server_address = self.socket.getsockname()
  79.  
  80. def server_activate(self):
  81. """Called by constructor to activate the server.
  82.  
  83. May be overridden.
  84.  
  85. """
  86. self.socket.listen(self.request_queue_size)
  87.  
  88. def server_close(self):
  89. """Called to clean-up the server.
  90.  
  91. May be overridden.
  92.  
  93. """
  94. self.socket.close()
  95.  
  96. def fileno(self):
  97. """Return socket file number.
  98.  
  99. Interface required by select().
  100.  
  101. """
  102. return self.socket.fileno()
  103.  
  104. def get_request(self):
  105. """Get the request and client address from the socket.
  106.  
  107. May be overridden.
  108.  
  109. """
  110. return self.socket.accept()
  111.  
  112. def shutdown_request(self, request):
  113. """Called to shutdown and close an individual request."""
  114. try:
  115. #explicitly shutdown. socket.close() merely releases
  116. #the socket and waits for GC to perform the actual close.
  117. request.shutdown(socket.SHUT_WR)
  118. except socket.error:
  119. pass #some platforms may raise ENOTCONN here
  120. self.close_request(request)
  121.  
  122. def close_request(self, request):
  123. """Called to clean up an individual request."""
  124. request.close()

TCPServer

  1. class ThreadingMixIn:
  2. """Mix-in class to handle each request in a new thread."""
  3.  
  4. # Decides how threads will act upon termination of the
  5. # main process
  6. daemon_threads = False
  7.  
  8. def process_request_thread(self, request, client_address):
  9. """Same as in BaseServer but as a thread.
  10.  
  11. In addition, exception handling is done here.
  12.  
  13. """
  14. try:
  15. self.finish_request(request, client_address)
  16. self.shutdown_request(request)
  17. except:
  18. self.handle_error(request, client_address)
  19. self.shutdown_request(request)
  20.  
  21. def process_request(self, request, client_address):
  22. """Start a new thread to process the request."""
  23. t = threading.Thread(target = self.process_request_thread,
  24. args = (request, client_address))
  25. t.daemon = self.daemon_threads
  26. t.start()

ThreadingMixIn

  1. class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

ThreadingTCPServer

RequestHandler相关源码

  1. class BaseRequestHandler:
  2.  
  3. """Base class for request handler classes.
  4.  
  5. This class is instantiated for each request to be handled. The
  6. constructor sets the instance variables request, client_address
  7. and server, and then calls the handle() method. To implement a
  8. specific service, all you need to do is to derive a class which
  9. defines a handle() method.
  10.  
  11. The handle() method can find the request as self.request, the
  12. client address as self.client_address, and the server (in case it
  13. needs access to per-server information) as self.server. Since a
  14. separate instance is created for each request, the handle() method
  15. can define arbitrary other instance variariables.
  16.  
  17. """
  18.  
  19. def __init__(self, request, client_address, server):
  20. self.request = request
  21. self.client_address = client_address
  22. self.server = server
  23. self.setup()
  24. try:
  25. self.handle()
  26. finally:
  27. self.finish()
  28.  
  29. def setup(self):
  30. pass
  31.  
  32. def handle(self):
  33. pass
  34.  
  35. def finish(self):
  36. pass

SocketServer.BaseRequestHandler

实例:

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. import SocketServer
  4.  
  5. class MyServer(SocketServer.BaseRequestHandler):
  6.  
  7. def handle(self):
  8. # print self.request,self.client_address,self.server
  9. conn = self.request
  10. conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
  11. Flag = True
  12. while Flag:
  13. data = conn.recv(1024)
  14. if data == 'exit':
  15. Flag = False
  16. elif data == '':
  17. conn.sendall('通过可能会被录音.balabala一大推')
  18. else:
  19. conn.sendall('请重新输入.')
  20.  
  21. if __name__ == '__main__':
  22. server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
  23. server.serve_forever()

服务端

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. import socket
  5.  
  6. ip_port = ('127.0.0.1',8009)
  7. sk = socket.socket()
  8. sk.connect(ip_port)
  9. sk.settimeout(5)
  10.  
  11. while True:
  12. data = sk.recv(1024)
  13. print 'receive:',data
  14. inp = raw_input('please input:')
  15. sk.sendall(inp)
  16. if inp == 'exit':
  17. break
  18.  
  19. sk.close()

客户端

源码精简:

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

如精简代码可以看出,SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于select 和 Threading 两个东西,其实本质上就是在服务器端为每一个客户端创建一个线程,当前线程用来处理对应客户端的请求,所以,可以支持同时n个客户端链接(长连接)。

select
 
select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。
 
poll
 
poll在1986年诞生于System V Release3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。
 
epoll
 
直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

python——网络编程的更多相关文章

  1. Python 网络编程(二)

    Python 网络编程 上一篇博客介绍了socket的基本概念以及实现了简单的TCP和UDP的客户端.服务器程序,本篇博客主要对socket编程进行更深入的讲解 一.简化版ssh实现 这是一个极其简单 ...

  2. Python 网络编程(一)

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

  3. Python学习(22)python网络编程

    Python 网络编程 Python 提供了两个级别访问的网络服务.: 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的 ...

  4. Day07 - Python 网络编程 Socket

    1. Python 网络编程 Python 提供了两个级别访问网络服务: 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口 ...

  5. python网络编程-01

    python网络编程 1.socket模块介绍 ①在网络编程中的一个基本组件就是套接字(socket),socket是两个程序之间的“信息通道”. ②套接字包括两个部分:服务器套接字.客户机套接字 ③ ...

  6. 《Python网络编程》学习笔记--使用谷歌地理编码API获取一个JSON文档

    Foundations of Python Network Programing,Third Edition <python网络编程>,本书中的代码可在Github上搜索fopnp下载 本 ...

  7. Python网络编程基础pdf

    Python网络编程基础(高清版)PDF 百度网盘 链接:https://pan.baidu.com/s/1VGwGtMSZbE0bSZe-MBl6qA 提取码:mert 复制这段内容后打开百度网盘手 ...

  8. python 网络编程(Socket)

    # from wsgiref.simple_server import make_server## def RunServer(environ,start_response):# start_resp ...

  9. python 网络编程 IO多路复用之epoll

    python网络编程——IO多路复用之epoll 1.内核EPOLL模型讲解     此部分参考http://blog.csdn.net/mango_song/article/details/4264 ...

  10. 自学Python之路-Python网络编程

    自学Python之路-Python网络编程 自学Python之路[第一回]:1.11.2 1.3

随机推荐

  1. 超出父视图无法点击问题hitTest

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Menlo; color: #c91b13 } p.p2 { margin: 0.0px 0. ...

  2. 前端资讯周报 3.13 - 3.19: WebVR来了!以及如何优化scroll事件性能

    每周一我都会分享上一周我订阅的技术站点中,和解决问题的过程中阅读到的值得分享的文章.这是迫使我学习的一个动力 本周推荐 Minecraft in WebVR with HTML Using A-Fra ...

  3. ios 个推推送集成

    个推推送总结: 个推第三方平台官网地址:http://www.getui.com/cn/index.html 首先去官网注册账号,创建应用,应用的配置信息,创建APNs推送证书上传 P12证书(开发对 ...

  4. BootStrap入门教程 (三)

    本文转自 http://www.cnblogs.com/ventlam/archive/2012/06/05/2524966.html 上讲回顾:Bootstrap的基础CSS(Base CSS)提供 ...

  5. CSS中清除浮动的方法

    CSS浮动,最早是为了达到文字环绕的效果提出的,也可以用来做布局,但是布局会产生很多问题(高度塌陷,漂浮在普通流上),会使当前标签产生上浮的效果,会影响前后标签,同样的代码在不同的浏览器的兼容性也不一 ...

  6. UWP Composition API - New FlexGrid 锁定行列

    如果之前看了 UWP Jenkins + NuGet + MSBuild 手把手教你做自动UWP Build 和 App store包 这篇的童鞋,针对VS2017,需要对应更新一下配置,需要的童鞋点 ...

  7. HDFS入门(1)

    2015.07.12笔记 1.HDFS Distributed File System(操作系统实现人机交互,最重要的功能是文件管理,使用文件管理系统,windows.Linux文件管理系统有共性:用 ...

  8. (转)centos6.5安装VNC

    在Linux下用VNC远程桌面是个很不错的玩意.但在CentOS中默认没有安装VNC的.可以用下面语句查询,如果出现下面情况说明没有安装vnc #rpm -q tigervnc tigervnc-se ...

  9. 走入PHP-数据类型和字符串语法

    PHP支持8种原始数据类型 四种标量类型: boolean | integer | float(as double) | string 两种复合类型: array | object 两种特殊类型 re ...

  10. 使用cmd命令打开Python文件式程序方法

    首先:需要确定已编好的Python程序的存储路径:(即在哪个磁盘,哪个文件中) 其次:打开cmd命令,输入该程序所在磁盘,敲回车键:(例如其存储在E盘,则输入“E:”,敲回车键.) 然后:输入pyth ...