大师兄的Python学习笔记(十五): Socket编程

大师兄的Python学习笔记(十四): 迭代器、生成器和协程
大师兄的Python学习笔记(十六): FTP与ftplib

一、关于互联网的一些基础知识

  • 当我们需要在不同的计算机运行程序,并进行通信时,就需要涉及到互联网编程。
  • 本篇案例的运行环境包括一台本地计算机和一台Linux服务器。
1.关于程序架构
1.1 C/S架构
  • 客户端(Client)与服务器端(Server)的架构。
  • 客户端一般泛指应用程序,对用户端的电脑操作环境依赖较大。
  • 比如: steam和qq等。


1.2 B/S架构
  • 浏览器端(Browser)与服务器端(Server)的架构。
  • 仅需要浏览器,就可以通过HTTP请求服务器端的资源和增删改查。
  • 比如微博网页版和各种网站等。


2.关于IP和Port
  • ip+port可以在互联网上定位某个设备的某个程序,比如(百度): 220.181.38.150:80
2.1 关于IP
  • IP是指互联网协议地址(Internet Protocol Address), 是设备在互联网上的唯一地址。

1)IPv4和IPv6

  • IPv4(Internet Protocol version 4)指的是互联网通信协议的第四版,是第一个被广泛部署的版本。
  • IPv4使用32位(4字节)地址,因此共有43亿(2^32-1)个地址, 全球IPv4地址已于2019年11月26日耗尽。
  • IPv6(Internet Protocol Version 6)指的是互联网通信协议的第六版,是用于替代IPv4的下一代IP协议。
  • IPv6将IPv4中32位的地址长度扩展到了128位,即有2^128-1个地址,可以将地球上每一粒沙子都赋予ip地址。

2)IP地址的分类

类型 号段 说明
A类 1.0.0.0-126.0.0.0 第一个字节为网络号,后三个字节为主机号。
该类IP地址的最前面为“0”,所以地址的网络号取值于1~126之间。
一般用于大型网络。
B类 128.0.0.0-191.255.0.0 前两个字节为网络号,后两个字节为主机号。
该类IP地址的最前面为“10”,所以地址的网络号取值于128~191之间。
一般用于中等规模网络。
C类 192.0.0.0-223.255.255.0 前三个字节为网络号,最后一个字节为主机号。
该类IP地址的最前面为“110”,所以地址的网络号取值于192~223之间。
一般用于小型网络。
D类 最前面为“1110” 多播地址。
地址的网络号取值于224~239之间。
一般用于多路广播用户。
E类 最前面为“1111” 保留地址。
地址的网络号取值于240~255之间。

3)几个需要记住的地址

地址 说明
0.0.0.0 -代表所有'不清楚'的主机和目的网络。
-这里的“不清楚”是指在本机的路由表里没有特定条目指明如何到达。
-对本机来说,它就是一个“收容所”,所有不认识的“三无”人员,一 律送进去。
255.255.255.255 受限广播地址。
对于本机来说,这个地址指本网段内的所有主机。
127.0.0.1 本机地址,主要用于测试。
与"localhost"相同。
192.168.0.1是本机ip地址。
224.0.0.1 组播地址,从224.0.0.0到239.255.255.255都是这样的地址。
224.0.0.1 特指所有主机
224.0.0.2 特指所有路由器。
168.254.x.x Windows操作系统在DHCP信息租用失败时自动给客户机分配的IP地址。
10.x.x.x, 172.16.x.x
~172.31.x.x,192.168.x.x
内网私有地址。
2.2 关于Port

1)Port的基础知识

  • Port(端口)是设备与外界通讯交流的出口。
  • 如果理解互联网是一个社区,每台设备是一栋建筑,ip是楼号,port就是具体的门牌号。
  • Port的范围是0-65535,其中知名端口(已占用)是0-1023。

2)一些常用端口

端口 服务
21 FTP
22 SSH
23 Telnet
25 SMTP
53 DNS(UDP)
69 TFTP(简单文件传输协议)
79 Finger(列出用户的一些信息)
80 HTTP
110 POP3
111 RPC远程过程调用
113 Windows验证服务
119 NNTP网络新闻组传输协议
135 RPC远程过程调用
137 NetBIOS
139 Windows文件和打印机共享, Unix中的Samba服务
161 SNMP简单网络管理协议
389 LDAP
443 HTTPS
445 SMB
1080 Socks代理服务
2601、2604 zebra路由,默认密码zebra
5900 VNC
8080 WWW代理服务
3.关于网络协议
  • 网络协议是为计算机网络中进行数据交换建立的规则、标准或约定的集合。
3.1 OSI七层模型
  • 开放式系统互联(Open System Interconnect)按照分工不同将互联网协议从逻辑上划分成七层。
  • 是ISO(国际标准化组织)组织在1985年研究的网络互联模型。
  • 同一层中的各网络节点都有相同的层次结构,具有同样的功能。
  • 同一节点内相邻层之间通过接口(可以是逻辑接口)进行通信。
  • 七层结构中的每一层使用下一层提供的服务,并且向其上层提供服务。
  • 不同节点的同等层按照协议实现对等层之间的通信。
说明
物理层
Physical
- 位于最低层,是传送信号的物理实体。
- 通过机械和电气的方式将各站点连接起来,组成物理通路,以便使数据流通过。
数据链路层
Data Link
- 在物理层所提供的数据传输电路的基础上,提供了一条无差错的数据链路。
- 进行二进制数据流的传输,并进行差错检测和流量控制。
网络层
Network
- 处理报文分组,完成分组的多路复用和分组交换,以及通信子网络间的数据据传输。
传输层
Transport
- 实现端点到端点的可靠数据传输。
会话层
Session
- 用于建立、控制和终止终端用户的实用进程间的逻辑信道的连接。
- 提供支持同步和管理应用进程间的对话服务,验证会话双方的身份,会话连接的恢复和释放。
表示层
Presentation
- 为用户应用进程提供了一系列统一的数据表示方式的服务。
- 解决不同系统不同终端所用的信息代码和控制字符等的差异。
应用层
Application
- 直接为端点用户提供服务。
3.2 TCP/IP四层模型
  • 在实际使用中OSI模型过于庞大,由技术人员自己开发的TCP/IP协议栈获得了更为广泛的应用。
  • TCP/IP协议栈是美国国防部高级研究计划局计算机网(ARPANET)和其后继因特网使用的参考模型。
TCP/IP四层模型 对应OSI七层模型 协议
数据链路层 物理层
数据链路层
IEEE 802.1A, IEEE 802.2到IEEE 802.11
FDDI, Ethernet, Arpanet, PDN, SLIP, PPP
网络层 网络层 IP, ICMP, ARP, RARP, AKP, UUCP
传输层 传输层 TCP, UDP
应用层 会话层
表示层
应用层
Telnet, Rlogin, SNMP, Gopher
SMTP, DNS
HTTP、TFTP, FTP, NFS, WAIS、SMTP
3.3 关于TCP和UDP协议

1)关于TCP协议(Transmission Control Protocol)

  • TCP是一种可靠的、面向连接的传输协议。
  • 传输效率低全双工通信(即同一时刻双向传输)。
  • 面向字节流。
  • 有点类似快递或挂号信,大部分文件传输、浏览器、邮件都使用TCP协议。
    2)关于UDP协议(User Datagram Protocol)
  • UDP协议是一种不可靠的、不面向链接的传输协议。
  • 安全性差,没有发送顺序。
  • 包大小不能超过64kb,但速度快。
  • 类似平邮信,即时通讯比如微信、qq会用到UDP协议。

二、SOCKET编程

  • 在[笔记十:多进程和多线程]中曾经提到过socket套接字,作为多进程间传输信息的一种方案。
  • 实际上Socket是一个网络通信的端点,能实现不同主机的进程通信,网络大多基于Socket通信。
  • Socket作为应用层与TCP/IP协议族(包括TCP协议和UDP协议)通信的中间软件抽象层,是一组接口。
  • 在设计模式中,Socket把复杂的TCP/IP协议族隐藏在Socket接口后面。
  • 对用户来说,只需要面对简单的接口,让Socket去组织数据,以符合指定的协议。
  • Socket编程通常将发起访问端和接收访问端分为Client端(用户端)和Server端(服务器端)。
1.Socket模块
  • Python提供了两个基本socket模块,分别是SocketSocketServer
  • Socket模块提供了在构建 socket 服务器和客户机时所需要的所有功能。
1.1 Socket模块的常用类方法

1) socket.socket(family,type)

  • 创建并返回socket对象。
  • family表示套接字家族,有两种类型:
家族名 说明
AF_UNIX 基于文件的
AF_INET 基于网络的
  • type表示套接字的种类:
种类 说明
SOCK_STREAM TCP套接字
SOCK_DGRAM UDP套接字
>>>import socket
>>>sock = socket.socket()
>>>print(type(sock))

2) socket.getfqdn(name)

  • 返回完整的主机名。
>>>import socket
>>>sock = socket.getfqdn('127.0.0.1')
>>>print(sock)
DESKTOP-xxxxx

3) socket.gethostbyname(hostname)

  • 返回主机的IP地址。
>>>import socket
>>>sock = socket.gethostbyname('www.baidu.com')
>>>print(sock)
220.181.38.150

4) socket.fromfd(fd, family, type)

  • 从文件描述符创建socket对象。
>>>import socket
>>>sock = socket.socket()
>>>print(sock)


>>>new_sock = socket.fromfd(sock.fileno(),sock.family,sock.type)
>>>print(new_sock)

1.2 Socket模块的常用实例方法

1) socket.bind((addres, port))

  • 将socket实例绑定到指定的ip和port上。
>>>import socket
>>>sock = socket.socket()
>>>sock.bind(('127.0.0.1',80))
>>>print(sock)

2) socket.accept()

  • 用于Server端接受Client端的socket。
  • 返回Client端的socket和地址。

3) socket.listen()

  • 使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。

4) socket.connect( (addres, port) )

  • 将socket连接到定义的主机和端口上。

5) socket.recv( buflen[, flags] )

  • 从socket中接收数据,最多buflen个字符。

6) socket.recvfrom( buflen[, flags] )

  • 从 socket 中接收数据,最多 buflen 个字符,同时返回数据来源的远程主机和端口号。

7) socket.send( data[, flags] )

  • 通过socket发送数据

8) socket.send( data[, flags] ,addr)

  • 通过 socket 发送数据到指定地址。

9) socket.close()

  • 关闭socket。

10) socket.getsockopt( lvl, optname )

  • 获得指定socket的值。

11) socket.setsockopt( lvl, optname ,val)

  • 设置指定 socket 选项的值。
>>>#Client端
>>>import socket
>>>sock = socket.socket()
>>>sock.connect(("127.0.0.1",10005)) # 连接服务器
>>>sock.send(b"from client...") # 发送
>>>ret = sock.recv(1024) # 接收
>>>print(ret)
>>>sock.close() # 关闭套接字
b'from server...'

>>>#Server端
>>>import socket
>>>sock = socket.socket()
>>>sock.bind(("127.0.0.1",10005))
>>>sock.listen() # 监听sock
>>>conn,addr = sock.accept() # 接受客户端socket
>>>ret = conn.recv(1024) # 接受客户端信息
>>>print(ret)
>>>conn.send(b'from server...') # 向客户端发送信息
>>>print('Client Socket : ',conn)
>>>print('Client Address:',addr)
>>>conn.close() # 关闭客户端socket
>>>sock.close() # 关闭服务器socket
b'from client...'
Client Socket :  
Client Address: ('127.0.0.1', 58698)
2.基于UDP协议的Socket编程

1) Server端需要完成的任务

  • 建立Socket实例。
  • 为Socket实例绑定ip和port。
  • 接收Client端的访问。
  • 反馈信息(如果需要)。
#Server端
>>>import socket

>>># 服务器函数
>>>def server_sample():
>>>    # 1.建立socket对象
>>>    sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
>>>    addr = ("127.0.0.1",10005)

>>>    # 2.绑定地址
>>>    sock.bind(addr)

>>>    # 3.接收消息
>>>    data,addr = sock.recvfrom(1024)
>>>    text = data.decode() # 数据流解码
>>>    print(text)

>>>    # 4.返回消息
>>>    rsp = b"from serer..."
>>>    sock.sendto(rsp,addr)
>>>    sock.close()

>>>if __name__ == '__main__':
>>>    print("启动服务器...")
>>>    server_sample()
>>>    print("关闭服务器...")
启动服务器...
from client...
关闭服务器...

2) Client端需要完成的任务

  • 建立Socket实例。
  • 发送内容到Server端。
  • 接收Server端的反馈(如果有)。
#Client端
>>>import socket

>>>def client_sample():
>>>    # 建立socket实例
>>>    sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

>>>    # 发送内容到Server端
>>>    data = b'from client...'
>>>    sock.sendto(data,("127.0.0.1",10005))

>>>    # 接收Server端反馈
>>>    reply,addr = sock.recvfrom(1024)
>>>    text = reply.decode()
>>>    print(text)
>>>    sock.close()

>>>if __name__ == '__main__':
>>>    client_sample()
from serer...
3.基于TCP协议的Socket编程
  • 与UDP不同,TCP协议在每次传输之前先要建立一个双向管道。

1) Server端需要完成的任务

  • 建立Socket实例,此实例仅用于接收请求。
  • 为Socket实例绑定ip和port。
  • 监听接入的Socket。
  • 接受Client端访问的Socket,建立通讯链接通路。
  • 使用Client端Socket接收内容
  • 反馈信息(如果需要)。
  • 关闭链接通路。
>>># Server端
>>>import socket

>>>def server_sample():
>>>    # 1. 建立socket实例
>>>    sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

>>>    # 2. 绑定地址
>>>    addr = ("127.0.0.1",10005)
>>>    sock.bind(addr)

>>>    # 3. 开启监听模式
>>>    sock.listen(5)

>>>    while True:
>>>        # 4.接受访问,建立通讯链路
>>>        connection,address = sock.accept()

>>>        # 5.接受内容,内容解码
>>>        data = connection.recv(1024)
>>>        data = data.decode()
>>>        reply = ("Received {} from {}".format(data,address))

>>>        # 6 发送反馈
>>>        connection.send(reply.encode())
>>>        print(reply)

>>>        # 7.关闭链路
>>>        connection.close()

>>>if __name__ == '__main__':
>>>    print("Start Server...")
>>>    server_sample()
>>>    print("End Server...")
Received from client... from ('127.0.0.1', 62157)

2) Client端需要完成的任务

  • 建立Socket实例。
  • 与Server端建立链接通路。
  • 发送内容到Server端。
  • 接收Server端反馈(如果有)
  • 关闭链接通路
>>>import socket

>>>def client():
>>>    # 1.建立socketobj
>>>    sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

>>>    #2.建立链接
>>>    addr = ("127.0.0.1",10005)
>>>    sock.connect(addr)

>>>    #3.发送内容
>>>    msg = b"from client..."
>>>   sock.send(msg)

>>>    #4.接受反馈
>>>    rst = sock.recv(1024)
>>>    print(rst.decode())

>>>    #5.关闭链路
>>>    sock.close()

>>>if __name__ == '__main__':
>>>    client()
Received from client... from ('127.0.0.1', 62157)
4. 处理多个Client(用户端)
  • Server端在处理多个客户端的请求时,需要解决信息阻塞的问题。
4.1 多进程解决方案
  • 用派生服务器的方式编写Server端,避免Client端堵塞。
  • 使用多进程fork()方式派生服务器。
  • Server端:
# Linux服务器
# Server端
>>>import os,time,sys,socket

>>>host = '172.21.0.4' 
>>>post = 10005

>>>sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
>>>sock.bind((host,post))
>>>sock.listen(5)

>>>activeChildren = [] # 子进程容器
>>>def replace_children():
>>>    # 移除子进程
>>>    while activeChildren:
>>>        pid,stat = os.waitpid(0,os.WNOHANG)
>>>        if not pid:break
 >>>       activeChildren.remove(pid)

>>>def handle_client(conn):
>>>    # 处理子进程
>>>    time.sleep(5)
>>>    while True:
>>>        data = conn.recv(1024)
>>>        if not data:break
>>>        reply = 'Receive {} at {}'.format(data,time.ctime(time.time()))
>>>        conn.send(reply.encode())
>>>    conn.close()
>>>    os._exit(0) # 如果不退出,每个子进程会在返回后继续运行

>>>def main():
>>>    # 监听接入
>>>    while True:
>>>        print("awaiting connection")
>>>        connection,address = sock.accept()
>>>        print("Server connected by {} at {}".format(address,time.ctime(time.time())))
>>>        replace_children()   
>>>        childPid = os.fork()
>>>        if childPid == 0: 
>>>            handle_client(connection)
>>>        else: 
>>>            activeChildren.append(childPid)

>>>if __name__ == '__main__':
>>>    print("starting server..")  
>>>    main()
>>>    print("stoping server...")
Server connected by ('117.119.102.10', 58962) at Sun Apr 12 18:26:30 2020
awaiting connection
Server connected by ('117.119.102.10', 58968) at Sun Apr 12 18:26:32 2020
awaiting connection
Server connected by ('117.119.102.10', 58970) at Sun Apr 12 18:26:34 2020
awaiting connection
  • Client端:
# 本地PC端
>>># Client端
>>>import socket,threading,time

>>>def client():
>>>    sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

>>>    addr = ("123.206.41.120",10005)
>>>    sock.connect(addr)

>>>    msg = b"from client..."
>>>    sock.send(msg)

>>>    rst = sock.recv(1024)
>>>    print(rst.decode())

>>>    sock.close()

>>>if __name__ == '__main__':
>>>    for i in range(3): # 用线程模拟多个Client端
>>>        t = threading.Thread(target=client,args=())
>>>        t.start()
>>>        time.sleep(2)
Receive b'from client...' at Sun Apr 12 18:26:35 2020
Receive b'from client...' at Sun Apr 12 18:26:37 2020
Receive b'from client...' at Sun Apr 12 18:26:39 2020
4.2 多线程解决方案
  • 相比进程,线程的消耗更小(尤其是在Windows下),可以跨平台部署,且不用担心线程的回收问题。
  • Server端:
>>>import time,threading,socket

>>>host='127.0.0.1'
>>>port=10005

>>>sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
>>>sock.bind((host,port))
>>>sock.listen(5)

>>>now = lambda: time.ctime(time.time())

>>>def handle_client(connection):
>>>    time.sleep(5)
>>>    while True:
>>>        data = connection.recv(1024)
>>>        if not data:break
>>>        reply = "got {0} at {1}".format(data,now())
>>>        connection.send(reply.encode())
>>>    connection.close()

>>>def main():
>>>    while True:
>>>        connection,address = sock.accept()
>>>        print('Server connected by {0} at {1}...'.format(address,now()))
>>>        t = threading.Thread(target=handle_client,args=(connection,))
>>>        t.start()

>>>if __name__ == '__main__':
>>>    main()
Server connected by ('127.0.0.1', 54071) at Mon Apr 13 09:34:59 2020...
Server connected by ('127.0.0.1', 54073) at Mon Apr 13 09:35:01 2020...
Server connected by ('127.0.0.1', 54077) at Mon Apr 13 09:35:03 2020...
  • Client端:
# Client端
>>>import socket,threading,time

>>>def client():
>>>    sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

>>>    addr = ("127.0.0.1",10005)
>>>    sock.connect(addr)

>>>    msg = b"from client..."
>>>    sock.send(msg)

>>>    rst = sock.recv(1024)
>>>    print(rst.decode())
>>>    sock.close()

>>>if __name__ == '__main__':
>>>    for i in range(3): # 用线程模拟多个Client端
>>>        t = threading.Thread(target=client,args=())
>>>        t.start()
>>>        time.sleep(2)
got b'from client...' at Mon Apr 13 09:35:04 2020
got b'from client...' at Mon Apr 13 09:35:06 2020
got b'from client...' at Mon Apr 13 09:35:08 2020
4.3 socketserver包解决方案
  • 标准库中的socketserver包提供了更简单的多线程服务器或多进程服务器方法:ThreadingTCPServer(address,server)ForkingTCPServer(address,server)
  • UDP服务器同理。
  • Server端:
>>>import socketserver,time

>>>host='127.0.0.1'
>>>port=10005
>>>now = lambda: time.ctime(time.time())

>>>class MyClientHandler(socketserver.BaseRequestHandler):
>>>    def handle(self):
>>>        # 处理每个接入的请求
>>>        print('Server connected by {0} at {1}...'.format(self.client_address, now()))
>>>        time.sleep(5)
>>>        while True:
>>>            data = self.request.recv(1024)
>>>            if not data:break
>>>            reply = "got {0} at {1}".format(data, now())
>>>            self.request.send(reply.encode())
>>>        self.request.close()

>>>def main():
>>>    server = socketserver.ThreadingTCPServer((host,port),MyClientHandler)
>>>    server.serve_forever()

>>>if __name__ == '__main__':
>>>    print('Starting server...')
>>>    main()
>>>    print('Stoping Server...')
Starting server...
Server connected by ('127.0.0.1', 60706) at Mon Apr 13 11:22:27 2020...
Server connected by ('127.0.0.1', 60708) at Mon Apr 13 11:22:29 2020...
Server connected by ('127.0.0.1', 60710) at Mon Apr 13 11:22:31 2020...
  • Client端:
>>># Client端
>>>import socket,threading,time

>>>def client():
>>>    sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

>>>    addr = ("127.0.0.1",10005)
>>>    sock.connect(addr)

>>>    msg = b"from client..."
>>>    sock.send(msg)

>>>    rst = sock.recv(1024)
>>>    print(rst.decode())
>>>    sock.close()

>>>if __name__ == '__main__':
>>>    for i in range(3): # 用线程模拟多个Client端
>>>        t = threading.Thread(target=client,args=())
>>>        t.start()
>>>        time.sleep(2)
got b'from client...' at Mon Apr 13 11:22:32 2020
got b'from client...' at Mon Apr 13 11:22:34 2020
got b'from client...' at Mon Apr 13 11:22:36 2020
4.4 协程解决方案
  • 用asyncio包创建协程版TCP服务器。
  • Server端:
>>>import time,asyncio

>>>host='127.0.0.1'
>>>port=10005
>>>now = lambda: time.ctime(time.time())

>>>async def handle_client(reader,writer):
>>>    data = await reader.read(1024)
>>>    message = data.decode()
>>>    address = writer.get_extra_info('peername')
>>>    print('Server connected by {0} at {1}...'.format(address, now()))

>>>    reply = "got {0} at {1}".format(message,now())
>>>    writer.write(reply.encode())
>>>    await writer.drain()

>>>    print('Closing Client:{}'.format(address))
>>>    writer.close()

>>>def main():
>>>    loop = asyncio.get_event_loop() # 创建loop
>>>    task = asyncio.start_server(handle_client,host,port,loop=loop) # 将事件抛到loop中
>>>    server = loop.run_until_complete(task)
>>>    print('serving on {}'.format(server.sockets[0].getsockname()))

>>>    try:
>>>        loop.run_forever()
>>>    except KeyboardInterrupt: # 用Ctrl+c打断服务器
>>>        pass

>>>    server.close()
>>>    loop.run_until_complete(server.wait_closed())
>>>    loop.close()

>>>if __name__ == '__main__':
>>>    print('Starting server...')
>>>    main()
>>>    print('Stoping Server...')
Starting server...
serving on ('127.0.0.1', 10005)
Server connected by ('127.0.0.1', 59093) at Mon Apr 13 10:46:30 2020...
Closing Client:('127.0.0.1', 59093)
Server connected by ('127.0.0.1', 59095) at Mon Apr 13 10:46:32 2020...
Closing Client:('127.0.0.1', 59095)
Server connected by ('127.0.0.1', 59096) at Mon Apr 13 10:46:34 2020...
Closing Client:('127.0.0.1', 59096)
  • Client端:
# Client端
>>>import socket,threading,time

>>>def client():
>>>    sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

>>>    addr = ("127.0.0.1",10005)
>>>    sock.connect(addr)

>>>    msg = b"from client..."
>>>    sock.send(msg)

>>>    rst = sock.recv(1024)
>>>    print(rst.decode())

>>>    sock.close()

>>>if __name__ == '__main__':
>>>    for i in range(3): # 用线程模拟多个Client端
>>>        t = threading.Thread(target=client,args=())
>>>        t.start()
>>>        time.sleep(2)
got from client... at Mon Apr 13 10:46:30 2020
got from client... at Mon Apr 13 10:46:32 2020
got from client... at Mon Apr 13 10:46:34 2020
4.5 select包 I/O多路复用解决方案
  • 用轮询的方式在单线程中实现多重通道的传输。
  • select.select(rlist, wlist, xlist,timeout)函数包含三个返回值:readable, writable, exceptional
参数 含义
rlist 可读集合
wlist 可写集合
xlist 异常集合
timeout 等待期限
0:立即返回
省略:等待至少一个对象准备好。
  • Server端
>>>import time,socket
>>>from select import select

>>>def main():
>>>    host = '127.0.0.1'
>>>    port = 10005
>>>    now = lambda: time.ctime(time.time())
>>>    num_socks = 3  # 允许的接入数量

>>>    mainsocks,readsocks,writesocks = [],[],[]
>>>    for i in range(num_socks):
>>>        # 建立一个连接
>>>        portsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
>>>        portsock.bind((host,port))
>>>        portsock.listen(5)
>>>        # 将连接放到列表中
>>>        mainsocks.append(portsock)
>>>        readsocks.append(portsock)
>>>        port += 1

>>>    print('loop starting...')
>>>    while True:
>>>        readables,writeables,exceptions = select(readsocks,writesocks,[])
>>>        for sock in readables:
>>>          if sock in mainsocks: # 如果sock已经准备好
>>>                # 如果是新的接入
>>>                newsock,address = sock.accept()
>>>                print('Server connected by {0} at {1}...'.format(address, now()))
>>>                readsocks.append(newsock) # 加入到已读列表
>>>            else:
>>>                # 如果是池子中的连接
>>>                data = sock.recv(1024)
>>>                if not data:
>>>                    sock.close()
>>>                    readsocks.remove(sock)
>>>                else:
>>>                    # 可能会阻塞的部分
>>>                    reply = "got {0} at {1}".format(data, now())
>>>                    sock.send(reply.encode())

>>>if __name__ == '__main__':
>>>    main()
loop starting...
Server connected by ('127.0.0.1', 56875) at Mon Apr 13 14:49:33 2020...
Server connected by ('127.0.0.1', 56877) at Mon Apr 13 14:49:35 2020...
Server connected by ('127.0.0.1', 56879) at Mon Apr 13 14:49:37 2020...
  • Client端:
>>># Client端
>>>import socket,threading,time

>>>def client():
>>>    sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

>>>    addr = ("127.0.0.1",10005)
>>>    sock.connect(addr)

>>>    msg = b"from client..."
>>>    sock.send(msg)

>>>    rst = sock.recv(1024)
>>>    print(rst.decode())

>>>    sock.close()

>>>if __name__ == '__main__':
>>>    for i in range(3): # 用线程模拟多个Client端
>>>        t = threading.Thread(target=client,args=())
>>>        t.start()
>>>        time.sleep(2)
got b'from client...' at Mon Apr 13 14:49:33 2020
got b'from client...' at Mon Apr 13 14:49:35 2020
got b'from client...' at Mon Apr 13 14:49:37 2020
5. Socket流的重定向
  • 使用socket.makefile(),将Socket流与文件相关联。
  • socket.makefile(mode ='r',buffering = None,*,encoding = None,errors = None,newline = None )
  • mode参数支持'r''w',分别对应读写。
  • Server端:
>>>import time,sys,socket

>>>host='127.0.0.1'
>>>port=10005

>>>sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
>>>sock.bind((host,port))
>>>sock.listen(1)

>>>now = lambda: time.ctime(time.time())

>>>def handle_client(connection):
>>>    time.sleep(5)
>>>    while True:
>>>        file = connection.makefile('r') # 将输入流关联到file
>>>        data = file.readline().rstrip() # 读取第一行
>>>        if not data:break
>>>        reply = "got {0} at {1}".format(data,now())
>>>        connection.send(reply.encode())
>>>    connection.close()

>>>def main():
>>>    while True:
>>>        connection,address = sock.accept()
>>>        print('Server connected by {0} at {1}...'.format(address, now()))
>>>        handle_client(connection)

>>>if __name__ == '__main__':
>>>    print('starting server...')
>>>    main()
>>>    print('stoping server...')
starting server...
Server connected by ('127.0.0.1', 56127) at Mon Apr 13 16:19:18 2020...
  • Client端:
>>># Client端
>>>import socket,sys,time

>>>host = "127.0.0.1"
>>>port = 10005
>>>sysout = sys.stdout # 备份输出流

>>>def client():
>>>    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>>    sock.connect((host, port))

>>>    # 将输出流导入file
>>>    file = sock.makefile('w')
>>>    sys.stdout = file

>>>    msg = b"from client..."
>>>    print(msg)
>>>    sys.stdout.flush() # 闪现缓存

>>>    sys.stdout = sysout # 将输出流恢复默认
>>>    rst = sock.recv(1024)
>>>    print(rst.decode())

>>>    sock.close()

>>>if __name__ == '__main__':
>>>    client()
got b'from client...' at Mon Apr 13 16:19:23 2020

参考资料


  • https://blog.csdn.net/u010138758/article/details/80152151 J-Ombudsman
  • https://www.cnblogs.com/zhuluqing/p/8832205.html moisiet
  • https://www.runoob.com 菜鸟教程
  • http://www.tulingxueyuan.com/ 北京图灵学院
  • http://www.imooc.com/article/19184?block_id=tuijian_wz#child_5_1 两点水
  • https://blog.csdn.net/weixin_44213550/article/details/91346411 python老菜鸟
  • https://realpython.com/python-string-formatting/ Dan Bader
  • https://www.liaoxuefeng.com/ 廖雪峰
  • https://blog.csdn.net/Gnewocean/article/details/85319590 新海说
  • https://www.cnblogs.com/Nicholas0707/p/9021672.html Nicholas
  • https://www.cnblogs.com/dalaoban/p/9331113.html 超天大圣
  • 《Python学习手册》Mark Lutz
  • 《Python编程 从入门到实践》Eric Matthes

本文作者:大师兄(superkmi)

你可能感兴趣的:(大师兄的Python学习笔记(十五): Socket编程)