一、Socket

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

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

socket和file的区别:

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

1、信息交互的程序

服务端:

  1. #server端
  2.  
  3. import socket
  4.  
  5. ip_port = ("127.0.0.1",9999) #服务器端ip和服务端口
  6. server = socket.socket() #创建server
  7. #server = <socket.socket fd=344, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
  8.  
  9. server.bind(ip_port) #绑定地址ip
  10. #server = <socket.socket fd=344, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999)>
  11. server.listen(5) #开始监听,允许5个客户端排队
  12. conn,addr = server.accept() #等待连接
  13. #conn = <socket.socket fd=340, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999),
  14. # raddr=('127.0.0.1', 60086)>
  15. #addr = ('127.0.0.1', 60086)
  16.  
  17. client_data = conn.recv(1024) #收取信息,1024字节
  18. #client_data = b'hello'
  19. conn.sendall(client_data.upper()) #发送数据
  20.  
  21. conn.close() #关闭程序

客户端:

  1. #client端
  2.  
  3. import socket
  4.  
  5. ip_port = ("127.0.0.1",9999) #要连接的服务器IP和端口
  6. client = socket.socket() #创建client
  7.  
  8. client.connect(ip_port) #连接服务器端
  9. info = "hello"
  10.  
  11. client.sendall(info.encode("utf-8")) #发送数据包,把str转换为bytes类型
  12. server_data = client.recv(1024) #收取数据包
  13.  
  14. print(server_data.decode("utf-8"))

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

2、功能介绍

server = socket.socket()

  1. 参数一:地址簇
  2.  
  3.   socket.AF_INET IPv4(默认)
  4.   socket.AF_INET6 IPv6
  5.  
  6.   socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
  7.  
  8. 参数二:类型
  9.  
  10.   socket.SOCK_STREAM  流式socket , for TCP (默认)
  11.   socket.SOCK_DGRAM   数据报式socket , for UDP
  12.  
  13.   socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMPIGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
  14.   socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
  15.   socket.SOCK_SEQPACKET 可靠的连续数据包服务
  16.  
  17. 参数三:协议
  18.  
  19.   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. 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 Demo

② server.bind(address)

  server.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(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、聊天机器人

我们对上面的代码进行下升级,做个聊天机器人

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. #-Author-Lian
  4.  
  5. #server
  6.  
  7. import socket
  8.  
  9. ip_port = ("127.0.0.1",8888)
  10. server = socket.socket()
  11. server.bind(ip_port)
  12. server.listen(5)
  13.  
  14. while True:
  15. print("等待连接........")
  16. conn, addr = server.accept()
  17. while True:
  18. data = conn.recv(1024).decode("utf-8")
  19. if data == "exit":
  20. conn.sendall("exit".encode("utf-8"))
  21. break
  22. elif data == "":
  23. conn.sendall("汪星人星球正要进攻地球。。。。".encode("utf-8"))
  24. else:
  25. print(data)
  26. conn.sendall("继续发送电波".encode("utf-8"))
  27. conn.close()

server端

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. #-Author-Lian
  4.  
  5. #client
  6.  
  7. import socket
  8.  
  9. ip_port = ("127.0.0.1",8888)
  10. client = socket.socket()
  11. client.connect(ip_port)
  12.  
  13. while True:
  14. info = input("->>>")
  15. client.sendall(info.encode("utf-8"))
  16. data = client.recv(1024).decode("utf-8")
  17. if data == "exit":
  18. break
  19. else:
  20. print(data)
  21. client.close()

client端

4、ssh程序

整合下上面的代码,做个ssh连接的客户端,实现基本xshell功能

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. #-Author-Lian
  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. client_data = conn.recv(1024)
  18. recv_data = client_data.decode("utf-8")
  19. if recv_data == "exit":
  20. break
  21. send_data = os.popen(recv_data).read()
  22. if not send_data:
  23. conn.sendall(client_data+"命令不存在".encode("utf-8"))
  24. else:
  25. conn.sendall(send_data.encode("utf-8"))
  26. conn.close()

ssh 服务端

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. #-Author-Lian
  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()

ssh 客户端

5、粘包

ssh程序运行的时候会出现一个问题,啥问题类?当我们执行ipconfig /all 命令时,服务器给返回的信息是不完整的,当我们再次执行其他命令时,返回的信息依然是ipconfig /all上次未传完的数据;这是由于我们每次传输的数据只能是字节,未传完的数据只能等待下次传输,这个现象就是粘包现象。那好,我们直接把1024字节调成无穷大不就好了,呵呵!! 调大并没有卵用;难道就没有办法解决了吗?!突然想到一个办法了,发送数据前,先把数据包的大小发过来,我循环收取,直到收到的数据与数据包大小一样不就得了。哈哈,机智如我~嘎嘎

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. #-Author-Lian
  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. if client_data == "exit": #收到exit 退出
  21. break
  22. send_data = os.popen(client_data).read() #执行命令结果,要发送的数据
  23.  
  24. send_data = send_data.encode("utf-8") #转换为bytes类型
  25.  
  26. length = str(len(send_data)) #统计发送数据的长度
  27. conn.sendall(length.encode("utf-8")) #长度以bytes类型发送过去
  28.  
  29. return_value = conn.recv(1024)
  30. return_value = return_value.decode("utf-8")
  31.  
  32. if return_value == "start":
  33. conn.sendall(send_data)
  34. conn.close()

ssh 服务端升级版

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. #-Author-Lian
  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. length = client.recv(1024) #数据长度
  22. length = length.decode("utf-8")
  23. length = int(length) #长度转换为int
  24.  
  25. client.sendall("start".encode("utf-8")) #发送字节start
  26.  
  27. sum_data = b"" #初始汇总的数据
  28. while length > 0: #循环收数据
  29. server_data = client.recv(1024)
  30. length -=len(server_data)
  31. sum_data +=server_data
  32. print(sum_data.decode("utf-8")) #打印最终的执行数据
  33.  
  34. client.close()

ssh 客户端升级版

注:int类型在socket传输中,要先把int类型转换为str格式,再转化为bytes类型;另当前程序只能实现一对一,还不能一对多处理,思考下如何能多用户同时使用?

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. #-Author-Lian
  4.  
  5. import socket,os,hashlib
  6.  
  7. ip_port = ("127.0.0.1",9999)
  8. server = socket.socket()
  9. server.bind(ip_port)
  10. server.listen(5)
  11.  
  12. while True:
  13. conn,add = server.accept()
  14. while True:
  15. print("开始")
  16. data = conn.recv(1024)
  17.  
  18. cmd,filename = data.decode().split()
  19. if os.path.isfile(filename):
  20. with open(filename,"rb") as file:
  21. file_size = os.stat(filename).st_size
  22. conn.sendall(str(file_size).encode()) #发送文件大小
  23. ack = conn.recv(1024) #等待ack
  24. m = hashlib.md5()
  25. for line in file:
  26. m.update(line)
  27. conn.sendall(line)
  28. conn.sendall(m.hexdigest().encode())
  29. conn.close()

ftp 上传服务端

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. #-Author-Lian
  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("->>").strip()
  13. if not cmd:continue
  14. client.sendall(cmd.encode()) #要执行的命令发送过去
  15. if cmd.startswith("get"):
  16. filename = cmd.split()[1]
  17. server_response = client.recv(1024)
  18. file_total_size = int(server_response.decode())
  19. client.sendall("ok".encode())
  20. with open("%s"%filename,"wb") as file:
  21. revice_size = 0
  22. m = hashlib.md5()
  23. while revice_size < file_total_size:
  24. if file_total_size - revice_size > 1024:
  25. size =1024
  26. else:
  27. size = file_total_size - revice_size
  28. data = client.recv(size)
  29. revice_size += len(data)
  30. file.write(data)
  31. m.update(data)
  32. print(file_total_size,revice_size,file_total_size - revice_size)
  33.  
  34. new_file_md5 = m.hexdigest()
  35. server_file_md5 = client.recv(1024).decode()
  36. print("new",new_file_md5)
  37. print("old",server_file_md5)
  38.  
  39. client.close()

ftp 上传服务端

二、socketserver(多连接

  正如前面的socket模块部分看到的一样,写一个简单套接字服务器不是很难,如果想实现超出继承的应用,最好寻求一些帮助,socketserver模块是标准库中很多服务器框架的基础,这些服务器架构包括BaseHTTPServer、SimpleHTTPServer、CGIHTTPServer、SimpleXMLRPCServer、DocXMLRPCServer,所有的这些服务器框架都为基础服务器增加了特定功能;

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

ThreadingTCPServer(多线程,真并发

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

使用ThreadingTCPServer:

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

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

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

ssh 服务端多用户同时连接

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. #-Author-Lian
  4.  
  5. #ssh client
  6.  
  7. import socket
  8.  
  9. ip_port = ("127.0.0.1",8888)
  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. length = client.recv(1024) #数据长度
  22. length = length.decode("utf-8")
  23. length = int(length) #长度转换为int
  24.  
  25. client.sendall("start".encode("utf-8")) #发送字节start
  26.  
  27. sum_data = "" #初始汇总的数据
  28. while length >= 0: #循环收数据
  29. server_data = client.recv(1024)
  30. length -=1024
  31. sum_data +=server_data.decode("utf-8")
  32. print(sum_data) #打印最终的执行数据
  33.  
  34. client.close()

ssh 客户端多用户同时连接

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方法)

相关源码:

  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
  197.  
  198. BaseServer

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()
  125.  
  126. TCPServer

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

  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. 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 == '0':
  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个客户端链接(长连接)

三、IO多路复用多连接

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:

  •   当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
  •   当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
  •   如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
  •   如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
  •   如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

select模块实现伪并发

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

  1. select 模块
  2.  
  3. Windows Python
  4. 提供: select
  5. Mac Python
  6. 提供: select
  7. Linux Python
  8. 提供: selectpollepoll
  • select

  select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。

  • poll

  poll在1986年诞生于System V Release 3,它和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()时便得到通知。

select.select方法:

  select函数需要3个序列作为它的必选参数,此外还有一个可选的以秒单位的超时时间作为第4个参数。3个序列用于输入、输出以及异常情况(错误);如果没有给定超时时间,select会阻塞(也就是处于等待状态),知道其中的一个文件描述符以及为行动做好了准备,如果给定了超时时间,select最多阻塞给定的超时时间,如果超时时间为0,那么就给出一个连续的poll(即不阻塞);select的返回值是3个序列,每个代表相应参数的一个活动子集。第一个序列用于监听socket对象内部是否发生变化,如果有变化表示有新的连接,下面直接看程序代码

select.select伪并发程序服务端

  1. import socket
  2. import select
  3.  
  4. sk = socket.socket()
  5. sk.bind(('127.0.0.1',8002))
  6. sk.listen(5)
  7. sk.setblocking(0)  #不阻塞
  8.  
  9. inputs = [sk,]
  10. messages = {}
  11. outputs = []
  12. while True:
  13. readable_list, writeable_list, error_list = select.select(inputs, outputs, [], 1)
  14. # readable_list 监听服务端对象,当inputs列表有变化时,变化的值会赋值给readable_list中
  15. # 如果有新的连接进来,sk会发生变化,此时readable_list—的值为sk
  16. # 如果conn对象发生变化,表示客户端发送了新的消息过来,此时readable_list的值为客户端连接
  17. # writeable_lists实现读写分离,需要回复信息的conn对象添加到里面
  18. print(len(inputs),len(readable_list),len(writeable_list),len(outputs))
  19.  
  20. for r in readable_list:
  21. # 当客户端第一次连接服务端时,未在inputs里
  22. if r == sk:
  23. print('accept')
  24. conn, address = r.accept()
  25. conn.sendall("hello".encode())
  26. inputs.append(conn) #添加到inputs
  27. messages[conn]=[] #设置messages key值r为列表
  28.  
  29. # 当客户端连接上服务端之后,再次发送数据时,已经存在inputs
  30. else:
  31. try:
  32. received = r.recv(1024)
  33. # 当正常接收客户端发送的数据时
  34. if not received:
  35. raise Exception("断开连接")
  36. else:
  37. messages[r].append(received)
  38. outputs.append(r)
  39.  
  40. # 当客户端关闭程序时
  41. except Exception as e:
  42. inputs.remove(r)
  43. del messages[r]
  44. for w in writeable_list:
  45. msg = messages[w].pop()
  46. rest = msg + "response".encode()
  47. w.sendall(rest)
  48. outputs.remove(w)
  49.  
  50. sk.close()

select.select伪并发程序客户端

  1. import socket
  2.  
  3. sk = socket.socket()
  4. sk.connect(("127.0.0.1",8002))
  5. print(sk.recv(1024).decode())
  6.  
  7. while True:
  8. command = input("--->>>")
  9. sk.sendall(command.encode())
  10. res = sk.recv(1024)
  11. print(res.decode())
  12. sk.close()

select.poll方法:

  poll方法使用起来比select简单。在调用poll时,会得到一个poll对象。然后就可以使用poll的对象的register方法注册一个文件描述符(或者是带有fileno方法的对象)。注册后可以使用unregister方法移出注册的对象。注册了一些对象(比如套接字)以后,就可以调用poll方法(带有一个可选的超时时间参数)并得到一个(fd,event)格式列表(可能为空),其中fd是文件描述符,event则告诉你发生了什么。这是一个位掩码(bitmask),意思是它是一个整数,这个整数的每个位对应不同的事件。那些不同的事件是select模块的常量,为了验证是否设置了一个定位(也就是说,一个给定的事件是否发生了),可以使用按位与操作符(&):if event & select.POLLIN

select模块中的polling事件常量:

  1. 事件名 描述
  2.  
  3. POLLIN 读取来自文件描述符的数据
  4. POLLPRT 读取来自文件描述符的紧急数据
  5. POLLOUT 文件描述符已经准备好数据,写入时不会发生阻塞
  6. POLLERR 与文件描述符有关的错误情况
  7. POLLHUP 挂起,连接丢失
  8. POLLNVAL 无效请求,连接没有打开

poll的简单程序服务端(linux)

  1. #poll 异步I/O
  2.  
  3. import socket,select
  4.  
  5. s = socket.socket()
  6. host = "127.0.0.1"
  7. port = 8002
  8. s.bind((host,port))
  9.  
  10. fdmap = {s.fileno():s} #文件描述符到套接字对象的映射
  11.  
  12. s.listen(5)
  13. p = select.poll() #poll对象
  14. p.register(s) #注册一个文件描述符(带有fileno方法的对象)
  15. while True:
  16. events = p.poll()
  17. for fd,event in events:
  18. if fd == s.fileno(): #新的连接进来
  19. c,addr = s.accept()
  20. print("Got connectins from",addr)
  21. p.register(c) #注册一个文件描述符(带有fileno方法的对象)
  22. fdmap[c.fileno()] = c #添加到fdmap
  23. elif event & select.POLLIN: #读取来自文件描述符的数据
  24. data = fdmap[fd].recv(1024)
  25. if not data: #表示客户端断开
  26. print(fdmap[fd].getpeername(),"disconnected")
  27. p.unregister(fd) #清除文件描述符
  28. del fdmap[fd] #删除fdmap对应的key值
  29. else:
  30. print(data.decode())

poll程序客户端

  1. #poll 异步I/O
  2.  
  3. import socket
  4.  
  5. sk = socket.socket()
  6. sk.connect(("127.0.0.1",8002))
  7.  
  8. while True:
  9. command = input("--->>>")
  10. sk.sendall(command.encode())
  11. sk.close()

epoll方法:

epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

一 epoll操作过程

epoll操作过程需要三个接口,分别如下:

  1. int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大
  2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

1. int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值,参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议
当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数是对指定描述符fd执行op操作。
- epfd:是epoll_create()的返回值。
- op:表示op操作,用三个宏来表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。
- fd:是需要监听的fd(文件描述符)
- epoll_event:是告诉内核需要监听什么事

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待epfd上的io事件,最多返回maxevents个事件。
参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

  1. #_*_coding:utf-8_*_
  2. __author__ = 'Alex Li'
  3.  
  4. import socket, logging
  5. import select, errno
  6.  
  7. logger = logging.getLogger("network-server")
  8.  
  9. def InitLog():
  10. logger.setLevel(logging.DEBUG)
  11.  
  12. fh = logging.FileHandler("network-server.log")
  13. fh.setLevel(logging.DEBUG)
  14. ch = logging.StreamHandler()
  15. ch.setLevel(logging.ERROR)
  16.  
  17. formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
  18. ch.setFormatter(formatter)
  19. fh.setFormatter(formatter)
  20.  
  21. logger.addHandler(fh)
  22. logger.addHandler(ch)
  23.  
  24. if __name__ == "__main__":
  25. InitLog()
  26.  
  27. try:
  28. # 创建 TCP socket 作为监听 socket
  29. listen_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
  30. except socket.error as msg:
  31. logger.error("create socket failed")
  32.  
  33. try:
  34. # 设置 SO_REUSEADDR 选项
  35. listen_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  36. except socket.error as msg:
  37. logger.error("setsocketopt SO_REUSEADDR failed")
  38.  
  39. try:
  40. # 进行 bind -- 此处未指定 ip 地址,即 bind 了全部网卡 ip 上
  41. listen_fd.bind(('', 2003))
  42. except socket.error as msg:
  43. logger.error("bind failed")
  44.  
  45. try:
  46. # 设置 listen 的 backlog 数
  47. listen_fd.listen(10)
  48. except socket.error as msg:
  49. logger.error(msg)
  50.  
  51. try:
  52. # 创建 epoll 句柄
  53. epoll_fd = select.epoll()
  54. # 向 epoll 句柄中注册 监听 socket 的 可读 事件
  55. epoll_fd.register(listen_fd.fileno(), select.EPOLLIN)
  56. except select.error as msg:
  57. logger.error(msg)
  58.  
  59. connections = {}
  60. addresses = {}
  61. datalist = {}
  62. while True:
  63. # epoll 进行 fd 扫描的地方 -- 未指定超时时间则为阻塞等待
  64. epoll_list = epoll_fd.poll()
  65.  
  66. for fd, events in epoll_list:
  67. # 若为监听 fd 被激活
  68. if fd == listen_fd.fileno():
  69. # 进行 accept -- 获得连接上来 client 的 ip 和 port,以及 socket 句柄
  70. conn, addr = listen_fd.accept()
  71. logger.debug("accept connection from %s, %d, fd = %d" % (addr[0], addr[1], conn.fileno()))
  72. # 将连接 socket 设置为 非阻塞
  73. conn.setblocking(0)
  74. # 向 epoll 句柄中注册 连接 socket 的 可读 事件
  75. epoll_fd.register(conn.fileno(), select.EPOLLIN | select.EPOLLET)
  76. # 将 conn 和 addr 信息分别保存起来
  77. connections[conn.fileno()] = conn
  78. addresses[conn.fileno()] = addr
  79. elif select.EPOLLIN & events:
  80. # 有 可读 事件激活
  81. datas = ''
  82. while True:
  83. try:
  84. # 从激活 fd 上 recv 10 字节数据
  85. data = connections[fd].recv(10)
  86. # 若当前没有接收到数据,并且之前的累计数据也没有
  87. if not data and not datas:
  88. # 从 epoll 句柄中移除该 连接 fd
  89. epoll_fd.unregister(fd)
  90. # server 侧主动关闭该 连接 fd
  91. connections[fd].close()
  92. logger.debug("%s, %d closed" % (addresses[fd][0], addresses[fd][1]))
  93. break
  94. else:
  95. # 将接收到的数据拼接保存在 datas 中
  96. datas += data
  97. except socket.error as msg:
  98. # 在 非阻塞 socket 上进行 recv 需要处理 读穿 的情况
  99. # 这里实际上是利用 读穿 出 异常 的方式跳到这里进行后续处理
  100. if msg.errno == errno.EAGAIN:
  101. logger.debug("%s receive %s" % (fd, datas))
  102. # 将已接收数据保存起来
  103. datalist[fd] = datas
  104. # 更新 epoll 句柄中连接d 注册事件为 可写
  105. epoll_fd.modify(fd, select.EPOLLET | select.EPOLLOUT)
  106. break
  107. else:
  108. # 出错处理
  109. epoll_fd.unregister(fd)
  110. connections[fd].close()
  111. logger.error(msg)
  112. break
  113. elif select.EPOLLHUP & events:
  114. # 有 HUP 事件激活
  115. epoll_fd.unregister(fd)
  116. connections[fd].close()
  117. logger.debug("%s, %d closed" % (addresses[fd][0], addresses[fd][1]))
  118. elif select.EPOLLOUT & events:
  119. # 有 可写 事件激活
  120. sendLen = 0
  121. # 通过 while 循环确保将 buf 中的数据全部发送出去
  122. while True:
  123. # 将之前收到的数据发回 client -- 通过 sendLen 来控制发送位置
  124. sendLen += connections[fd].send(datalist[fd][sendLen:])
  125. # 在全部发送完毕后退出 while 循环
  126. if sendLen == len(datalist[fd]):
  127. break
  128. # 更新 epoll 句柄中连接 fd 注册事件为 可读
  129. epoll_fd.modify(fd, select.EPOLLIN | select.EPOLLET)
  130. else:
  131. # 其他 epoll 事件不进行处理
  132. continue
  133.  
  134. epoll socket echo server

epoll socket echo server

selectors模块

selectors模块已经封装了epoll,select方法;epoll优先级大于select

  1. import selectors
  2. import socket
  3.  
  4. sel = selectors.DefaultSelector()
  5.  
  6. def accept(sock, mask):
  7. conn, addr = sock.accept() # Should be ready
  8. print('accepted', conn, 'from', addr)
  9. conn.setblocking(False)
  10. sel.register(conn, selectors.EVENT_READ, read)
  11.  
  12. def read(conn, mask):
  13. data = conn.recv(1000) # Should be ready
  14. if data:
  15. print('echoing', repr(data), 'to', conn)
  16. conn.send(data) # Hope it won't block
  17. else:
  18. print('closing', conn)
  19. sel.unregister(conn)
  20. conn.close()
  21.  
  22. sock = socket.socket()
  23. sock.bind(('localhost', 10000))
  24. sock.listen(100)
  25. sock.setblocking(False)
  26. sel.register(sock, selectors.EVENT_READ, accept)
  27.  
  28. while True:
  29. events = sel.select()
  30. for key, mask in events:
  31. callback = key.data
  32. callback(key.fileobj, mask)

Python开发【第八章】:Socket的更多相关文章

  1. python开发学习-day08(socket高级、socketserver、进程、线程)

    s12-20160305-day08 *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: ...

  2. python socket编程---从使用Python开发一个Socket示例说到开发者的思维和习惯问题

    今天主要说的是一个开发者的思维和习惯问题. 思维包括编程的思维和解决一个具体问题的分析思维,分析思路,分析方法,甚至是分析工具. 无论是好习惯还是不好的习惯,都是在者一天一天的思维中形成的.那些不好的 ...

  3. Python服务器开发三:Socket

    Python服务器开发三:Socket   socket是操作系统中I/O的延续,它可以使进程和机器之间的通信成为可能.socket可以看成一个标准的文件描述符.不同的是文件需要用open()函数打开 ...

  4. Python开发【第八篇】:网络编程 Socket

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

  5. Python开发【第*篇】【Socket网络编程】

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

  6. Python全栈【Socket网络编程】

    Python全栈[socket网络编程] 本章内容: Socket 基于TCP的套接字 基于UDP的套接字 TCP粘包 SocketServer 模块(ThreadingTCPServer源码剖析) ...

  7. Python开发【第十四篇】:Web框架本质

    Web框架本质 众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...

  8. Python之路,Day8 - Socket编程进阶

    Python之路,Day8 - Socket编程进阶   本节内容: Socket语法及相关 SocketServer实现多并发 Socket语法及相关 socket概念 socket本质上就是在2台 ...

  9. Python开发爆破工具

    上一篇讲到了如何用Python开发字典,而当我们手里有了字典 就可以进一步去做爆破的任务了,可以用现成的工具,当然也可以自己写 接下来我就要一步一步来写爆破工具! 爆破MySQL: 想要爆破MySQL ...

  10. 通过编写聊天程序来熟悉python中多线程及socket的用法

    1.引言 Python中提供了丰富的开源库,方便开发者快速就搭建好自己所需要的应用程序.本文通过编写基于tcp/ip协议的通信程序来熟悉python中socket以及多线程的使用. 2.python中 ...

随机推荐

  1. [ JS 进阶 ] test, exec, match, replace

    https://segmentfault.com/a/1190000003497780 对了,这篇文章可能会涉及到正则表达式相关知识,所以推荐没有正则基础的去看看这篇入门文章:正则表达式30分钟入门教 ...

  2. <构建之法>第十一章、十二章有感

    十一章:软件设计与实现 工作时要懂得平衡进度和质量.我一直有一个困扰:像我们团队这次做 男神女神配 社区交友网,我负责主页的设计及内容模块,有个队友负责网站的注册和登录模块,有个队友负责搜索模块,有个 ...

  3. js,html,css注释大集合

    1.js注释: 单行注释,在注释内容前加符号 “//” <script type="text/javascript"> document.write("单行注 ...

  4. Linux之线程管理

    linux下查看线程数的几种方法   1. cat /proc/${pid}/status [root@limt01 2325]# ps -ef|grep xinetd|grep -v grep ro ...

  5. OSG中的HUD

    OSG中的HUD 所谓HUD节点,说白了就是无论三维场景中的内容怎么改变,它都能在屏幕上固定位置显示的节点. 实现要点: 关闭光照,不受场景光照影响,所有内容以同一亮度显示 关闭深度测试 调整渲染顺序 ...

  6. PHP面向对象学习三 类的抽象方法和类

    一个类中至少有一个方法是抽象的,我们称之为抽象类. 所以如果定义抽象类首先定义抽象方法. 1.类中至少有一个抽象方法 2.抽象方法不允许有{ } 3.抽象方法前面必须要加abstract 抽象类的几个 ...

  7. Invalid escape sequence(valid ones are \b \t \n \f \r \" \' \\)

    Invalid escape sequence(valid ones are \b \t \n \f \r \" \' \\) 在运行eclipse的相关程序代码时遇到了报错信息,查看控制台 ...

  8. 【原创】windows下搭建vue开发环境+IIS部署

    [原创]win10下搭建vue开发环境  如果要转发,请注明原作者和原产地,谢谢! 特别说明:下面任何命令都是在windows的命令行工具下进行输入,打开命令行工具的快捷方式如下图:     详细的安 ...

  9. 在Eclipse中使用JSHint检查JavaScript

    之前使用 JSlint 来校验 JavaScript 代码,发现灵活性不够,因此改用 JSHint.按照官方的说法,JSHint 是一个社区驱动(community-driven)的工具,用于检测Ja ...

  10. hao123列表的实现

    <!DOCTYPE html><html><head>        <meta http-equiv="Content-Type" co ...